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


97

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

 myconnection.Close();    
 File.Delete(filename);

И Delete выдает исключение, что файл все еще используется. Я повторно попробовал Delete () в отладчике через несколько минут, так что это не проблема времени.

У меня есть код транзакции, но он вообще не запускается до вызова Close (). Так что я почти уверен, что это не открытая сделка. Команды sql между открытием и закрытием - это просто выбор.

ProcMon показывает мою программу и мой антивирус, просматривая файл базы данных. Это не показывает, что моя программа освобождает файл db после close ().

Visual Studio 2010, C #, 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);

Вы пробовали: myconnection.Close (); myconnection.Dispose (); ?
— UGEEN

1
При использовании sqlite-net вы можете использовать SQLiteAsyncConnection.ResetPool(), подробности см. В этой проблеме .
— Uwe Keim

Ответы:


113

Некоторое время назад столкнулся с той же проблемой при написании уровня абстракции БД для C #, и я никогда не удосужился выяснить, в чем проблема. Я только что выбросил исключение, когда вы попытались удалить базу данных SQLite с помощью моей библиотеки.

Во всяком случае, сегодня днем ​​я просматривал все это снова и решил, что попытаюсь выяснить, почему он делает это, раз и навсегда, так что вот что я нашел до сих пор.

Когда вы вызываете SQLiteConnection.Close(), то (наряду с рядом проверок и прочего) удаляется объект SQLiteConnectionHandle, указывающий на экземпляр базы данных SQLite. Это делается с помощью вызова SQLiteConnectionHandle.Dispose(), однако на самом деле это не освобождает указатель, пока сборщик мусора CLR не выполнит некоторую сборку мусора. Поскольку SQLiteConnectionHandleпереопределяет CriticalHandle.ReleaseHandle()функцию для вызова sqlite3_close_interop()(через другую функцию), это не закрывает базу данных.

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

TL; DR Решение состоит в том, чтобы принудительно выполнить сборку мусора после вашего вызова 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
Да! Спасибо! Похоже, сборщику мусора может потребоваться немного времени для выполнения своей работы.
— Tom Cerul

1
Вы также можете взглянуть на C # SQLite, я только что перешел на его использование. Конечно, если вы выполняете что-то критическое для производительности, то C, вероятно, быстрее, чем C #, но я поклонник управляемого кода ...
— Бенджамин Паннелл

1
Я знаю, что это старое, но спасибо, что избавили меня от боли. Эта ошибка также влияет на сборку Windows Mobile / Compact Framework SQLite.
— StrayPointer

2
Отличная работа! Решил мою проблему сразу. За 11 лет разработки C # у меня никогда не было необходимости использовать GC.Collect: это первый пример, который я вынужден сделать.
— Pilsator

10
GC.Collect (); работает, но System.Data.SQLite.SQLiteConnection.ClearAllPools (); решает проблему с помощью API библиотеки.
— Аарон Худон

57

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

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


5
Это не так уж и удивительно, GC.Collect()просто запускается асинхронная сборка мусора, поэтому, чтобы убедиться, что все было очищено, вам нужно явно дождаться этого.
— ChrisWue

2
Я испытал то же самое, пришлось добавить GC.WaitForPendingFinalizers (). Это было в 1.0.103
— Vort3x

18

В моем случае я создавал 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

6
Я очень рекомендую не глотать исключения вроде этого
— Том МакКерни

17

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

Обнаружение утилизации предметов SQLiteCommandи SQLiteDataReaderпредметов после использования избавило меня от использования сборщика мусора.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();

2
В яблочко. Убедитесь, что вы удалили ВСЕ, SQLiteCommandдаже если SQLiteCommandпозже вы повторно используете переменную.
— Бруно Бьери

Это сработало для меня. Я также позаботился о любых сделках.
— Джей-Николас Хаклеман

1
Большой! Вы сэкономили мне немного времени. Исправлена ​​ошибка, когда я добавлял command.Dispose();все, SQLiteCommandчто было выполнено.
— Иван Б.

Кроме того, убедитесь, что вы освобождаете (т.е. .Dispose()) другие объекты, такие как SQLiteTransaction, если они у вас есть.
— Иван Б.

14

Для меня сработало следующее:

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

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


1
Не могли бы вы объяснить, почему это хорошее решение проблемы.
— Матас Вайткявичюс

Вы также можете использовать SQLiteConnectionPool.Shared.Reset(). Это закроет все открытые соединения. В частности, это решение, если вы используете SQLiteAsyncConnectionне имеющий Close()метода.
— Лоренцо Полидори,

9

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

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


ты был прав! В некоторых случаях у меня работал простой GC.Collect, в других мне приходилось удалять любые SqliteCommands, связанные с соединением, перед вызовом GC.Collect, иначе это не сработает!
— Eitan HS

1
Вызов Dispose в SQLiteCommand работал у меня. В качестве небольшого комментария - если вы вызываете GC.Collect, вы делаете что-то не так.
— Натали Адамс

@NathanAdams при работе с EntityFramework нет ни одного объекта команды, который вы когда-либо могли бы удалить. Так что либо сама EntityFramework, либо оболочка SQLite для EF тоже что-то не так.
— springy76

Ваш ответ должен быть правильным. Большое спасибо.
— Ахмед

6

У меня была такая же проблема с EF и System.Data.Sqlite.

Что касается меня, я обнаружил SQLiteConnection.ClearAllPools()и GC.Collect()уменьшил частоту блокирования файлов, но это все равно время от времени происходило (примерно в 1% случаев).

Я исследовал, и кажется, что некоторые, SQLiteCommandкоторые создает EF, не удаляются, и для их свойства Connection все еще установлено закрытое соединение. Я попытался избавиться от них, но Entity Framework тогда выдает исключение во время следующего DbContextчтения - кажется, EF иногда все еще использует их после закрытия соединения.

Мое решение заключалось в том, чтобы установить для свойства Connection значение, Nullкогда соединение закрывается на этих SQLiteCommands. Кажется, этого достаточно, чтобы снять блокировку файла. Я тестировал приведенный ниже код и не видел проблем с блокировкой файлов после нескольких тысяч тестов:

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();в начале загрузки приложения. После этого будет сохранен список активных команд, и для них будет установлено значение Connection, Nullкогда они будут указывать на закрытое соединение.


Мне также пришлось установить соединение на null в части DisposingCommand, иначе я иногда получал ObjectDisposedExceptions.
— Эллиот

На мой взгляд, это недооцененный ответ. Это решило мои проблемы с очисткой, которые я не мог сделать сам из-за слоя EF. Очень рад использовать это вместо уродливого взлома сборщика мусора. Спасибо!
— Джейсон Тайлер,

Если вы используете это решение в многопоточной среде, список OpenCommands должен быть [ThreadStatic].
— Беро

5

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

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

надеюсь, это поможет


1
WaitForPendingFinalizers все изменили для меня
— Тодд

4

Использовать GC.WaitForPendingFinalizers()

Пример:

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

3

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


2

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


2

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

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

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


1

Я боролся с аналогичной проблемой. Позор мне ... Я наконец понял, что Reader не закрывается. Я почему-то подумал, что Ридер закроется при закрытии соответствующего соединения. Очевидно, GC.Collect () у меня не работал.
Обернуть Reader с помощью оператора using: также является хорошей идеей. Вот код для быстрой проверки.

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();
        }
    }   
}

0

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

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

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


Я думаю, это должен быть комментарий, а не ответ
— Кевин Уоллис

1
Кевин, я согласен, но мне не разрешили комментировать, потому что мне нужно 50 репутации (очевидно).
— Тони Салливан

0

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

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

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


0

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


0

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

dbConnection.Close();
System.Data.SQLite.SQLiteConnection.ClearAllPools();

GC.Collect();
GC.WaitForPendingFinalizers();

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.