web-dev-qa-db-fra.com

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

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 que le fichier est toujours en cours d'utilisation. J'ai réessayé la suppression () dans le débogueur après quelques minutes, donc ce n'est pas un problème de synchronisation.

J'ai le code de transaction mais il ne fonctionne pas du tout avant l'appel de 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 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);
70
Tom Cerul

J'ai rencontré le même problème il y a un moment en écrivant une couche d'abstraction de base de données pour C # et je n'ai jamais vraiment trouvé le problème. Je viens de lancer une exception lorsque vous avez tenté de supprimer une base de données SQLite à l'aide de ma bibliothèque.

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

Lorsque vous appelez SQLiteConnection.Close(), il se passe que (avec un certain nombre de vérifications et d’autres tâches) la SQLiteConnectionHandle qui pointe vers l’instance de base de données SQLite est supprimée. Cette opération est effectuée via un appel à SQLiteConnectionHandle.Dispose(); toutefois, cela ne libère pas le pointeur tant que le ramasse-miettes du CLR n'a pas effectué de ramassage des ordures. Puisque SQLiteConnectionHandle remplace la fonction CriticalHandle.ReleaseHandle() pour 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 certain de la date de fermeture de la base de données, mais c’est comme cela que les choses se sont déroulées. quelques modifications à System.Data.SQLite. Tous les volontaires sont les bienvenus. Malheureusement, je n'ai plus le temps de le faire avant l'année prochaine.

TL; DR La solution consiste à forcer un CPG 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 cela aidera

79
Benjamin Pannell

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

Je devais ajouter GC.WaitForPendingFinalizers() après GC.Collect() afin de procéder à la suppression du fichier.

51
Batiati

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

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

J'ai enveloppé ma commande dans une instruction using et le problème a été résolu.

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'instruction 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
14
Nate

Ce qui suit a fonctionné pour moi:

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

Plus d'infos : Les connexions sont regroupées par SQLite afin d’améliorer les performances. Lorsque vous savez que vous ne voulez plus de nouvelle connexion, l'appel de ClearAllPools ferme toutes les connexions actives en arrière-plan et le ou les descripteurs de fichier du fichier db sont libérés. Le fichier db peut être supprimé, supprimé ou utilisé par un autre processus.

9
Arvin

Avait un problème similaire, mais la solution du ramasse-miettes n'a pas résolu le problème. 

La suppression des objets SQLiteCommand et SQLiteDataReader après leur utilisation m'a sauvé de l'utilisation du ramasse-miettes. 

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

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

J'ai trouvé une solution alternative impliquant la suppression de la variable SQLiteCommands sous-jacente dans TableAdapters, voir cette réponse pour plus d'informations.

8
edymtt

Utilisez GC.WaitForPendingFinalizers()

Exemple:

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

Peut-être n’avez-vous pas besoin de traiter du tout avec GC. S'il vous plaît, vérifiez si tout sqlite3_prepare est finalisé. 

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

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

2
João Monteiro

Avait un problème similaire. Appeler Garbage Collector ne m'a pas aidé. LAter j'ai trouvé un moyen de résoudre le problème

Author 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 ni d'appeler Garbage Collector. Tout ce que j'avais à faire est de fermer le lecteur créé lors de l'exécution de la requête SELECT

2
Schullz

Essayez ceci ... celui-ci essaie tout ce qui précède codes ... a fonctionné pour moi

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

J'espère que cela pourra aider

2
Bishnu Dev

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 j'ai mis à niveau à System.Data.SQLite 1.0.99.0 dans 3/2016

1
Jona Varque

Je me débattais avec le même problème. Honte à moi ... J'ai finalement compris que Lecteur n'était pas fermé. Pour une raison quelconque, je pensais que le Reader serait fermé lorsque la connexion correspondante serait fermée. De toute évidence, GC.Collect () n'a pas fonctionné pour moi.
Envelopper le Reader avec "using: statement est aussi 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();
        }
    }   
}
1
Mike Znaet

J'utilisais SQLite 1.0.101.0 avec EF6 et rencontrais des difficultés pour verrouiller le fichier après la suppression de toutes les connexions et entités.

Cela s'est aggravé avec les mises à jour de l'EF qui maintenait la base de données verrouillée une fois qu'elles avaient été complétées.

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

0
Tony Sullivan

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

0
ekalchev

Attendre que Garbage Collector ne libère pas toujours la base de données, ce qui m’est arrivé. Lorsqu'un type d'exception survient dans la base de données SQLite, par exemple, lorsque vous essayez d'insérer une ligne avec une valeur existante pour PrimaryKey, il conserve le fichier de base de données jusqu'à ce que vous le disposiez. 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 problématiques des commandes, Garbage Collector ne peut rien y faire car il existe des exceptions non gérées sur ces commandes, elles ne sont donc pas gâchées. Cette méthode de manipulation a bien fonctionné pour moi avec l’attente du ramasse-miettes.

0
Muhammed Kadir