120 votes

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

J'ai un problème pour fermer ma base de données avant d'essayer de supprimer le fichier. Le code est juste

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

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

J'ai le code de la transaction mais il ne s'exécute pas du tout avant l'appel à Close(). Je suis donc presque sûr qu'il ne s'agit pas d'une transaction ouverte. Les commandes sql entre open et close sont juste des selects.

ProcMon montre mon programme et mon antivirus en train d'examiner le fichier de la base de données. Il ne montre pas que mon programme libère le fichier db après le close().

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

J'ai vu un bug vieux de deux ans exactement comme celui-ci, mais le changelog dit qu'il est corrigé.

Y a-t-il autre chose à vérifier ? Existe-t-il un moyen d'obtenir une liste des commandes ou des transactions en cours ?


Nouveau, code de travail :

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

136voto

spartan563 Points 206

J'ai rencontré le même problème il y a quelque temps alors que j'écrivais une couche d'abstraction DB pour C# et je n'ai jamais réussi à trouver la cause du problème. J'ai fini par lever 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, j'ai regardé à nouveau tout cela et je me suis dit que j'allais essayer de trouver la raison pour laquelle il faisait cela une fois pour toutes, alors voici ce que j'ai trouvé jusqu'à présent.

Que se passe-t-il lorsque vous appelez SQLiteConnection.Close() est que (en plus d'un certain nombre de contrôles et d'autres choses) la SQLiteConnectionHandle qui pointe vers l'instance de la base de données SQLite est éliminée. Cela se fait par l'intermédiaire d'un appel à SQLiteConnectionHandle.Dispose() mais cela ne libère pas le pointeur tant que le collecteur de déchets du CLR n'a pas procédé au ramassage des ordures. Depuis que le SQLiteConnectionHandle remplace l'option CriticalHandle.ReleaseHandle() pour appeler la fonction sqlite3_close_interop() (par le biais d'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 puisque le programmeur n'est pas certain du moment où la base de données est fermée, mais c'est la façon dont cela a été fait et je suppose que nous devons vivre avec pour l'instant, ou apporter quelques modifications à System.Data.SQLite. Tout volontaire est le bienvenu pour le faire, malheureusement je n'ai plus le temps de le faire avant l'année prochaine.

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

Voici un 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 cela vous aidera.

56voto

Batiati Points 71

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

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

23voto

themullet Points 683

J'ai eu un problème similaire, mais la solution du ramasse-miettes n'a pas permis de le résoudre.

Trouvé en train de se débarrasser de SQLiteCommand y SQLiteDataReader après utilisation, ce qui m'a évité d'utiliser le ramasse-miettes.

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

22voto

Arvin Points 388

La méthode suivante 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 encore être vivante (en arrière-plan) de sorte que la méthode Open suivante devienne plus rapide.Lorsque vous savez que vous ne voulez plus de nouvelle connexion, l'appel à ClearAllPools ferme toutes les connexions qui sont vivantes en arrière-plan et le(s) handle(s) du fichier db sont libérés.Ensuite, le fichier db peut être supprimé, effacé ou utilisé par un autre processus.

20voto

Nate Points 870

Dans mon cas, je créais SQLiteCommand sans les éliminer explicitement.

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

J'ai enveloppé ma commande dans un using 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();
        }
    }
}

En using garantit que Dispose est appelé même si une exception se produit.

Il est alors beaucoup plus facile d'exécuter des commandes.

value = connection.ExecuteScalar(commandText)
// Command object created and disposed

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X