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.