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


95

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

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

І функція Delete видає виняток, що файл все ще використовується. Я повторно спробував Delete () в налагоджувачі через кілька хвилин, тому це не проблема часу.

У мене є код транзакції, але він взагалі не запускається до виклику Close (). Тож я впевнений, що це не відкрита транзакція. Команди sql між відкриттям та закриттям - це лише вибір.

ProcMon показує мою програму та антивірус, які переглядають файл бази даних. Це не відображає мою програму, що випускає файл db після закриття ().

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(), докладнішу інформацію див. У цій проблемі .
Уве Кейм,

Відповіді:


110

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

У будь-якому випадку, сьогодні вдень я ще раз переглядав це все і думав, що спробую з’ясувати, чому це робиться раз і назавжди, тож ось що я знайшов досі.

Що відбувається під час дзвінка 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 #, але я шанувальник керованого коду ...
Бенджамін Паннелл

1
Я знаю, що це старе, але дякую, що врятував мені біль. Ця помилка також впливає на збірку SQLite Windows Mobile / Compact Framework.
StrayPointer

2
Чудова робота! Вирішив мою проблему негайно. За 11 років розробки C # у мене ніколи не було необхідності використовувати GC.Collect: Зараз це перший приклад, я змушений це робити.
Пілсатор,

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, якщо вони є.
Іван Б

13

У мене працювало:

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

Додаткова інформація : З'єднання об'єднуються SQLite для підвищення продуктивності. Це означає, що коли ви викликаєте метод Close на об'єкті підключення, підключення до бази даних може все ще бути активним (у фоновому режимі), щоб наступний метод Open став швидшим. Коли ви знаєте, що Ви більше не хочете нового підключення, виклик ClearAllPools закриває всі підключення, що існують у фоновому режимі, і дескриптори файлу (файлів?) до файлу db звільняються. Тоді файл db може бути видалений, видалений або використаний іншим процесом.


1
Не могли б ви додати пояснення, чому це є гарним рішенням проблеми.
Matas Vaitkevicius

Ви також можете використовувати 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

Ваша відповідь повинна бути правильною. Дуже дякую.
Ахмед Шамель

5

Спробуйте це ... цей спробує всі вищезазначені коди ... працював у мене

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

Сподіваюся, це допомагає


1
WaitForPendingFinalizers зробили для мене все різне
Тодд,

5

У мене були однакові проблеми з 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();на початку завантаження програми. Потім зберігатиметься список активних команд та встановлюватиметься їх З’єднання, Nullколи вони вказуватимуть на з'єднання, яке закрито.


Мені також довелося встановити для з'єднання значення null у частині DisposedCommand цього, або я іноді отримував ObjectDisposedExceptions.
Елліот

На мою думку, це недооцінена відповідь. Це вирішило мої проблеми з очищенням, які я не міг зробити сам через рівень EF. Дуже радий використати це над тим потворним хакерством GC. Дякую!
Джейсон Тайлер,

Якщо ви використовуєте це рішення в багатопотоковому середовищі, список OpenCommands має бути [ThreadStatic].
Беро

3

Використовуйте GC.WaitForPendingFinalizers()

Приклад:

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

3

Була подібна проблема. Виклик Сміттєзбірника мені не допоміг. Пізніше я знайшов спосіб вирішити проблему

Автор також писав, що робив запити 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.

Крім того, мені не потрібно переривати підключення до бази даних і викликати Garbage Collector. Все, що мені потрібно було зробити, це закрити зчитувач, який був створений під час виконання запиту 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 не закритий. Чомусь я думав, що 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();
}

Якщо ви не обробляєте винятки проблемних команд, то Garbage Collector не може з ними нічого зробити, оскільки є деякі необроблені винятки щодо цих команд, тому вони не є сміттям. Цей метод поводження для мене добре працював, чекаючи збирача сміття.


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.