System.Data.SQLite Close () не выпускает файл базы данных - PullRequest
81 голосов
/ 15 декабря 2011

У меня проблема с закрытием базы данных перед попыткой удалить файл. Код просто

 myconnection.Close();    
 File.Delete(filename);

И Delete выдает исключение, что файл все еще используется. Через несколько минут я снова попытался удалить Delete () в отладчике, так что это не проблема синхронизации.

У меня есть код транзакции, но он не запускается вообще до вызова Close (). Поэтому я уверен, что это не открытая сделка. Команды sql между open и close просто выбираются.

ProcMon показывает мою программу и мой антивирус, просматривая файл базы данных. Он не показывает, что моя программа освобождает файл db после закрытия ().

Visual Studio 2010, C #, System.Data.SQLite версия 1.0.77.0, Win7

Я видел двухлетнюю ошибку, похожую на эту, но в журнале изменений написано, что она исправлена.

Есть что-нибудь еще, что я могу проверить? Есть ли способ получить список любых открытых команд или транзакций?


Новый, рабочий код:

 db.Close();
 GC.Collect();   // yes, really release the db

 bool worked = false;
 int tries = 1;
 while ((tries < 4) && (!worked))
 {
    try
    {
       Thread.Sleep(tries * 100);
       File.Delete(filename);
       worked = true;
    }
    catch (IOException e)   // delete only throws this on locking
    {
       tries++;
    }
 }
 if (!worked)
    throw new IOException("Unable to close file" + filename);

Ответы [ 16 ]

88 голосов
/ 15 декабря 2011

Обнаружил ту же проблему некоторое время назад, когда писал уровень абстракции БД для C #, и я так и не смог найти причину проблемы.Я просто закончил тем, что выдал исключение, когда вы пытались удалить базу данных SQLite, используя мою библиотеку.

Во всяком случае, сегодня днем ​​я снова просмотрел все это и решил, что постараюсь выяснить, почему это происходит один раз.и для всех, вот что я нашел до сих пор.

Что происходит, когда вы звоните SQLiteConnection.Close(), это то, что (вместе с рядом проверок и других вещей) SQLiteConnectionHandle указывает наЭкземпляр базы данных SQLite расположен.Это делается с помощью вызова SQLiteConnectionHandle.Dispose(), однако это на самом деле не освобождает указатель, пока сборщик мусора в CLR не выполнит некоторую сборку мусора.Поскольку SQLiteConnectionHandle переопределяет функцию CriticalHandle.ReleaseHandle() для вызова sqlite3_close_interop() (через другую функцию), это не закрывает базу данных.

С моей точки зрения, это очень плохой способ сделать что-то, так как программистна самом деле не уверен, когда база данных будет закрыта, но это именно так, как это было сделано, поэтому я думаю, что мы должны с этим пока жить или внести несколько изменений в System.Data.SQLite.Любые добровольцы могут сделать это, к сожалению, у меня нет времени сделать это до следующего года.

TL; DR Решение состоит в том, чтобы принудительно собрать GC после вашего звонка на SQLiteConnection.Close()и перед вашим звонком на File.Delete().

Вот пример кода:

string filename = "testFile.db";
SQLiteConnection connection = new SQLiteConnection("Data Source=" + filename + ";Version=3;");
connection.Close();
GC.Collect();
GC.WaitForPendingFinalizers();
File.Delete(filename);

Удачи с этим, и я надеюсь, что это поможет

55 голосов
/ 01 июля 2014

Просто GC.Collect() у меня не сработало.

Мне пришлось добавить GC.WaitForPendingFinalizers() после GC.Collect(), чтобы продолжить удаление файла.

15 голосов
/ 05 апреля 2013

В моем случае я создавал SQLiteCommand объекты без явного их удаления.

var command = connection.CreateCommand();
command.CommandText = commandText;
value = command.ExecuteScalar();

Я завернул свою команду в оператор using, и это исправило мою проблему.

static public class SqliteExtensions
{
    public static object ExecuteScalar(this SQLiteConnection connection, string commandText)
    {
        using (var command = connection.CreateCommand())
        {
            command.CommandText = commandText;
            return command.ExecuteScalar();
        }
    }
}

Оператор using гарантирует, что Dispose вызывается, даже если возникает исключение.

Тогда намного проще выполнять команды.

value = connection.ExecuteScalar(commandText)
// Command object created and disposed
11 голосов
/ 27 января 2015

Была похожая проблема, хотя решение для сборщика мусора не устранило ее.

Найденное удаление объектов SQLiteCommand и SQLiteDataReader после использования спасло меня от использования сборщика мусора вообще.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
11 голосов
/ 04 июля 2014

У меня сработало следующее:

MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()

Подробнее : Соединения объединяются в SQLite для повышения производительности. Это означает, что когда вы вызываете метод Close для объекта соединения, соединение с базой данных может оставаться живым (в фоновом режиме), так что следующий метод Open становится быстрее. Когда вы знаете, что не делаете Чтобы больше не хотеть нового соединения, вызов ClearAllPools закрывает все соединения, которые существуют в фоновом режиме, и дескриптор файла (s?) для файла db освобождается. Затем файл db может быть удален, удален или использован другим процессом.

9 голосов
/ 01 октября 2012

У меня была похожая проблема, я пробовал решение с GC.Collect, но, как уже отмечалось, может пройти много времени, прежде чем файл станет не заблокированным.

Я нашел альтернативное решение, которое включает удаление базовых SQLiteCommand s в TableAdapters, см. этот ответ для получения дополнительной информации.

3 голосов
/ 26 октября 2017

Была похожая проблема.Вызов сборщика мусора мне не помог.ПОСЛЕДНЕЕ Я нашел способ решить проблему

Автор также написал, что он делал запросы SELECT к этой базе данных, прежде чем пытаться ее удалить.У меня такая же ситуация.

У меня есть следующий код:

SQLiteConnection bc;
string sql;
var cmd = new SQLiteCommand(sql, bc);
SQLiteDataReader reader = cmd.ExecuteReader();
reader.Read();
reader.Close(); // when I added that string, the problem became solved.

Кроме того, мне не нужно закрывать соединение с базой данных и вызывать сборщик мусора.Все, что мне нужно было сделать, это закрыть программу чтения, которая была создана при выполнении запроса SELECT

3 голосов
/ 18 сентября 2017

Использование GC.WaitForPendingFinalizers()

Пример:

Con.Close();  
GC.Collect();`
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
3 голосов
/ 19 августа 2016

Попробуйте это ... этот пробует все выше коды ... работал для меня

    Reader.Close()
    connection.Close()
    GC.Collect()
    GC.WaitForPendingFinalizers()
    command.Dispose()
    SQLite.SQLiteConnection.ClearAllPools()

Надеюсь, что помогает

2 голосов
/ 20 сентября 2016

Может быть, вам вообще не нужно иметь дело с GC.Пожалуйста, проверьте, завершено ли все sqlite3_prepare.

Для каждого sqlite3_prepare вам нужен корреспондент sqlite3_finalize.

Если вы не завершите корректно, sqlite3_close не закроет соединение.

...