System.Data.SQLite Close () gibt die Datenbankdatei nicht frei

93

Ich habe ein Problem beim Schließen meiner Datenbank, bevor versucht wird, die Datei zu löschen. Der Code ist gerecht

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

Und das Löschen löst eine Ausnahme aus, dass die Datei noch verwendet wird. Ich habe Delete () im Debugger nach einigen Minuten erneut ausprobiert, es handelt sich also nicht um ein Timing-Problem.

Ich habe einen Transaktionscode, der jedoch vor dem Aufruf von Close () überhaupt nicht ausgeführt wird. Ich bin mir also ziemlich sicher, dass es sich nicht um eine offene Transaktion handelt. Die SQL-Befehle zwischen Öffnen und Schließen sind nur Auswahlen.

ProcMon zeigt mein Programm und mein Antivirenprogramm in der Datenbankdatei. Es zeigt nicht, dass mein Programm die Datenbankdatei nach dem Schließen () freigibt.

Visual Studio 2010, C #, System.Data.SQLite Version 1.0.77.0, Win7

Ich habe einen zwei Jahre alten Fehler wie diesen gesehen, aber das Changelog sagt, dass er behoben ist.

Kann ich noch etwas überprüfen? Gibt es eine Möglichkeit, eine Liste offener Befehle oder Transaktionen abzurufen?


Neuer Arbeitscode:

 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);
Tom Cerul
quelle
Haben Sie versucht: myconnection.Close (); myconnection.Dispose (); ?
UGEEN
1
Bei der Verwendung von SQLite-net , können Sie verwenden SQLiteAsyncConnection.ResetPool(), finden Sie diese Ausgabe für Details.
Uwe Keim

Antworten:

109

Ich bin vor einiger Zeit beim Schreiben einer DB-Abstraktionsschicht für C # auf dasselbe Problem gestoßen, und ich bin nie dazu gekommen, herauszufinden, wo das Problem liegt. Ich habe gerade eine Ausnahme ausgelöst, als Sie versucht haben, eine SQLite-Datenbank mithilfe meiner Bibliothek zu löschen.

Wie auch immer, heute Nachmittag habe ich alles noch einmal durchgesehen und mir gedacht, ich würde versuchen herauszufinden, warum das ein für alle Mal so war. Also hier ist, was ich bisher gefunden habe.

Wenn Sie aufrufen, SQLiteConnection.Close()wird (zusammen mit einer Reihe von Überprüfungen und anderen Dingen) das SQLiteConnectionHandle, was auf die SQLite-Datenbankinstanz verweist, entsorgt. Dies erfolgt über einen Aufruf von SQLiteConnectionHandle.Dispose(), der Zeiger wird jedoch erst freigegeben, wenn der Garbage Collector der CLR eine Garbage Collection durchführt. Da SQLiteConnectionHandledie CriticalHandle.ReleaseHandle()aufzurufende Funktion sqlite3_close_interop()(über eine andere Funktion) überschrieben wird, wird die Datenbank nicht geschlossen.

Aus meiner Sicht ist dies eine sehr schlechte Vorgehensweise, da der Programmierer nicht sicher ist, wann die Datenbank geschlossen wird, aber so wurde es gemacht, also müssen wir wohl erst einmal damit leben oder uns verpflichten einige Änderungen an System.Data.SQLite. Freiwillige sind dazu herzlich eingeladen, leider habe ich vor dem nächsten Jahr keine Zeit mehr dazu.

TL; DR Die Lösung besteht darin, einen GC nach Ihrem Anruf bei SQLiteConnection.Close()und vor Ihrem Anruf bei zu erzwingen File.Delete().

Hier ist der Beispielcode:

string filename = "testFile.db";
SQLiteConnection connection = new SQLiteConnection("Data Source=" + filename + ";Version=3;");
connection.Close();
GC.Collect();
GC.WaitForPendingFinalizers();
File.Delete(filename);

Viel Glück damit und ich hoffe es hilft

Benjamin Pannell
quelle
1
Ja! Danke dir! Es sieht so aus, als würde der GC ein wenig brauchen, um seine Arbeit zu erledigen.
Tom Cerul
1
Vielleicht möchten Sie sich auch C # SQLite ansehen. Ich habe gerade meinen gesamten Code auf die Verwendung verschoben. Natürlich, wenn Sie etwas Leistungskritisches ausführen, ist C wahrscheinlich schneller als C #, aber ich bin ein Fan von verwaltetem Code ...
Benjamin Pannell
1
Ich weiß, dass das alt ist, aber danke, dass du mir Schmerzen erspart hast. Dieser Fehler betrifft auch den Windows Mobile / Compact Framework-Build von SQLite.
StrayPointer
2
Gute Arbeit! Mein Problem wurde sofort gelöst. In 11 Jahren C # -Entwicklung musste ich nie GC.Collect verwenden: Dies ist das erste Beispiel, zu dem ich gezwungen bin.
Pilsator
10
GC.Collect (); funktioniert, aber System.Data.SQLite.SQLiteConnection.ClearAllPools (); behandelt das Problem mithilfe der API der Bibliothek.
Aaron Hudon
57

Hat GC.Collect()bei mir einfach nicht funktioniert.

Ich musste hinzufügen GC.WaitForPendingFinalizers()nach , GC.Collect()um mit dem Löschen von Dateien , um fortzufahren.

Batiati
quelle
5
Dies ist nicht so überraschend. Startet GC.Collect()einfach eine Garbage Collection, die asynchron ist. Um sicherzustellen, dass alles bereinigt wurde, müssen Sie explizit darauf warten.
ChrisWue
2
Ich habe das gleiche erlebt, musste die GC.WaitForPendingFinalizers () hinzufügen. Dies war in 1.0.103
Vort3x
18

In meinem Fall habe ich SQLiteCommandObjekte erstellt, ohne sie explizit zu entsorgen.

var command = connection.CreateCommand();
command.CommandText = commandText;
value = command.ExecuteScalar();

Ich habe meinen Befehl in eine usingAnweisung eingeschlossen, die mein Problem behoben hat.

static public class SqliteExtensions
{
    public static object ExecuteScalar(this SQLiteConnection connection, string commandText)
    {
        using (var command = connection.CreateCommand())
        {
            command.CommandText = commandText;
            return command.ExecuteScalar();
        }
    }
}

Die usingAnweisung stellt sicher, dass Dispose auch dann aufgerufen wird, wenn eine Ausnahme auftritt.

Dann ist es auch viel einfacher, Befehle auszuführen.

value = connection.ExecuteScalar(commandText)
// Command object created and disposed
Nate
quelle
6
Ich empfehle sehr, solche Ausnahmen nicht zu schlucken
Tom McKearney
16

Hatte ein ähnliches Problem, obwohl die Garbage Collector-Lösung es nicht behoben hat.

Gefundene Entsorgung SQLiteCommandund SQLiteDataReaderGegenstände nach Gebrauch retteten mich überhaupt mit dem Müllsammler.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
themullet
quelle
2
Genau. Stellen Sie sicher, dass Sie JEDES entsorgen, SQLiteCommandauch wenn Sie SQLiteCommandspäter eine Variable recyceln .
Bruno Bieri
Das hat bei mir funktioniert. Ich habe auch darauf geachtet, alle Transaktionen zu entsorgen.
Jay-Nicolas Hackleman
1
Toll! Du hast mir ziemlich viel Zeit gespart. Es hat den Fehler behoben, als ich command.Dispose();zu jedem SQLiteCommand, der ausgeführt wurde, hinzugefügt habe .
Ivan B
.Dispose()Stellen Sie außerdem sicher, dass Sie andere Objekte wie SQLiteTransaction freigeben (z. B. ), falls vorhanden.
Ivan B
13

Folgendes hat bei mir funktioniert:

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

Weitere Informationen : Verbindungen werden von SQLite zusammengefasst, um die Leistung zu verbessern. Wenn Sie die Close-Methode für ein Verbindungsobjekt aufrufen, ist die Verbindung zur Datenbank möglicherweise noch aktiv (im Hintergrund), sodass die nächste Open-Methode schneller wird. Wenn Sie das wissen Wenn Sie keine neue Verbindung mehr möchten, werden durch Aufrufen von ClearAllPools alle im Hintergrund aktiven Verbindungen geschlossen und die Dateihandles zur Datenbankdatei freigegeben. Anschließend wird die Datenbankdatei möglicherweise entfernt, gelöscht oder von einem anderen Prozess verwendet.

Arvin
quelle
1
Könnten Sie bitte erklären, warum dies eine gute Lösung für das Problem ist?
Matas Vaitkevicius
Sie können auch verwenden SQLiteConnectionPool.Shared.Reset(). Dadurch werden alle offenen Verbindungen geschlossen. Dies ist insbesondere dann eine Lösung, wenn Sie SQLiteAsyncConnectionkeine Close()Methode verwenden.
Lorenzo Polidori
9

Ich hatte ein ähnliches Problem, ich habe die Lösung mit versucht, GC.Collectaber wie bereits erwähnt, kann es lange dauern, bis die Datei nicht gesperrt wird.

Ich habe eine alternative Lösung gefunden, bei der die zugrunde liegenden SQLiteCommands in den TableAdapters entsorgt werden. Weitere Informationen finden Sie in dieser Antwort .

edymtt
quelle
du hattest Recht! In einigen Fällen funktionierte einfaches 'GC.Collect' für mich, in anderen musste ich alle mit der Verbindung verbundenen SqliteCommands entsorgen, bevor ich GC.Collect aufrief, sonst funktioniert es nicht!
Eitan HS
1
Das Aufrufen von Dispose über den SQLiteCommand hat bei mir funktioniert. Nebenbei bemerkt: Wenn Sie GC.Collect anrufen, machen Sie etwas falsch.
Natalie Adams
@NathanAdams Wenn Sie mit EntityFramework arbeiten, gibt es kein einziges Befehlsobjekt, über das Sie jemals verfügen können. Entweder das EntityFramework selbst oder der SQLite for EF-Wrapper machen auch etwas falsch.
springy76
Ihre Antwort sollte die richtige sein. Vielen Dank.
Ahmed Shamel
5

Versuchen Sie dies ... dieser versucht alle oben genannten Codes ... hat für mich funktioniert

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

hoffentlich hilft das

Bishnu Dev
quelle
1
WaitForPendingFinalizers machten den Unterschied für mich
Todd
5

Ich habe das gleiche Problem mit EF und System.Data.Sqlite.

Für mich fand SQLiteConnection.ClearAllPools()und GC.Collect()reduzierte ich , wie oft das Sperren von Dateien passieren würde, aber es kam immer noch gelegentlich vor (in etwa 1% der Fälle).

Ich habe nachgeforscht und es scheint, dass einige von SQLiteCommandEF erstellte s nicht entsorgt sind und ihre Connection-Eigenschaft immer noch auf die geschlossene Verbindung festgelegt ist. Ich habe versucht, diese zu entsorgen, aber Entity Framework hat dann beim nächsten DbContextLesen eine Ausnahme ausgelöst - anscheinend verwendet EF sie manchmal noch, nachdem die Verbindung geschlossen wurde.

Meine Lösung bestand darin, sicherzustellen, Nulldass die Verbindungseigenschaft auf gesetzt ist, wenn die Verbindung auf diesen SQLiteCommands geschlossen wird. Dies scheint ausreichend zu sein, um die Dateisperre aufzuheben. Ich habe den folgenden Code getestet und nach einigen tausend Tests keine Probleme mit der Dateisperre festgestellt:

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

Zur Verwendung rufen Sie einfach ClearSQLiteCommandConnectionHelper.Initialise();zu Beginn des Anwendungsladens auf. Dadurch wird eine Liste der aktiven Befehle geführt und deren Verbindung festgelegt, Nullwenn sie auf eine geschlossene Verbindung verweisen.

Hallupa
quelle
Ich musste auch die Verbindung im DisposingCommand-Teil auf null setzen, sonst bekam ich gelegentlich ObjectDisposedExceptions.
Elliot
Dies ist meiner Meinung nach eine unterschätzte Antwort. Es löste meine Bereinigungsprobleme, die ich aufgrund der EF-Schicht nicht selbst lösen konnte. Sehr glücklich, dies über diesen hässlichen GC-Hack zu verwenden. Danke dir!
Jason Tyler
Wenn Sie diese Lösung in einer Multithread-Umgebung verwenden, sollte die OpenCommands-Liste [ThreadStatic] sein.
Bero
3

Verwenden GC.WaitForPendingFinalizers()

Beispiel:

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

Hatte ein ähnliches Problem. Garbage Collector anzurufen hat mir nicht geholfen. Später habe ich einen Weg gefunden, das Problem zu lösen

Der Autor schrieb auch, dass er SELECT-Abfragen an diese Datenbank durchgeführt hat, bevor er versucht hat, sie zu löschen. Ich habe die gleiche Situation.

Ich habe folgenden Code:

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.

Außerdem muss ich die Datenbankverbindung nicht schließen und Garbage Collector aufrufen. Ich musste lediglich den Reader schließen, der beim Ausführen der SELECT-Abfrage erstellt wurde

Schullz
quelle
2

Ich glaube, der Anruf bei SQLite.SQLiteConnection.ClearAllPools()ist die sauberste Lösung. Soweit ich weiß, ist es nicht richtig, GC.Collect()die WPF-Umgebung manuell aufzurufen . Das Problem ist mir jedoch erst System.Data.SQLiteaufgefallen , als ich 3/2016 auf 1.0.99.0 aktualisiert habe

Jona Varque
quelle
2

Vielleicht müssen Sie sich überhaupt nicht mit GC befassen. Bitte überprüfen Sie, ob alles sqlite3_prepareabgeschlossen ist.

Für jeden sqlite3_preparebenötigen Sie einen Korrespondenten sqlite3_finalize.

Wenn Sie nicht korrekt abschließen, sqlite3_closewird die Verbindung nicht geschlossen.

João Monteiro
quelle
1

Ich hatte mit dem ähnlichen Problem zu kämpfen. Schande über mich ... Endlich wurde mir klar, dass der Reader nicht geschlossen war. Aus irgendeinem Grund dachte ich, dass der Reader geschlossen wird, wenn die entsprechende Verbindung geschlossen wird. Offensichtlich hat GC.Collect () bei mir nicht funktioniert.
Es ist auch eine gute Idee, den Reader mit der Anweisung "using:" zu versehen. Hier ist ein kurzer Testcode.

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();
        }
    }   
}
Mike Znaet
quelle
0

Ich habe SQLite 1.0.101.0 mit EF6 verwendet und Probleme mit dem Sperren der Datei, nachdem alle Verbindungen und Entitäten entsorgt wurden.

Dies wurde noch schlimmer, da Aktualisierungen von der EF die Datenbank nach Abschluss gesperrt hielten. GC.Collect () war die einzige Problemumgehung, die half, und ich begann zu verzweifeln.

In meiner Verzweiflung habe ich Oliver Wickendens ClearSQLiteCommandConnectionHelper ausprobiert (siehe seine Antwort vom 8. Juli). Fantastisch. Alle Schließprobleme weg! Danke Oliver.

Tony Sullivan
quelle
Ich denke, dies sollte ein Kommentar statt einer Antwort sein
Kevin Wallis
1
Kevin, ich stimme zu, aber ich durfte keinen Kommentar abgeben, weil ich (anscheinend) 50 Ruf brauche.
Tony Sullivan
0

Das Warten auf Garbage Collector gibt die Datenbank möglicherweise nicht immer frei, und das ist mir passiert. Wenn in der SQLite-Datenbank eine Ausnahme auftritt, z. B. beim Versuch, eine Zeile mit dem vorhandenen Wert für PrimaryKey einzufügen, wird die Datenbankdatei gespeichert, bis Sie sie entsorgen. Der folgende Code fängt eine SQLite-Ausnahme ab und bricht den problematischen Befehl ab.

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

Wenn Sie die Ausnahmen problematischer Befehle nicht behandeln, kann Garbage Collector nichts dagegen tun, da es einige nicht behandelte Ausnahmen zu diesen Befehlen gibt, sodass sie kein Müll sind. Diese Handhabungsmethode hat bei mir gut funktioniert, wenn ich auf den Müllsammler gewartet habe.

Muhammed Kadir
quelle
0

Dies funktioniert bei mir, aber ich habe festgestellt, dass manchmal Journaldateien -wal -shm nicht gelöscht werden, wenn der Prozess geschlossen wird. Wenn SQLite -wal -shm-Dateien entfernen soll, wenn alle Verbindungen geschlossen sind, MUSS die zuletzt geschlossene Verbindung nicht schreibgeschützt sein. Hoffe das wird jemandem helfen.

ekalchev
quelle
0

Beste Antwort, die für mich funktioniert hat.

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

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

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Kazem Fallahi
quelle