SQLite сохраняет базу данных заблокированной даже после закрытия соединения
Я использую провайдера System.Data.SQLite в приложении ASP.NET (фреймворк 4.0). Проблема, с которой я сталкиваюсь, заключается в том, что, когда я вставляю что-то в таблицу в базе данных SQLite, база данных блокируется, и блокировка не снимается даже после удаления соединения.
При попытке доступа к файлу возникает ошибка: "Процесс не может получить доступ к файлу catalog.sqlite, потому что он используется другим процессом".
Мой код довольно прост, я открываю соединение, читаю некоторые данные из базы данных SQLServer, вставляю эти данные в SQLite (через SQLiteDataAdapter), а затем закрываю соединение и распоряжаюсь всем, чтобы быть на безопасной стороне. Но, тем не менее, я получаю эту ошибку при попытке заархивировать файл после того, как он заполнен данными.
Я прочитал всевозможные предложения в Stackru, но ни одно из них не помогло решить проблему (отключение антивируса, изменение модели транзакции, ожидание за несколько секунд до архивации файла, завершение всех вызовов вставки в транзакцию, и т.д.. но никто не помог решить эту проблему.
Может быть, есть что-то специфическое для ASP.NET (проблема заключается в многопоточности? Даже при том, что я тестирую это на компьютере разработчика, где есть только один вызов этой функции и нет параллелизма?)
Как примечание, я попытался избежать DataTable и SQLiteDataAdapter и использовать только SQLiteCommand напрямую, и таким образом он работает очаровательно. Конечно, я могу продолжать строить свои запросы в виде строк вместо использования адаптеров данных, но я нахожу это немного неловким, когда для этого создана платформа.
18 ответов
У меня была та же проблема с использованием наборов данных / таблиц-адаптеров, созданных вместе с дизайнером, поставляемым с System.Data.Sqlite.dll
версия 1.0.82.0 - после закрытия соединения мы не смогли прочитать файл базы данных, используя System.IO.FileStream
, Я правильно распределял подключения и таблицы адаптеров и не использовал пул подключений.
В соответствии с моими первыми поисками (например, этим и этим потоком), которые казались проблемой в самой библиотеке - либо объекты неправильно освобождены, и / или проблемы с пулами (которые я не использую).
Прочитав ваш вопрос, я попытался воспроизвести проблему, используя только объекты SQLiteCommand, и обнаружил, что проблема возникает, когда вы не избавляетесь от них. Обновление 2012-11-27 19:37 UTC: это еще раз подтверждается этим билетом для System.Data.SQLite, в котором разработчик объясняет, что " все объекты SQLiteCommand и SQLiteDataReader, связанные с соединением [должны быть] должным образом удалены".
Затем я снова включил сгенерированные адаптеры таблиц и увидел, что реализации Dispose
метод - так фактически созданные команды не были расположены. Я реализовал это, позаботившись об утилизации всех команд, и у меня нет проблем.
Вот код на C#, надеюсь, это поможет. Обратите внимание, что код преобразуется из оригинала в 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 Gorogm подчеркивает две важные вещи:
согласно журналу изменений на официальном сайте System.Data.SQLite, начиная с версии 1.0.84.0 приведенный выше код не требуется, поскольку об этом позаботится библиотека. Я не проверял это, но в худшем случае вам нужен только этот фрагмент:
//In Table Adapter protected override void Dispose(bool disposing) { base.Dispose(disposing); this.Adapter.Dispose(); }
о реализации
Dispose
зовTableAdapter
: лучше поместить это в частичный класс, чтобы регенерация набора данных не повлияла на этот код (и любой дополнительный код, который вам может понадобиться добавить).
У меня та же проблема. Мой сценарий был после получения данных внутри файла базы данных SQLite, я хочу удалить этот файл, но он всегда выдает ошибку "... используя другой процесс". Даже я располагаю SqliteConnection или SqliteCommand, ошибка все равно происходит. Я исправил ошибку, позвонив GC.Collect()
,
Фрагмент кода
public void DisposeSQLite()
{
SQLiteConnection.Dispose();
SQLiteCommand.Dispose();
GC.Collect();
}
Надеюсь, это поможет.
Следующее работало для меня:
MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()
В моем случае я создавал 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);
Как говорилось ранее, объекты SQLite должны быть уничтожены. Однако происходит странное поведение: соединение должно быть открыто во время вызова Dispose по командам. Например:
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();
}
}
дает такую же блокировку файла
В большинстве случаев проблема возникнет, если вы не распорядитесь своими читателями и командами должным образом. Существует сценарий, в котором команды и читатели не будут располагаться должным образом.
Сценарий 1: Если вы используете булеву функцию. перед достижением результата код в блоке finally не будет выполнен. Это большая проблема, если вы собираетесь оценивать результаты функции isDataExists при выполнении кода, если он соответствует результату, т.е.
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();
}
Старый вопрос, но вот пример с EntityFrameworkCore в .NET 7. Решение действительно состояло в том, чтобы очистить пул соединений, используяSqliteConnection.ClearPool(...)
.
У меня есть веб-служба, в которой пользователь загружает файл базы данных SQLite, загружает его, читает таблицу, а затем немедленно удаляет загруженный файл базы данных.
var dbFilePath = "Database.db";
var connectionString = $"Data Source={dbFilePath};";
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlite(connectionString);
await using var context = new MyDbContext(optionsBuilder.Options);
var retrievedData = await context.MyTable.ToListAsync();
SqliteConnection.ClearPool((SqliteConnection) context.Database.GetDbConnection());
await context.DisposeAsync();
// File.Delete(dbFilePath); // <- works immediately, file is not locked
Это простой пример входа и выхода. Он загружает базу данных SQLite, запрашивает таблицу, а затем очищает (конкретное) соединение из пула соединений. Вопреки некоторым другим ответам, которые вызываютClearAllPools
что может вызвать проблемы в многопоточных ситуациях.
У меня была та же проблема, и она была исправлена только путем утилизации DbCommand
в using
заявление, но с Pooling = true
моя проблема была исправлена !!
SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder
{
Pooling = true
};
Откройте диспетчер задач Windows и закройте экземпляр SQl и проект, чтобы вручную закрыть соединение.
В коде добавитьSystem.Data.SQLite.SQLiteConnection.ClearAllPools();
чтобы закрыть соединения после того, как вы выполнили все свои операции.
Теперь должно работать без сбоев
Я нашел правильный ответ edymtt об обвинении TableAdapters / Datasets, но вместо того, чтобы изменять каждый раз заново генерируемый кодовый файл TableAdapter, я нашел другое решение: вручную вызывать.Dispose для дочерних элементов 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());
}
должен освободить все (SQLiteConnection, SQLiteDataAdapter, SQLiteCommand,...), это то, что у меня работает:
SQLiteConnection con = null;
SQLiteDataAdapter adp = null;
SQLiteCommand s = null;
try
{
....
con.Close();
if (s != null)
{
try
{
s.Dispose();
s = null;
}
catch { }
}
if (adp != null)
{
try
{
adp.Dispose();
adp = null;
}
catch { }
}
ReleaseSQLiteDatabaseConnection.Release(con);
try
{
con.Dispose();
}
catch { }
con = null;
}
catch (Exception ex)
{
if (con != null)
{
con.Close();
if (s != null)
{
try
{
s.Dispose();
s = null;
}
catch { }
}
if (adp != null)
{
try
{
adp.Dispose();
adp = null;
}
catch { }
}
ReleaseSQLiteDatabaseConnection.Release(con);
try
{
con.Dispose();
}
catch { }
con = null;
}
throw ex;
}
.....
public class ReleaseSQLiteDatabaseConnection
{
public static void Release(SQLiteConnection con)
{
try
{
SQLiteConnection.ClearPool(con);
GC.Collect();
GC.WaitForPendingFinalizers();
}
catch
{
}
}
}
Это был один из лучших результатов Google, которые я нашел, когда столкнулся с этой ошибкой. Тем не менее, ни один из ответов не помог мне, поэтому после более продолжительного поиска и поиска в Google я придумал этот код, который работает из некоторого кода с http://www.tsjensen.com/blog/post/2012/11/10/SQLite-on-Visual-Studio-with-NuGet-and-Easy-Instructions.aspx
Однако мне не пришлось использовать NuGet вообще. Моя программа загружает файл БД с сервера каждый раз, когда он открывается. Затем, если пользователь обновит эту базу данных, он будет загружен для всех, чтобы в следующий раз открыть ту же программу. Я получал сообщение об ошибке, что файл использовался после обновления локального файла и попытки загрузить его на наш 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
Была такая же проблема. Решил просто установкой SQLite 1.0.111 (через nuget).
Мне не пришлось ничего менять в моем коде, только обновить до этой версии. Раньше я использовал 1.0.85, где файл был заблокирован, хотя соединение было закрыто и удалено.
Ни одно из других решений не помогло мне. В моем случае я создаю новую меньшую БД из большей БД. Сначала я прикреплял большую БД. Я переключил его так, чтобы была подключена меньшая БД. Я запускаю команду DETACH в меньшей БД перед закрытием соединения. Меньшая БД больше не заблокирована, поэтому пользователи могут ее загрузить. Я по-прежнему мог без проблем использовать большую БД с одновременными подключениями.
В дополнение к ответу Рене. Мое приложение .NET 7 - без использования EF - использовало nuget Microsoft.Data.Sqlite 5.x, и после обновления до 7.x база данных блокировалась при первом вызове базы данных и оставалась такой до тех пор, пока приложение не было закрыто хотя я правильно закрывал и утилизировал.
Похоже, что объединение в пул было добавлено для повышения производительности. https://github.com/dotnet/efcore/issues/13837
Итак, если в вашем коде есть место, которое вам нужно разблокировать базу данных, вы можете сделать это:
using SqliteConnection sqliteConnection = new($"data source = {MyApplication.PathToDatabase}");
SqliteConnection.ClearPool(sqliteConnection);
Вот и все. Вам не нужно делать это в своей библиотеке данных или при каждом вызове. Просто там, где вам это нужно.
Я провел хорошее тестирование и обнаружил, что проблема заключается в объединении пулов. Я знаю, что они сказали выше:
System.Data.SQLite.SQLiteConnection.ClearAllPools();
Но у меня это не сработало, поскольку я использую Microsoft.Data.Sqlite. Вместо этого мне пришлось установить для пула значение false непосредственно в строке подключения или в построителе строки подключения. Так:
string connectionString = new SqliteConnectionStringBuilder
{
DataSource = fileName,
Mode = SqliteOpenMode.ReadWriteCreate,
Pooling = false,
}.ToString();
Или вот так, если вы хотите сделать это прямо в строке подключения:
using (var mDbConnection = new SqliteConnection("DataSource=DatabaseFileNameHere,Pooling=false"))
Итак, все вместе это:
public void CreateSqliteDbFile(string fileName)
{
string connectionString = new SqliteConnectionStringBuilder
{
DataSource = fileName,
Mode = SqliteOpenMode.ReadWriteCreate,
Pooling = false,
}.ToString();
using (var mDbConnection = new SqliteConnection(connectionString))
{
mDbConnection.Open();
foreach (var sqlCommand in _createTableSqlCommands)
{
using (var command = new SqliteCommand(sqlCommand, mDbConnection))
{
command.ExecuteNonQuery();
}
}
mDbConnection.Close();
}
}
У меня были только проблемы, упомянутые здесь, когда блокировка компьютера, даже после разблокировки, работала нормально, иначе повезло, потому что я только недавно начал блокировать его, и программное обеспечение было выпущено пару дней назад, прежде чем кто-то узнал об этом.
В любом случае у меня были все вещи, такие как закрытие соединений, ClearAllPools и т. Д., Но мне не хватало aTableAdapter.Adapter.Dispose(), и это исправило это.
Обеспечение правильной утилизации любого IDisposable (например, SQLiteConnection, SQLiteCommand и т. Д.) Решает эту проблему. Я должен повторить, что нужно использовать "использование" как привычку, чтобы обеспечить правильное распоряжение одноразовыми ресурсами.