System.Data.SQLite Close () ne libère pas le fichier de base de données

94

Je rencontre un problème pour fermer ma base de données avant de tenter de supprimer le fichier. Le code est juste

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

Et la suppression lève une exception selon laquelle le fichier est toujours en cours d'utilisation. J'ai réessayé le Delete () dans le débogueur après quelques minutes, donc ce n'est pas un problème de synchronisation.

J'ai un code de transaction mais il ne fonctionne pas du tout avant l'appel Close (). Je suis donc à peu près sûr que ce n'est pas une transaction ouverte. Les commandes sql entre ouvrir et fermer ne sont que des sélections.

ProcMon montre mon programme et mon antivirus en regardant le fichier de base de données. Il ne montre pas mon programme libérant le fichier db après la fermeture ().

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

J'ai vu un bogue vieux de deux ans comme celui-ci, mais le journal des modifications dit qu'il est corrigé.

Y a-t-il autre chose que je puisse vérifier? Existe-t-il un moyen d'obtenir une liste de toutes les commandes ou transactions ouvertes?


Nouveau code fonctionnel:

 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
la source
Avez-vous essayé: myconnection.Close (); myconnection.Dispose (); ?
UGEEN
1
Lorsque vous utilisez sqlite-net , vous pouvez utiliser SQLiteAsyncConnection.ResetPool(), voir ce problème pour plus de détails.
Uwe Keim

Réponses:

110

J'ai rencontré le même problème il y a quelque temps lors de l'écriture d'une couche d'abstraction de base de données pour C # et je n'ai jamais vraiment réussi à découvrir quel était le problème. J'ai juste fini par lancer une exception lorsque vous avez tenté de supprimer une base de données SQLite en utilisant ma bibliothèque.

Quoi qu'il en soit, cet après-midi, je regardais à nouveau tout cela et je me suis dit que j'essaierais de découvrir pourquoi il le faisait une fois pour toutes, alors voici ce que j'ai trouvé jusqu'à présent.

Ce qui se passe lorsque vous appelez, SQLiteConnection.Close()c'est que (avec un certain nombre de vérifications et d'autres choses) le SQLiteConnectionHandlequi pointe vers l'instance de base de données SQLite est supprimé. Cela se fait via un appel à SQLiteConnectionHandle.Dispose(), mais cela ne libère pas réellement le pointeur tant que le garbage collector du CLR n'a pas effectué un garbage collection. Comme SQLiteConnectionHandleremplace la CriticalHandle.ReleaseHandle()fonction à appeler sqlite3_close_interop()(via une autre fonction), cela ne ferme pas la base de données.

De mon point de vue, c'est une très mauvaise façon de faire les choses car le programmeur n'est pas vraiment certain de la fermeture de la base de données, mais c'est ainsi que cela a été fait, donc je suppose que nous devons vivre avec pour l'instant, ou nous engager quelques modifications apportées à System.Data.SQLite. Tous les volontaires sont invités à le faire, malheureusement je n'ai plus le temps de le faire avant l'année prochaine.

TL; DR La solution est de forcer un GC après votre appel à SQLiteConnection.Close()et avant votre appel à File.Delete().

Voici l exemple de code:

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

Bonne chance et j'espère que ça aide

Benjamin Pannell
la source
1
Oui! Je vous remercie! Il semble que le GC pourrait avoir besoin d'un peu de temps pour faire son travail.
Tom Cerul
1
Vous voudrez peut-être aussi regarder C # SQLite, je viens de déplacer tout mon code pour l'utiliser. Bien sûr, si vous exécutez quelque chose de critique en termes de performances, C est probablement plus rapide que C #, mais je suis un fan de code managé ...
Benjamin Pannell
1
Je sais que c'est vieux, mais merci de m'avoir épargné de la douleur. Ce bogue affecte également la version Windows Mobile / Compact Framework de SQLite.
StrayPointer
2
Bon travail! J'ai résolu mon problème immédiatement. En 11 ans de développement C #, je n'ai jamais eu besoin d'utiliser GC.Collect: C'est maintenant le premier exemple que je suis obligé de faire.
Pilsator
10
GC.Collect (); fonctionne, mais System.Data.SQLite.SQLiteConnection.ClearAllPools (); traite le problème à l'aide de l'API de la bibliothèque.
Aaron Hudon
57

Ça GC.Collect()n'a pas fonctionné pour moi.

J'ai dû ajouter GC.WaitForPendingFinalizers()après GC.Collect()pour procéder à la suppression du fichier.

Batiati
la source
5
Ce n'est pas si surprenant, GC.Collect()démarre simplement un garbage collection qui est asynchrone, donc pour vous assurer que tout a été nettoyé, vous devez l'attendre explicitement.
ChrisWue
2
J'ai vécu la même chose, j'ai dû ajouter le GC.WaitForPendingFinalizers (). C'était en 1.0.103
Vort3x
18

Dans mon cas, je créais des SQLiteCommandobjets sans les disposer explicitement.

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

J'ai enveloppé ma commande dans une usingdéclaration et cela a résolu mon problème.

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

L' usinginstruction garantit que Dispose est appelé même si une exception se produit.

Ensuite, il est également beaucoup plus facile d'exécuter des commandes.

value = connection.ExecuteScalar(commandText)
// Command object created and disposed
Nate
la source
6
Je recommande vivement de ne pas avaler des exceptions comme celle-ci
Tom McKearney
16

Eu un problème similaire, bien que la solution de ramasse-miettes ne l'ait pas résolu.

La mise au rebut SQLiteCommandet les SQLiteDataReaderobjets trouvés après utilisation m'ont permis de ne pas utiliser le ramasse-miettes.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
la balle
la source
2
Exactement. Assurez-vous de vous débarrasser de TOUS, SQLiteCommandmême si vous recyclez une SQLiteCommandvariable plus tard.
Bruno Bieri
Cela a fonctionné pour moi. Je me suis également assuré de disposer de toutes les transactions.
Jay-Nicolas Hackleman
1
Génial! Vous m'avez fait gagner un peu de temps. Cela a corrigé l'erreur lorsque j'ai ajouté command.Dispose();à tout ce SQLiteCommandqui était exécuté.
Ivan B du
Assurez-vous également de libérer (c'est-à-dire .Dispose()) d'autres objets comme SQLiteTransaction, si vous en avez.
Ivan B du
13

Ce qui suit a fonctionné pour moi:

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

Plus d'informations : Les connexions sont regroupées par SQLite afin d'améliorer les performances.Cela signifie que lorsque vous appelez la méthode Close sur un objet de connexion, la connexion à la base de données peut toujours être active (en arrière-plan) afin que la prochaine méthode Open devienne plus rapide. vous ne voulez plus une nouvelle connexion, l'appel de ClearAllPools ferme toutes les connexions qui sont actives en arrière-plan et le (s) descripteur (s?) de fichier vers le fichier db est libéré.Ensuite, le fichier db peut être supprimé, supprimé ou utilisé par un autre processus.

Arvin
la source
1
Pourriez-vous s'il vous plaît ajouter une explication pour expliquer pourquoi c'est une bonne solution au problème.
Matas Vaitkevicius
Vous pouvez également utiliser SQLiteConnectionPool.Shared.Reset(). Cela fermera toutes les connexions ouvertes. En particulier, c'est une solution si vous utilisez SQLiteAsyncConnectionqui n'a pas de Close()méthode.
Lorenzo Polidori
9

J'avais un problème similaire, j'ai essayé la solution avec GC.Collect mais, comme indiqué, cela peut prendre du temps avant que le fichier ne soit verrouillé.

J'ai trouvé une solution alternative qui implique la suppression des SQLiteCommands sous-jacents dans les TableAdapters, voir cette réponse pour plus d'informations.

Edymtt
la source
tu avais raison! Dans certains cas, un simple «GC.Collect» a fonctionné pour moi, dans d'autres, j'ai dû supprimer toutes les commandes SqliteCommands associées à la connexion avant d'appeler GC.Collect, sinon cela ne fonctionnera pas!
Eitan HS
1
L'appel de Dispose sur SQLiteCommand a fonctionné pour moi. En guise de commentaire, si vous appelez GC.Collect, vous faites quelque chose de mal.
Natalie Adams
@NathanAdams lorsque vous travaillez avec EntityFramework, vous ne pouvez jamais supprimer un seul objet de commande. Donc, l'EntityFramework lui-même ou le wrapper SQLite pour EF fait également quelque chose de mal.
springy76
Votre réponse doit être la bonne. Merci beaucoup.
Ahmed Shamel
5

Essayez ceci ... celui-ci essaie tous les codes ci-dessus ... a fonctionné pour moi

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

J'espère que cela pourra aider

Bishnu Dev
la source
1
WaitForPendingFinalizers a fait toute la différence pour moi
Todd
5

J'ai eu le même problème avec EF et System.Data.Sqlite.

Pour moi, j'ai trouvé SQLiteConnection.ClearAllPools()et GC.Collect()je réduirais la fréquence à laquelle le verrouillage de fichier se produirait, mais cela se produisait encore occasionnellement (environ 1% du temps).

J'ai étudié et il semble que certains SQLiteCommands créés par EF ne sont pas supprimés et ont toujours leur propriété Connection définie sur la connexion fermée. J'ai essayé de les éliminer, mais Entity Framework lèverait alors une exception lors de la prochaine DbContextlecture - il semble qu'EF les utilise parfois encore après la fermeture de la connexion.

Ma solution était de m'assurer que la propriété Connection est définie sur Nulllorsque la connexion se ferme sur ces SQLiteCommands. Cela semble être suffisant pour libérer le verrouillage du fichier. J'ai testé le code ci-dessous et je n'ai vu aucun problème de verrouillage de fichier après quelques milliers de tests:

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

Pour utiliser il suffit d'appeler ClearSQLiteCommandConnectionHelper.Initialise();au début du chargement de l'application. Cela conservera alors une liste des commandes actives et définira leur connexion sur Nulllorsqu'elles pointent vers une connexion qui est fermée.

Hallupa
la source
Je devais également définir la connexion sur null dans la partie DisposingCommand de ceci, ou j'obtiendrais occasionnellement ObjectDisposedExceptions.
Elliot
C'est une réponse sous-estimée à mon avis. Cela a résolu mes problèmes de nettoyage que je ne pouvais pas faire moi-même à cause de la couche EF. Très heureux d'utiliser cela sur ce vilain hack GC. Je vous remercie!
Jason Tyler le
Si vous utilisez cette solution dans un environnement multithread, la liste OpenCommands doit être [ThreadStatic].
Bero le
3

Utilisation GC.WaitForPendingFinalizers()

Exemple:

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

Eu un problème similaire. L'appel de Garbage Collector ne m'a pas aidé. Plus tard, j'ai trouvé un moyen de résoudre le problème

L'auteur a également écrit qu'il avait effectué des requêtes SELECT sur cette base de données avant d'essayer de la supprimer. J'ai la meme situation.

J'ai le code suivant:

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.

De plus, je n'ai pas besoin de fermer la connexion à la base de données et d'appeler Garbage Collector. Tout ce que j'avais à faire est de fermer le lecteur qui a été créé lors de l'exécution de la requête SELECT

Schullz
la source
2

Je crois que l'appel à SQLite.SQLiteConnection.ClearAllPools()est la solution la plus propre. Autant que je sache, il n'est pas approprié d'appeler manuellement GC.Collect()dans l'environnement WPF. Bien que, je n'ai pas remarqué le problème jusqu'à ce que je passe à System.Data.SQLite1.0.99.0 en 3/2016

Jona Varque
la source
2

Peut-être que vous n'avez pas du tout besoin de traiter avec GC. Veuillez vérifier si toutsqlite3_prepare est finalisé.

Pour chacun sqlite3_prepare, vous avez besoin d'un correspondant sqlite3_finalize.

Si vous ne finalisez pas correctement, sqlite3_closene fermera pas la connexion.

João Monteiro
la source
1

J'étais aux prises avec le même problème. Honte à moi ... J'ai finalement réalisé que Reader n'était pas fermé. Pour une raison quelconque, je pensais que le Reader sera fermé lorsque la connexion correspondante sera fermée. Évidemment, GC.Collect () n'a pas fonctionné pour moi.
Emballer le Reader avec l'instruction "using:" est également une bonne idée. Voici un code de test rapide.

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
la source
0

J'utilisais SQLite 1.0.101.0 avec EF6 et j'avais des problèmes avec le fichier verrouillé après la suppression de toutes les connexions et entités.

Cela a empiré avec les mises à jour de l'EF gardant la base de données verrouillée après leur exécution. GC.Collect () était la seule solution de contournement qui a aidé et je commençais à désespérer.

En désespoir de cause, j'ai essayé ClearSQLiteCommandConnectionHelper d'Oliver Wickenden (voir sa réponse du 8 juillet). Fantastique. Tous les problèmes de verrouillage sont partis! Merci Oliver.

Tony Sullivan
la source
Je pense que cela devrait être un commentaire au lieu d'une réponse
Kevin Wallis
1
Kevin, je suis d'accord, mais je n'ai pas été autorisé à commenter car j'ai besoin de 50 points de réputation (apparemment).
Tony Sullivan
0

Attendre Garbage Collector peut ne pas libérer la base de données à tout moment et cela m'est arrivé. Lorsqu'un certain type d'exception se produit dans la base de données SQLite, par exemple en essayant d'insérer une ligne avec une valeur existante pour PrimaryKey, le fichier de base de données sera conservé jusqu'à ce que vous le supprimiez. Le code suivant intercepte une exception SQLite et annule la commande problématique.

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

Si vous ne gérez pas les exceptions de commandes problématiques, Garbage Collector ne peut rien y faire car il y a des exceptions non gérées à propos de ces commandes et ne sont donc pas des déchets. Cette méthode de gestion a bien fonctionné pour moi avec l'attente du ramasse-miettes.

Muhammed Kadir
la source
0

Cela fonctionne pour moi mais j'ai remarqué que parfois les fichiers journaux -wal -shm ne sont pas supprimés lorsque le processus est fermé. Si vous voulez que SQLite supprime les fichiers -wal -shm lorsque toutes les connexions sont fermées, la dernière connexion fermée DOIT ÊTRE non en lecture seule. J'espère que cela aidera quelqu'un.

ekalchev
la source
0

Meilleure réponse qui a fonctionné pour moi.

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

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

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