SQLite блокирует базу данных даже после закрытия соединения
Я использую поставщика System.Data.SQLite в приложении ASP.NET(фреймворк 4.0).
Проблема, с которой я сталкиваюсь, заключается в том, что когда я вставляю что-то в таблицу базы данных SQLite, база данных блокируется, и блокировка не освобождается даже после того, как соединение установлено.
При попытке доступа к файлу ошибка: "Процесс не может получить доступ к файлу" catalog.sqlite ", потому что он используется другим процессом".
Мой код довольно прост, я открываю соединение, читаю некоторые данные из базы данных SQLServer, вставляю эти данные в SQLite (через SQLiteDataAdapter), а затем закрываю соединение и удаляю все, чтобы быть в безопасности. Но все же я получаю эту ошибку, когда пытаюсь закрепить файл после заполнения данными.
Я прочитал все предложения здесь, в StackOverflow, но ни один из них не помог решить проблему (отключив антивирус, изменив модель транзакции, ожидая несколько секунд, прежде чем закрепить файл, обернув все вставки в транзакция и т.д., но никто не помог решить эту проблему.
Может быть, что-то особенное для ASP.NET(многопоточность является проблемой? Хотя я тестирую ее на машине разработки, где есть только один вызов этой функции и concurrency?)
В качестве побочного примечания я попытался избежать DataTable и SQLiteDataAdapter и использовать только SQLiteCommand напрямую, и таким образом это работает. Конечно, я могу продолжать строить свои запросы в виде строк вместо использования адаптеров данных, но мне кажется, что это немного неудобно, когда есть инфраструктура, созданная для этого.
Ответы
Ответ 1
У меня была та же проблема с использованием наборов данных/таблиц-адаптеров, созданных с помощью конструктора, поставляемого с System.Data.Sqlite.dll
версии 1.0.82.0 - после закрытия соединения мы не смогли прочитать файл базы данных, используя System.IO.FileStream
. Я правильно распределял подключения и табличные адаптеры и не использовал пул подключений.
Согласно моим первым поискам (например, этот и этот поток), которые казались проблемой в самой библиотеке - либо объекты не были правильно освобождены, и/или проблемы с пулами (которые я не не использовать).
Прочитав ваш вопрос, я попытался воспроизвести проблему, используя только объекты SQLiteCommand, и обнаружил, что проблема возникает, когда вы не избавляетесь от них. Обновление 2012-11-27 19:37 UTC: это подтверждается этим билетом для System.Data.SQLite, в котором разработчик объясняет, что "все объекты SQLiteCommand и SQLiteDataReader связаны с соединением [должно быть] правильно утилизировано ".
Затем я снова включил сгенерированные адаптеры таблиц и увидел, что реализации метода Dispose
не было, поэтому фактически созданные команды не были расположены. Я реализовал это, позаботившись об удалении всех команд, и у меня нет проблем.
Вот код на С#, надеюсь, это поможет. Обратите внимание, что код конвертируется из оригинала в Visual Basic, поэтому следует ожидать некоторых ошибок преобразования.
//In Table Adapter
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Common.DisposeTableAdapter(disposing, _adapter, _commandCollection);
}
public static class Common
{
/// <summary>
/// Disposes a TableAdapter generated by SQLite Designer
/// </summary>
/// <param name="disposing"></param>
/// <param name="adapter"></param>
/// <param name="commandCollection"></param>
/// <remarks>You must dispose all the command,
/// otherwise the file remains locked and cannot be accessed
/// (for example, for reading or deletion)</remarks>
public static void DisposeTableAdapter(
bool disposing,
System.Data.SQLite.SQLiteDataAdapter adapter,
IEnumerable<System.Data.SQLite.SQLiteCommand> commandCollection)
{
if (disposing) {
DisposeSQLiteTableAdapter(adapter);
foreach (object currentCommand_loopVariable in commandCollection)
{
currentCommand = currentCommand_loopVariable;
currentCommand.Dispose();
}
}
}
public static void DisposeSQLiteTableAdapter(
System.Data.SQLite.SQLiteDataAdapter adapter)
{
if (adapter != null) {
DisposeSQLiteTableAdapterCommands(adapter);
adapter.Dispose();
}
}
public static void DisposeSQLiteTableAdapterCommands(
System.Data.SQLite.SQLiteDataAdapter adapter)
{
foreach (object currentCommand_loopVariable in {
adapter.UpdateCommand,
adapter.InsertCommand,
adapter.DeleteCommand,
adapter.SelectCommand})
{
currentCommand = currentCommand_loopVariable;
if (currentCommand != null) {
currentCommand.Dispose();
}
}
}
}
Обновление 2013-07-05 17:36 UTC Вответе горогма выделены две важные вещи:
Согласно журналу изменений на официальном сайте System.Data.SQLite, начиная с версии 1.0.84.0, приведенный выше код не требуется, поскольку об этом заботится библиотека. Я не проверял это, но в худшем случае вам нужен только этот фрагмент:
//In Table Adapter
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this.Adapter.Dispose();
}
о реализации вызова Dispose
в TableAdapter
: лучше поместить это в частичный класс, чтобы регенерация набора данных не повлияла на этот код (и любой дополнительный код, который вам, возможно, понадобится добавить).
Ответ 2
У меня та же проблема. Мой сценарий состоял в том, что после получения данных в файле базы данных SQLite я хочу удалить этот файл, но он всегда выдает ошибку "... используя другой процесс". Даже я удаляю SqliteConnection или SqliteCommand, ошибка все еще происходит. Я исправил ошибку, вызвав GC.Collect()
.
Фрагмент кода
public void DisposeSQLite()
{
SQLiteConnection.Dispose();
SQLiteCommand.Dispose();
GC.Collect();
}
Надеюсь на эту помощь.
Ответ 3
В моем случае я создавал объекты 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)
{
// Added using
using (var command = connection.CreateCommand())
{
command.CommandText = commandText;
return command.ExecuteScalar();
}
}
}
Тогда вы можете использовать его так:
connection.ExecuteScalar(commandText);
Ответ 4
Следующие работали для меня:
MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()
Ответ 5
В большинстве случаев проблема возникает, если вы не распоряжаетесь своими читателями и командами должным образом. Существует сценарий, в котором команды и считыватели не будут правильно размещаться.
Сценарий 1: Если вы используете функцию boolean. до достижения результата код в блоке finally не будет отменен. Это большая проблема, если вы собираетесь оценивать результаты функции isDataExists при выполнении кода, если он соответствует результату i.e
if(isDataExists){
// execute some code
}
Оцениваемая функция
public bool isDataExists(string sql)
{
try
{
OpenConnection();
SQLiteCommand cmd = new SQLiteCommand(sql, connection);
reader = cmd.ExecuteReader();
if (reader != null && reader.Read())
{
return true;
}
else
{
return false;
}
}
catch (Exception expMsg)
{
//Exception
}
finally
{
if (reader != null)
{
reader.Dispose();
}
CloseConnection();
}
return true;
}
Решение: Уберите ваш читатель и команду внутри блока try следующим образом
OpenConnection();
SQLiteCommand cmd = new SQLiteCommand(sql, connection);
reader = cmd.ExecuteReader();
if (reader != null && reader.Read())
{
cmd.Dispose();
CloseConnection();
return true;
}
else
{
cmd.Dispose();
CloseConnection();
return false;
}
Наконец удалите читателя и команду на всякий случай, если что-то пошло не так.
finally
{
if (reader != null)
{
reader.Dispose();
}
CloseConnection();
}
Ответ 6
Я нашел, что edymtt правильно ответил на обвинение TableAdapters/Datasets, но вместо того, чтобы модифицировать каждый раз, заново созданный файл кода TableAdapter, я нашел другое решение: вручную вызвать. Установить на дочерние элементы TableAdapter. (В .NET 4.5, последний SQLite 1.0.86)
using (var db = new testDataSet())
{
using (testDataSetTableAdapters.UsersTableAdapter t = new testDataSetTableAdapters.UsersTableAdapter())
{
t.Fill(db.Users);
//One of the following two is enough
t.Connection.Dispose(); //THIS OR
t.Adapter.Dispose(); //THIS LINE MAKES THE DB FREE
}
Console.WriteLine((from x in db.Users select x.Username).Count());
}
Ответ 7
Как сказано ранее, объекты SQLite должны быть уничтожены. Однако есть странное поведение: соединение должно быть открыто во время вызова Dispose on commands. Например:
using(var connection = new SqliteConnection("source.db"))
{
connection.Open();
using(var command = connection.CreateCommand("select..."))
{
command.Execute...
}
}
работает нормально, но:
using(var connection = new SqliteConnection("source.db"))
{
connection.Open();
using(var command = connection.CreateCommand("select..."))
{
command.Execute...
connection.Close();
}
}
дает тот же файл блокировки
Ответ 8
Это был один из лучших результатов Google, который я нашел, когда я столкнулся с этой ошибкой. Тем не менее, ни один из ответов не помог мне так после дальнейшего поиска и поиска в Google. Я придумал этот код, который работает из некоторого кода из http://www.tsjensen.com/blog/post/2012/11/10/SQLite-on-Visual-Studio-with-NuGet-and-Easy-Instructions.aspx
Однако мне не пришлось вообще использовать NuGet. То, что делает моя программа, - это загрузка файла db с сервера при каждом его открытии. Затем, если пользователь обновит этот db, он будет загружен для всех, чтобы в следующий раз открыть одну и ту же программу. Я получаю сообщение об ошибке, которое файл использовал после обновления локального файла и пытается загрузить его в наш SharePoint. Теперь он отлично работает.
Public Function sqLiteGetDataTable(sql As String) As DataTable
Dim dt As New DataTable()
Using cnn = New SQLiteConnection(dbConnection)
cnn.Open()
Using cmd As SQLiteCommand = cnn.CreateCommand()
cmd.CommandText = sql
Using reader As System.Data.SQLite.SQLiteDataReader = cmd.ExecuteReader()
dt.Load(reader)
reader.Dispose()
End Using
cmd.Dispose()
End Using
If cnn.State <> System.Data.ConnectionState.Closed Then
cnn.Close()
End If
cnn.Dispose()
End Using
Return dt
End Function
Ответ 9
Обеспечение надлежащего удаления любых IDisposable (например, SQLiteConnection, SQLiteCommand и т.д.) решает эту проблему. Я должен повторить повторение того, что нужно использовать "использование" в качестве привычки для обеспечения надлежащего удаления одноразовых ресурсов.
Ответ 10
У меня была та же проблема, и она была исправлена только путем утилизации DbCommand
в инструкции using
, но с Pooling = true
моя проблема была исправлена!
SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder
{
Pooling = true
};
Ответ 11
У меня были только проблемы, упомянутые здесь, когда блокировка компьютера, даже после разблокировки, работала нормально, иначе повезло, потому что я только недавно начал блокировать его, и программное обеспечение было выпущено пару дней назад, прежде чем кто-то узнал об этом.
В любом случае у меня были все вещи, такие как закрытие соединений, ClearAllPools и т.д., Но мне не хватало aTableAdapter.Adapter.Dispose(), и это исправило это.
Ответ 12
Была такая же проблема.
Решил это просто установив SQLite 1.0.111 (через nuget).
Не нужно ничего менять в моем коде, только обновление до этой версии. Ранее я использовал 1.0.85, где файл был заблокирован, хотя соединение было закрыто и удалено.