web-dev-qa-db-fra.com

SQLite maintient la base de données verrouillée même après la fermeture de la connexion

J'utilise le fournisseur System.Data.SQLite dans une application ASP.NET (framework 4.0) . Le problème que je rencontre est que, lorsque j'insère quelque chose dans une table de la base de données SQLite, celle-ci est verrouillée et le verrou n'est pas libéré même après la suppression de la connexion. 

Lors de la tentative d'accès au fichier, l'erreur est la suivante: "Le processus ne peut pas accéder au fichier 'catalog.sqlite' car il est utilisé par un autre processus."

Mon code est assez simple: j'ouvre la connexion, lit certaines données d'une base de données SQLServer, les insère dans SQLite (via SQLiteDataAdapter), puis ferme la connexion et dispose de tout pour être sûr. Mais quand même, j'obtiens cette erreur lorsque j'essaie de compresser le fichier après l'avoir rempli avec les données.

J'ai lu toutes sortes de suggestions ici sur StackOverflow, mais aucune d'entre elles n'a permis de résoudre le problème (désactiver l'antivirus, changer le modèle de transaction, attendre quelques secondes avant de compresser le fichier, puis intégrer tous les appels insérés dans une transaction, etc. mais aucun n'a aidé à résoudre ce problème.

Peut-être qu'il y a quelque chose de spécifique à ASP.NET (le multithreading étant le problème? Même si je le teste sur une machine de développement où il n'y a qu'un seul appel à cette fonction et pas d'accès simultané?)

En guise de remarque, j'ai essayé d'éviter DataTable et SQLiteDataAdapter et d'utiliser uniquement SQLiteCommand directement, ce qui permet d'obtenir un charme. Bien sûr, je peux continuer à construire mes requêtes sous forme de chaînes au lieu d'utiliser des adaptateurs de données, mais je trouve cela un peu gênant quand un cadre est construit pour le faire.

35
Valerio Santinelli

J'ai eu le même problème en utilisant les datasets/tableadapters générés avec le concepteur fourni avec System.Data.Sqlite.dll version 1.0.82.0 - après la fermeture de la connexion, nous n'avons pas pu lire le fichier de base de données avec System.IO.FileStream. Je disposais correctement à la fois des connexions et des tableadapters et je n'utilisais pas le pooling de connexion.

Selon mes premières recherches (par exemple this et ce fil ) qui semblait être un problème dans la bibliothèque elle-même - soit des objets mal libérés et/ou des problèmes de mise en commun (que je n'utilise pas).

Après avoir lu votre question, j'ai essayé de reproduire le problème en utilisant uniquement des objets SQLiteCommand et j'ai constaté que le problème se pose lorsque vous ne les supprimez pas. Mise à jour 2012-11-27 19:37 UTC: cela est confirmé par ce ticket pour System.Data.SQLite, dans lequel un développeur explique que "tout SQLiteCommand et SQLiteDataReader les objets associés à la connexion [doivent être] correctement éliminés ".

J'ai ensuite retourné sur les TableAdapters générés et j'ai vu qu'il n'y avait pas d'implémentation de la méthode Dispose - ainsi, en fait, les commandes créées n'ont pas été supprimées. Je l'ai implémenté, en prenant soin de disposer de toutes les commandes, et je n'ai aucun problème.

Voici le code en C #, espérons que cela aide. Notez que le code est converti à partir de original dans Visual Basic , donc attendez-vous à des erreurs de conversion.

//In Table Adapter    
protected override void Dispose(bool disposing)
{
   base.Dispose(disposing);

    Common.DisposeTableAdapter(disposing, _adapter, _commandCollection);
}

public static class Common
{
    /// <summary>
    /// Disposes a TableAdapter generated by SQLite Designer
    /// </summary>
    /// <param name="disposing"></param>
    /// <param name="adapter"></param>
    /// <param name="commandCollection"></param>
    /// <remarks>You must dispose all the command,
    /// otherwise the file remains locked and cannot be accessed
    /// (for example, for reading or deletion)</remarks>
    public static void DisposeTableAdapter(
        bool disposing,
        System.Data.SQLite.SQLiteDataAdapter adapter,
        IEnumerable<System.Data.SQLite.SQLiteCommand> commandCollection)
    {
        if (disposing) {
            DisposeSQLiteTableAdapter(adapter);

            foreach (object currentCommand_loopVariable in commandCollection)
            {
                currentCommand = currentCommand_loopVariable;
                currentCommand.Dispose();
            }
        }
    }

    public static void DisposeSQLiteTableAdapter(
            System.Data.SQLite.SQLiteDataAdapter adapter)
    {
        if (adapter != null) {
            DisposeSQLiteTableAdapterCommands(adapter);

            adapter.Dispose();
        }
    }

    public static void DisposeSQLiteTableAdapterCommands(
            System.Data.SQLite.SQLiteDataAdapter adapter)
    {
        foreach (object currentCommand_loopVariable in {
            adapter.UpdateCommand,
            adapter.InsertCommand,
            adapter.DeleteCommand,
            adapter.SelectCommand})
        {
            currentCommand = currentCommand_loopVariable;
            if (currentCommand != null) {
                currentCommand.Dispose();
            }
        }
    }
}

Mise à jour 2013-07-05 17:36 UTCLa réponse de gorogm met en évidence deux choses importantes:

  • selon le changelog sur le site officiel de System.Data.SQLite, à partir de la version 1.0.84.0, le code ci-dessus ne devrait pas être nécessaire, car la bibliothèque s'en occupe. Je n'ai pas testé cela, mais dans le pire des cas, vous n'avez besoin que de cet extrait:

    //In Table Adapter    
    protected override void Dispose(bool disposing)
    {
      base.Dispose(disposing);
    
      this.Adapter.Dispose();
    }
    
  • à propos de l'implémentation de l'appel Dispose de TableAdapter: il est préférable de le placer dans une classe partielle, de sorte qu'une régénération de jeu de données n'affecte pas ce code (et tout code supplémentaire que vous devrez éventuellement ajouter).

34
edymtt

J'ai le même problème. Mon scénario était après la récupération des données dans le fichier de base de données SQLite. Je souhaite supprimer ce fichier, mais génère toujours une erreur "... en utilisant un autre processus". Même si je dispose de la SqliteConnection ou SqliteCommand l'erreur se produit toujours. J'ai corrigé l'erreur en appelant GC.Collect().

Extrait de code

public void DisposeSQLite()
{
    SQLiteConnection.Dispose();
    SQLiteCommand.Dispose();

    GC.Collect();
}

J'espère que cette aide.

24
klaydze

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 qui a résolu mon problème.

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

Ensuite, vous pouvez l'utiliser comme ça

connection.ExecuteScalar(commandText);
9
Nate

Ce qui suit a fonctionné pour moi: MySQLiteConnection.Close(); SQLite.SQLiteConnection.ClearAllPools()

6
Arvin

J'ai trouvé que la réponse d'Edymtt était juste à propos de blâmer les TableAdapters/Datasets, mais au lieu de modifier le fichier de code TableAdapter chaque fois régénéré, j'ai trouvé une autre solution: appeler manuellement .Dispose sur les éléments enfants du TableAdapter. (Dans .NET 4.5, dernière version de SQLite 1.0.86)

using (var db = new testDataSet())
{
    using (testDataSetTableAdapters.UsersTableAdapter t = new testDataSetTableAdapters.UsersTableAdapter())
    {
        t.Fill(db.Users);
        //One of the following two is enough
        t.Connection.Dispose(); //THIS OR
        t.Adapter.Dispose();    //THIS LINE MAKES THE DB FREE
    }
    Console.WriteLine((from x in db.Users select x.Username).Count());
}
1
gorogm

Dans la plupart des cas, le problème se posera si vous ne disposez pas correctement de vos lecteurs et de vos commandes. Il existe un scénario dans lequel les commandes et les lecteurs ne disposeront pas correctement.

Scénario 1: Si vous utilisez une fonction boolean . avant qu'un résultat soit atteint, le code dans le bloc finally ne sera pas exécuté. C'est un gros problème si vous allez évaluer les résultats de la fonction isDataExists lors de l'exécution du code si cela convient au résultat i.e

    if(isDataExists){
        // execute some code
    }

La fonction en cours d'évaluation

    public bool isDataExists(string sql)
    {
        try
        {
            OpenConnection();
            SQLiteCommand cmd = new SQLiteCommand(sql, connection);
            reader = cmd.ExecuteReader();
            if (reader != null && reader.Read())
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        catch (Exception expMsg)
        {
            //Exception
        }
        finally
        {
            if (reader != null)
            {
                reader.Dispose();
            }
            CloseConnection();
        }
        return true;
    }

Solution: Eliminez votre lecteur et votre commande à l'intérieur du bloc try comme suit

            OpenConnection();
            SQLiteCommand cmd = new SQLiteCommand(sql, connection);
            reader = cmd.ExecuteReader();
            if (reader != null && reader.Read())
            {
                cmd.Dispose();
                CloseConnection();
                return true;
            }
            else
            {
                cmd.Dispose();
                CloseConnection();
                return false;
            }

Enfin dispose le lecteur et commande juste au cas où quelque chose se passerait mal

        finally
        {
            if (reader != null)
            {
                reader.Dispose();
            }
            CloseConnection();
        }
1
desw

Comme indiqué précédemment, les objets SQLite doivent être détruits. Cependant, il existe un comportement étrange: la connexion doit être ouverte lors d'un appel aux commandes Dispose on. Par exemple:

using(var connection = new SqliteConnection("source.db"))
{
    connection.Open();
    using(var command = connection.CreateCommand("select..."))
    {
        command.Execute...
    }
}

fonctionne bien, mais:

using(var connection = new SqliteConnection("source.db"))
{
    connection.Open();
    using(var command = connection.CreateCommand("select..."))
    {
        command.Execute...
        connection.Close();
    }
}

donne le même verrou de fichier

1

S'assurer que tout objet IDisposable (par exemple, SQLiteConnection, SQLiteCommand, etc.) est correctement éliminé, résout ce problème. Je devrais réitérer qu'il faut utiliser "utiliser" comme habitude pour assurer une élimination appropriée des ressources disponibles.

0
pengu1n

C’est l’un des meilleurs résultats Google que j’ai trouvé lorsque j’ai rencontré cette erreur. Cependant, aucune des réponses ne m’a aidé. Après plus de recherches et de recherches sur Google, j’ai trouvé ce code qui fonctionne à partir du code de http://www.tsjensen.com/blog/post/2012/11/10 /SQLite-on-Visual-Studio-with-NuGet-and-Easy-Instructions.aspx

Cependant, je n'ai pas eu à utiliser NuGet du tout. Mon programme télécharge un fichier de base de données d'un serveur à chaque fois qu'il est ouvert. Ensuite, si un utilisateur met à jour cette base de données, elle sera téléchargée pour que tout le monde puisse l'obtenir à la prochaine ouverture du même programme. Je pensais que le fichier était en cours d'utilisation après avoir mis à jour le fichier local et essayé de le télécharger sur notre SharePoint. Maintenant cela fonctionne bien.

Public Function sqLiteGetDataTable(sql As String) As DataTable
    Dim dt As New DataTable()
    Using cnn = New SQLiteConnection(dbConnection)
        cnn.Open()
        Using cmd As SQLiteCommand = cnn.CreateCommand()
            cmd.CommandText = sql
            Using reader As System.Data.SQLite.SQLiteDataReader = cmd.ExecuteReader()
                dt.Load(reader)
                reader.Dispose()
            End Using
            cmd.Dispose()
        End Using
        If cnn.State <> System.Data.ConnectionState.Closed Then
            cnn.Close()
        End If
        cnn.Dispose()
    End Using
    Return dt
End Function
0
relic120

Je n'avais que les problèmes mentionnés ici lorsque verrouiller l'ordinateur, même après l'avoir déverrouillé, fonctionnait correctement, sinon je suis chanceux parce que je viens juste de commencer à le verrouiller récemment et que le logiciel vient de sortir il y a quelques jours avant que personne ne le sache.

Quoi qu'il en soit, j'avais tous les trucs comme la fermeture de connexions et ClearAllPools, etc., mais il manquait aTableAdapter.Adapter.Dispose () et cela a été corrigé.

0
colin lamarre

J'ai eu le même problème et il n'a été corrigé qu'en disposant de DbCommand dans l'instruction using, mais avec Pooling = true, mon problème a été résolu!

                SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder
                {
                    Pooling = true
                };
0
Dominic Jonas