System.Data.SQLite Close () не выпускает файл базы данных

61

У меня возникла проблема с закрытием моей базы данных перед попыткой удалить файл. Код просто

 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);
  • 0
    Вы пробовали: myconnection.Close (); myconnection.Dispose (); ?
  • 1
    При использовании sqlite-net вы можете использовать SQLiteAsyncConnection.ResetPool() , подробности смотрите в этой проблеме .
Теги:
system.data.sqlite

16 ответов

70
Лучший ответ

Некоторое время назад я сталкивался с одной и той же проблемой при написании слоя абстракции 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);

Удачи вам в этом, и я надеюсь, что это поможет

  • 1
    Да! Спасибо! Похоже, что GC может понадобиться немного, чтобы закончить свою работу.
  • 1
    Возможно, вы захотите взглянуть на C # SQLite, я только что перевел весь свой код на его использование. Конечно, если вы используете что-то критически важное для производительности, то C, вероятно, быстрее, чем C #, но я фанат управляемого кода ...
Показать ещё 5 комментариев
47

Просто GC.Collect() не работал у меня.

Мне пришлось добавить GC.WaitForPendingFinalizers() после GC.Collect(), чтобы продолжить удаление файла.

  • 5
    В этом нет ничего удивительного, GC.Collect() просто запускает сборку мусора, которая является асинхронной, поэтому, чтобы убедиться, что все вычищено, нужно явно ее дождаться.
  • 2
    Я испытал то же самое, пришлось добавить GC.WaitForPendingFinalizers (). Это было в 1.0.103
Показать ещё 1 комментарий
14

В моем случае я создавал объекты 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
  • 5
    Я очень рекомендую против глотания исключений, подобных этому
  • 0
    Согласен. Удалены.
9

Имел подобную проблему, хотя решение сборщика мусора не исправило его.

Найденное размещение объектов SQLiteCommand и SQLiteDataReader после использования спасло меня, используя сборщик мусора.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
  • 1
    Именно так. Убедитесь, что вы утилизируете КАЖДЫЙ SQLiteCommand даже если позже вы SQLiteCommand переменную SQLiteCommand .
  • 0
    Это сработало для меня. Я также удостоверился, чтобы распоряжаться любыми транзакциями.
9

Следующие работали для меня:

MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()

Дополнительная информация: Соединения объединяются SQLite для повышения производительности. Это означает, что когда вы вызываете метод Close для объекта соединения, соединение с базой данных может оставаться в живых (в фоновом режиме), чтобы следующий метод Open стал быстрее. Когда вы знаете, что вы не знаете, t требуется новое соединение, вызов ClearAllPools закрывает все соединения, которые находятся в фоновом режиме, и дескриптор файла (s?) в файл db освобождается. Затем файл db может быть удален, удален или использован другим процессом.

  • 1
    Не могли бы вы добавить объяснение, почему это хорошее решение проблемы.
  • 0
    Вы также можете использовать SQLiteConnectionPool.Shared.Reset() . Это закроет все открытые соединения. В частности, это решение, если вы используете SQLiteAsyncConnection , у которого нет метода Close() .
8

У меня была аналогичная проблема, я пробовал решение с GC.Collect, но, как уже отмечалось, это может занять много времени, прежде чем файл не будет заблокирован.

Я нашел альтернативное решение, которое включает в себя удаление базового SQLiteCommand в TableAdapters, см. этот ответ для получения дополнительной информации.

  • 0
    ты был прав! В некоторых случаях у меня работал простой «GC.Collect», в других мне приходилось располагать любые SqliteCommands, связанные с соединением, перед вызовом GC.Collect, иначе это не будет работать!
  • 1
    Вызов Dispose для SQLiteCommand работал для меня. В качестве дополнительного комментария - если вы звоните в GC.Collect, вы делаете что-то не так.
Показать ещё 2 комментария
3

Используйте GC.WaitForPendingFinalizers()

Пример:

Con.Close();  
GC.Collect();`
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
2

Попробуйте это... этот пытается все вышеперечисленные коды... работал у меня

    Reader.Close()
    connection.Close()
    GC.Collect()
    GC.WaitForPendingFinalizers()
    command.Dispose()
    SQLite.SQLiteConnection.ClearAllPools()

Надеюсь, что поможет

  • 1
    WaitForPendingFinalizers сделали всю разницу для меня
2

У меня такая же проблема с 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, когда они укажут на закрытое соединение.

  • 0
    Я должен был также установить нулевое соединение в части DisposingCommand этого, или я бы иногда получал ObjectDisposedExceptions.
1

Была аналогичная проблема. Вызов сборщика мусора не помог мне. 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

1

Возможно, вам вообще не нужно иметь дело с GC. Пожалуйста, проверьте, завершено ли все sqlite3_prepare.

Для каждого sqlite3_prepare вам нужен корреспондент sqlite3_finalize.

Если вы не закончили правильно, sqlite3_close не закроет соединение.

1

Я боролся с подобной проблемой. Позор мне... Наконец я понял, что 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();
        }
    }   
}
1

Я считаю, что вызов SQLite.SQLiteConnection.ClearAllPools() является самым чистым решением. Насколько мне известно, не правильно вручную вызывать GC.Collect() в среде WPF. Хотя, я не заметил эту проблему, пока не обновился до System.Data.SQLite 1.0.99.0 в 3/2016

0

Это работает для меня, но я заметил, что иногда файлы журнала -wal -shm не удаляются при закрытии процесса. Если вы хотите, чтобы SQLite удалял файлы -wal -shm, когда все соединения закрыты, последнее закрытое соединение ДОЛЖНО быть неточным. Надеюсь, это поможет кому-то.

0

Ожидание сборщика мусора не может выпускать базу данных все время, и это случилось со мной. Когда в базе данных SQLite возникает некоторый тип исключения, например попытка вставить строку с существующим значением для PrimaryKey, она будет хранить файл базы данных до тех пор, пока вы ее не удалите. Следующий код улавливает исключение SQLite и отменяет проблемную команду.

SQLiteCommand insertCommand = connection.CreateCommand();
try {
    // some insert parameters
    insertCommand.ExecuteNonQuery();
} catch (SQLiteException exception) {
    insertCommand.Cancel();
    insertCommand.Dispose();
}

Если вы не обрабатываете исключения проблемных команд, чем Garbage Collector, они ничего не могут с ними поделать, потому что есть некоторые необработанные исключения об этих командах, поэтому они не являются мусором. Этот метод обработки работал хорошо для меня с ожиданием сборщика мусора.

0

Я использовал SQLite 1.0.101.0 с EF6 и имел проблемы с заблокированным файлом после всех подключений и объектов.

Это ухудшилось с обновлениями из EF, которые заблокировали базу данных после их завершения. GC.Collect() был единственным обходным решением, которое помогло, и я начал отчаиваться.

В отчаянии я попробовал Oliver Wickenden ClearSQLiteCommandConnectionHelper (см. его ответ от 8 июля). Фантастика. Все проблемы с блокировкой исчезли! Спасибо Оливеру.

  • 0
    Я думаю, что это должен быть комментарий, а не ответ
  • 1
    Кевин, я согласен, но мне не разрешили комментировать, потому что мне нужно 50 репутации (очевидно).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню