У меня возникла проблема с закрытием моей базы данных перед попыткой удалить файл. Код просто
myconnection.Close();
File.Delete(filename);
И Delete выдает исключение, которое файл все еще используется. Я повторил попытку удаления() в отладчике через несколько минут, так что это не проблема времени.
У меня есть код транзакции, но он не запускается вообще до вызова Close(). Поэтому я уверен, что это не открытая транзакция. Команды sql между open и close просто выбираются.
ProcMon показывает мою программу и мой антивирус, смотрящий на файл базы данных. Он не показывает мою программу, освобождающую файл db после закрытия().
Visual Studio 2010, С#, 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);
Некоторое время назад я сталкивался с одной и той же проблемой при написании слоя абстракции DB для С#, и я никогда не узнал, в чем проблема. Я только что бросил исключение, когда вы попытались удалить SQLite DB, используя мою библиотеку.
Во всяком случае, сегодня днем я снова просмотрел все это и решил, что я попытаюсь выяснить, почему это происходит раз и навсегда, вот что я нашел до сих пор.
Что происходит, когда вы вызываете 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()
, чтобы продолжить удаление файла.
GC.Collect()
просто запускает сборку мусора, которая является асинхронной, поэтому, чтобы убедиться, что все вычищено, нужно явно ее дождаться.
В моем случае я создавал объекты 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
Имел подобную проблему, хотя решение сборщика мусора не исправило его.
Найденное размещение объектов SQLiteCommand
и SQLiteDataReader
после использования спасло меня, используя сборщик мусора.
SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
SQLiteCommand
даже если позже вы SQLiteCommand
переменную SQLiteCommand
.
Следующие работали для меня:
MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()
Дополнительная информация: Соединения объединяются SQLite для повышения производительности. Это означает, что когда вы вызываете метод Close для объекта соединения, соединение с базой данных может оставаться в живых (в фоновом режиме), чтобы следующий метод Open стал быстрее. Когда вы знаете, что вы не знаете, t требуется новое соединение, вызов ClearAllPools закрывает все соединения, которые находятся в фоновом режиме, и дескриптор файла (s?) в файл db освобождается. Затем файл db может быть удален, удален или использован другим процессом.
SQLiteConnectionPool.Shared.Reset()
. Это закроет все открытые соединения. В частности, это решение, если вы используете SQLiteAsyncConnection
, у которого нет метода Close()
.
У меня была аналогичная проблема, я пробовал решение с GC.Collect
, но, как уже отмечалось, это может занять много времени, прежде чем файл не будет заблокирован.
Я нашел альтернативное решение, которое включает в себя удаление базового SQLiteCommand
в TableAdapters, см. этот ответ для получения дополнительной информации.
Используйте GC.WaitForPendingFinalizers()
Пример:
Con.Close();
GC.Collect();`
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Попробуйте это... этот пытается все вышеперечисленные коды... работал у меня
Reader.Close()
connection.Close()
GC.Collect()
GC.WaitForPendingFinalizers()
command.Dispose()
SQLite.SQLiteConnection.ClearAllPools()
Надеюсь, что поможет
У меня такая же проблема с EF и System.Data.Sqlite
.
Для меня я обнаружил, что SQLiteConnection.ClearAllPools()
и GC.Collect()
уменьшат частоту блокировки файла, но это все равно случается (примерно 1% времени).
Я изучал, и кажется, что некоторые SQLiteCommand
, которые создаются 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
, когда они укажут на закрытое соединение.
Была аналогичная проблема. Вызов сборщика мусора не помог мне. LAter Я нашел способ решить проблему
Автор также написал, что он сделал 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
Возможно, вам вообще не нужно иметь дело с GC. Пожалуйста, проверьте, завершено ли все sqlite3_prepare
.
Для каждого sqlite3_prepare
вам нужен корреспондент sqlite3_finalize
.
Если вы не закончили правильно, sqlite3_close
не закроет соединение.
Я боролся с подобной проблемой. Позор мне... Наконец я понял, что Reader не был закрыт. По какой-то причине я думал, что Reader будет закрыт, когда соответствующее соединение будет закрыто. Очевидно, GC.Collect() не работает для меня.
Обертка читателя с помощью "using: statement" также является хорошей идеей. Вот быстрый тестовый код.
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();
}
}
}
Я считаю, что вызов SQLite.SQLiteConnection.ClearAllPools()
является самым чистым решением. Насколько мне известно, не правильно вручную вызывать GC.Collect()
в среде WPF. Хотя, я не заметил эту проблему, пока не обновился до System.Data.SQLite
1.0.99.0 в 3/2016
Это работает для меня, но я заметил, что иногда файлы журнала -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();
}
Если вы не обрабатываете исключения проблемных команд, чем Garbage Collector, они ничего не могут с ними поделать, потому что есть некоторые необработанные исключения об этих командах, поэтому они не являются мусором. Этот метод обработки работал хорошо для меня с ожиданием сборщика мусора.
Я использовал SQLite 1.0.101.0 с EF6 и имел проблемы с заблокированным файлом после всех подключений и объектов.
Это ухудшилось с обновлениями из EF, которые заблокировали базу данных после их завершения. GC.Collect() был единственным обходным решением, которое помогло, и я начал отчаиваться.
В отчаянии я попробовал Oliver Wickenden ClearSQLiteCommandConnectionHelper (см. его ответ от 8 июля). Фантастика. Все проблемы с блокировкой исчезли! Спасибо Оливеру.
SQLiteAsyncConnection.ResetPool()
, подробности смотрите в этой проблеме .