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 и т. Д.) Решает эту проблему. Я должен повторить, что нужно использовать "использование" как привычку, чтобы обеспечить правильное распоряжение одноразовыми ресурсами.

Другие вопросы по тегам