System.Data.SQLite Close() не выпускает файл базы данных
У меня проблема с закрытием базы данных перед попыткой удалить файл. Код просто
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);
18 ответов
Я столкнулся с той же проблемой некоторое время назад, когда писал уровень абстракции БД для 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);
Удачи с этим, и я надеюсь, что это поможет
Просто GC.Collect()
не работал для меня
Я должен был добавить GC.WaitForPendingFinalizers()
после GC.Collect()
для того, чтобы приступить к удалению файла.
Следующее работало для меня:
MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()
Дополнительная информация: Соединения объединяются в SQLite для повышения производительности. Это означает, что при вызове метода Close для объекта соединения соединение с базой данных может оставаться активным (в фоновом режиме), чтобы следующий метод Open стал быстрее. Когда вы знаете, что вам больше не нужно новое соединение, вызов ClearAllPools закрывает все соединения, которые существуют в фоновом режиме, и дескриптор файла (s?) для файла db освобождается. Затем файл db может быть удален, удален или использован другим процессом.
Была похожая проблема, хотя решение для сборщика мусора не устранило ее.
Найдены разборки SQLiteCommand
а также SQLiteDataReader
объекты после использования спасли меня с помощью сборщика мусора вообще.
SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
В моем случае я создавал 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
У меня была похожая проблема, я пробовал решение с GC.Collect
но, как уже отмечалось, может пройти много времени, прежде чем файл станет не заблокированным.
Я нашел альтернативное решение, которое включает в себя избавление от SQLiteCommand
s в TableAdapters, см. этот ответ для получения дополнительной информации.
У меня была такая же проблема с EF и System.Data.Sqlite
,
Для меня я нашел SQLiteConnection.ClearAllPools()
а также GC.Collect()
уменьшит частоту блокировки файлов, но все равно будет происходить время от времени (примерно в 1% случаев).
Я расследовал, и кажется, что некоторые SQLiteCommand
s, которые создает EF, не удаляются и все еще имеют свойство Connection, установленное на закрытое соединение. Я попытался избавиться от них, но Entity Framework затем выдаст исключение во время следующего DbContext
прочитайте - кажется, что EF иногда все еще использует их после закрытия соединения.
Мое решение состояло в том, чтобы гарантировать, что свойство Connection установлено в Null
когда соединение закрывается на этих SQLiteCommand
s. Кажется, этого достаточно, чтобы снять блокировку файла. Я тестировал приведенный ниже код и не видел проблем с блокировкой файлов после нескольких тысяч тестов:
public static class ClearSQLiteCommandConnectionHelper
{
private static readonly List<SQLiteCommand> OpenCommands = new List<SQLiteCommand>();
public static void Initialise()
{
SQLiteConnection.Changed += SqLiteConnectionOnChanged;
}
private static void SqLiteConnectionOnChanged(object sender, ConnectionEventArgs connectionEventArgs)
{
if (connectionEventArgs.EventType == SQLiteConnectionEventType.NewCommand && connectionEventArgs.Command is SQLiteCommand)
{
OpenCommands.Add((SQLiteCommand)connectionEventArgs.Command);
}
else if (connectionEventArgs.EventType == SQLiteConnectionEventType.DisposingCommand && connectionEventArgs.Command is SQLiteCommand)
{
OpenCommands.Remove((SQLiteCommand)connectionEventArgs.Command);
}
if (connectionEventArgs.EventType == SQLiteConnectionEventType.Closed)
{
var commands = OpenCommands.ToList();
foreach (var cmd in commands)
{
if (cmd.Connection == null)
{
OpenCommands.Remove(cmd);
}
else if (cmd.Connection.State == ConnectionState.Closed)
{
cmd.Connection = null;
OpenCommands.Remove(cmd);
}
}
}
}
}
Чтобы использовать просто позвонить ClearSQLiteCommandConnectionHelper.Initialise();
в начале загрузки приложения. Затем будет храниться список активных команд и будет установлено их соединение Null
когда они указывают на соединение, которое закрыто.
Попробуйте это... этот пытается все вышеперечисленные коды... работал для меня
Reader.Close()
connection.Close()
GC.Collect()
GC.WaitForPendingFinalizers()
command.Dispose()
SQLite.SQLiteConnection.ClearAllPools()
надеюсь, это поможет
Использование GC.WaitForPendingFinalizers()
Пример:
Con.Close();
GC.Collect();`
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Причиной этого, по-видимому, является функция под названием «Пулинг». Добавление "Pooling=false" к строке подключения приводит к освобождению DB-файла с помощью "connection.Close()".
См. FAQ по пулу соединений здесь:https://www.devart.com/dotconnect/sqlite/docs/FAQ.html#q54 .
Лучший ответ, который сработал для меня.
dbConnection.Close();
System.Data.SQLite.SQLiteConnection.ClearAllPools();
GC.Collect();
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Была похожая проблема. Вызов сборщика мусора мне не помог. Я нашел способ решить проблему
Автор также написал, что он делал запросы 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.
Я верю призыв к SQLite.SQLiteConnection.ClearAllPools()
это самое чистое решение. Насколько я знаю, это не правильно, чтобы вручную позвонить GC.Collect()
в среде WPF. Хотя я не заметил проблемы, пока не обновил до System.Data.SQLite
1.0.99.0 в 3/2016
Я боролся с подобной проблемой. Позор мне... Я наконец понял, что Читатель не был закрыт. Почему-то я думал, что Reader будет закрыт при закрытии соответствующего соединения. Очевидно, что GC.Collect() не работает для меня.
Обертывание Reader с помощью оператора using: также хорошая идея. Вот краткий тестовый код.
static void Main(string[] args)
{
try
{
var dbPath = "myTestDb.db";
ExecuteTestCommand(dbPath);
File.Delete(dbPath);
Console.WriteLine("DB removed");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.Read();
}
private static void ExecuteTestCommand(string dbPath)
{
using (var connection = new SQLiteConnection("Data Source=" + dbPath + ";"))
{
using (var command = connection.CreateCommand())
{
command.CommandText = "PRAGMA integrity_check";
connection.Open();
var reader = command.ExecuteReader();
if (reader.Read())
Console.WriteLine(reader.GetString(0));
//without next line database file will remain locked
reader.Close();
}
}
}
Может быть, вам не нужно иметь дело с GC вообще. Пожалуйста, проверьте все ли sqlite3_prepare
доработан.
Для каждого sqlite3_prepare
вам нужен корреспондент sqlite3_finalize
,
Если вы не завершите правильно, sqlite3_close
не закроет соединение.
Это работает для меня, но я заметил, что иногда файлы журнала -wal -shm не удаляются при закрытии процесса. Если вы хотите, чтобы SQLite удалял файлы -wal -shm, когда все соединения закрыты, последнее закрытое соединение ДОЛЖНО БЫТЬ не доступным только для чтения. Надеюсь, это кому-нибудь поможет.
Ожидание сборщика мусора может не выпустить базу данных все время, и это случилось со мной. Когда в базе данных SQLite возникает исключение какого-либо типа, например, при попытке вставить строку с существующим значением для PrimaryKey, он будет содержать файл базы данных, пока вы не удалите его. Следующий код перехватывает исключение SQLite и отменяет проблемную команду.
SQLiteCommand insertCommand = connection.CreateCommand();
try {
// some insert parameters
insertCommand.ExecuteNonQuery();
} catch (SQLiteException exception) {
insertCommand.Cancel();
insertCommand.Dispose();
}
Если вы не обрабатываете исключения проблемных команд, сборщик мусора не может ничего с ними сделать, потому что есть некоторые необработанные исключения для этих команд, поэтому они не являются мусором. Этот метод обработки работал хорошо для меня с ожиданием сборщика мусора.
Я использовал SQLite 1.0.101.0 с EF6 и у меня были проблемы с блокировкой файла после удаления всех соединений и сущностей.
Ситуация ухудшилась с обновлениями от EF, которые блокировали базу данных после их завершения. GC.Collect() был единственным обходным путем, который помог, и я начинал отчаиваться.
В отчаянии я попробовал ClearSQLiteCommandConnectionHelper Оливера Уикендена (см. Его ответ от 8 июля). Фантастика. Все проблемы с блокировкой исчезли! Спасибо Оливер.