System.Data.SQLite Close () non rilascia il file di database


96

Ho problemi a chiudere il database prima di tentare di eliminare il file. Il codice è giusto

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

E Elimina genera un'eccezione che il file è ancora in uso. Ho riprovato Delete () nel debugger dopo pochi minuti, quindi non è un problema di temporizzazione.

Ho un codice di transazione ma non viene eseguito affatto prima della chiamata Close (). Quindi sono abbastanza sicuro che non sia una transazione aperta. I comandi sql tra open e close sono solo selezioni.

ProcMon mostra il mio programma e il mio antivirus guardando il file del database. Non mostra il mio programma che rilascia il file db dopo close ().

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

Ho visto un bug di due anni proprio come questo ma il log delle modifiche dice che è stato risolto.

C'è qualcos'altro che posso controllare? C'è un modo per ottenere un elenco di eventuali comandi o transazioni aperti?


Nuovo codice funzionante:

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

Hai provato: myconnection.Close (); myconnection.Dispose (); ?
UGEEN

1
Quando si utilizza sqlite-net , è possibile utilizzare SQLiteAsyncConnection.ResetPool(), vedere questo problema per i dettagli.
Uwe Keim

Risposte:


111

Ho riscontrato lo stesso problema qualche tempo fa mentre scrivevo un livello di astrazione DB per C # e non sono mai riuscito a scoprire quale fosse il problema. Ho appena finito per lanciare un'eccezione quando hai tentato di eliminare un DB SQLite usando la mia libreria.

Comunque, questo pomeriggio stavo esaminando di nuovo tutto e ho pensato che avrei provato a scoprire perché lo stava facendo una volta per tutte, quindi ecco cosa ho trovato finora.

Quello che succede quando si chiama SQLiteConnection.Close()è che (insieme a una serie di controlli e altre cose) SQLiteConnectionHandleviene eliminato il punto che punta all'istanza del database SQLite. Questa operazione viene eseguita tramite una chiamata a SQLiteConnectionHandle.Dispose(), tuttavia ciò non rilascia effettivamente il puntatore fino a quando il Garbage Collector di CLR non esegue un'operazione di Garbage Collection. Poiché SQLiteConnectionHandlesovrascrive la CriticalHandle.ReleaseHandle()funzione da chiamare sqlite3_close_interop()(tramite un'altra funzione), questo non chiude il database.

Dal mio punto di vista questo è un pessimo modo di fare le cose poiché il programmatore non è effettivamente certo quando il database viene chiuso, ma è così che è stato fatto quindi immagino che per ora dobbiamo conviverci, o impegnarci alcune modifiche a System.Data.SQLite. Tutti i volontari sono invitati a farlo, purtroppo non ho il tempo per farlo prima del prossimo anno.

TL; DR La soluzione è forzare un GC dopo la chiamata a SQLiteConnection.Close()e prima della chiamata aFile.Delete() .

Ecco il codice di esempio:

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

Buona fortuna e spero che aiuti


1
Sì! Grazie! Sembra che il GC potrebbe aver bisogno di un po 'per portare a termine il suo lavoro.
Tom Cerul

1
Potresti anche dare un'occhiata a C # SQLite, ho appena spostato tutto il mio codice per usarlo. Ovviamente, se stai eseguendo qualcosa di critico per le prestazioni, C è probabilmente più veloce di C #, ma sono un fan del codice gestito ...
Benjamin Pannell

1
So che è vecchio, ma grazie per avermi risparmiato un po 'di dolore. Questo bug interessa anche la build Windows Mobile / Compact Framework di SQLite.
StrayPointer

2
Ottimo lavoro! Ho risolto immediatamente il mio problema. In 11 anni di sviluppo C # non ho mai avuto la necessità di usare GC.Collect: questo è il primo esempio in cui sono costretto a farlo.
Pilsator

10
GC.Collect (); funziona, ma System.Data.SQLite.SQLiteConnection.ClearAllPools (); affronta il problema utilizzando l'API della libreria.
Aaron Hudon

57

Semplicemente GC.Collect()non ha funzionato per me.

Ho dovuto aggiungere GC.WaitForPendingFinalizers()dopo GC.Collect()per procedere con l'eliminazione del file.


5
Questo non è così sorprendente, GC.Collect()avvia semplicemente una raccolta dei rifiuti che è asincrona, quindi per assicurarti che tutto sia stato ripulito devi aspettare esplicitamente.
ChrisWue

2
Ho sperimentato lo stesso, ho dovuto aggiungere GC.WaitForPendingFinalizers (). Questo era in 1.0.103
Vort3x

18

Nel mio caso stavo creando SQLiteCommandoggetti senza disporli esplicitamente.

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

Ho racchiuso il mio comando in una usingdichiarazione e ha risolto il mio problema.

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' usingistruzione garantisce che Dispose venga chiamato anche se si verifica un'eccezione.

Quindi è molto più facile eseguire anche i comandi.

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

6
Consiglio vivamente di ingerire eccezioni come questa
Tom McKearney,

17

Ha avuto un problema simile, anche se la soluzione del Garbage Collector non lo ha risolto.

Trovato lo smaltimento SQLiteCommande gli SQLiteDataReaderoggetti dopo l'uso mi ha salvato l'uso del garbage collector.

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

2
Esattamente. Assicurati di smaltire OGNI SQLiteCommandanche se in seguito ricicli una SQLiteCommandvariabile.
Bruno Bieri

Questo ha funzionato per me. Mi sono anche assicurato di disporre di eventuali transazioni.
Jay-Nicolas Hackleman

1
Grande! Mi hai risparmiato un bel po 'di tempo. Ha corretto l'errore quando ho aggiunto command.Dispose();a tutti SQLiteCommandquelli eseguiti.
Ivan B

Inoltre, assicurati di rilasciare (cioè .Dispose()) altri oggetti come SQLiteTransaction, se ne hai.
Ivan B

13

Quanto segue ha funzionato per me:

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

Ulteriori informazioni : le connessioni sono raggruppate da SQLite per migliorare le prestazioni Significa che quando chiami il metodo Close su un oggetto connessione, la connessione al database potrebbe essere ancora attiva (in background) in modo che il successivo metodo Open diventi più veloce. non vuoi più una nuova connessione, chiamando ClearAllPools si chiudono tutte le connessioni che sono attive in background e gli handle di file al file db vengono rilasciati. Quindi il file db può essere rimosso, eliminato o utilizzato da un altro processo.


1
Potresti aggiungere una spiegazione del motivo per cui questa è una buona soluzione al problema.
Matas Vaitkevicius

Puoi anche usare SQLiteConnectionPool.Shared.Reset(). Questo chiuderà tutte le connessioni aperte. In particolare, questa è una soluzione se usi SQLiteAsyncConnectionche non ha un Close()metodo.
Lorenzo Polidori

9

Stavo riscontrando un problema simile, ho provato la soluzione con GC.Collectma, come notato, può volerci molto tempo prima che il file non venga bloccato.

Ho trovato una soluzione alternativa che prevede lo smaltimento dei sottostanti SQLiteCommandnei TableAdapter, vedere questa risposta per ulteriori informazioni.


avevi ragione! In alcuni casi il semplice "GC.Collect" ha funzionato per me, in altri ho dovuto eliminare tutti i comandi Sqlite associati alla connessione prima di chiamare GC.Collect altrimenti non funzionava!
Eitan HS

1
La chiamata di Dispose su SQLiteCommand ha funzionato per me. Come commento a parte, se chiami GC.Collect stai facendo qualcosa di sbagliato.
Natalie Adams

@NathanAdams quando lavori con EntityFramework non c'è un singolo oggetto comando che puoi mai eliminare. Quindi anche lo stesso EntityFramework o il wrapper SQLite per EF sta facendo qualcosa di sbagliato.
springy76

La tua risposta dovrebbe essere quella corretta. Molte grazie.
Ahmed Shamel

5

Prova questo ... questo prova tutti i codici sopra ... ha funzionato per me

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

spero che aiuti


1
WaitForPendingFinalizers ha fatto la differenza per me
Todd

5

Ho avuto lo stesso problema con EF e System.Data.Sqlite.

Per quanto mi riguarda, ho scoperto SQLiteConnection.ClearAllPools()e GC.Collect()vorrei ridurre la frequenza con cui si verificava il blocco dei file, ma si verificava comunque occasionalmente (circa l'1% delle volte).

Ho studiato e sembra che alcuni messaggi di posta SQLiteCommandelettronica creati da EF non siano eliminati e abbiano ancora la loro proprietà Connection impostata sulla connessione chiusa. Ho provato a eliminarli, ma Entity Framework generava un'eccezione durante la DbContextlettura successiva : sembra che EF a volte li utilizzi ancora dopo la chiusura della connessione.

La mia soluzione era di assicurarmi che la proprietà Connection fosse impostata su Nullquando la connessione si chiude su questi SQLiteCommands. Questo sembra essere sufficiente per rilasciare il blocco dei file. Ho testato il codice seguente e non ho riscontrato alcun problema di blocco dei file dopo alcune migliaia di test:

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

Per usare basta chiamare ClearSQLiteCommandConnectionHelper.Initialise();all'inizio del caricamento dell'applicazione. Ciò manterrà quindi un elenco di comandi attivi e imposterà la loro connessione Nullquando puntano a una connessione chiusa.


Ho dovuto anche impostare la connessione su null nella parte DisposingCommand di questo, o occasionalmente ricevevo ObjectDisposedExceptions.
Elliot

Questa è una risposta sottovalutata secondo me. Ha risolto i miei problemi di pulizia che non potevo fare da solo a causa del livello EF. Molto felice di usarlo su quel brutto hack GC. Grazie!
Jason Tyler

Se si utilizza questa soluzione in un ambiente multithread, l'elenco OpenCommands dovrebbe essere [ThreadStatic].
Bero

3

Uso GC.WaitForPendingFinalizers()

Esempio:

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

3

Ho avuto un problema simile. Chiamare Garbage Collector non mi ha aiutato. Successivamente ho trovato un modo per risolvere il problema

L'autore ha anche scritto di aver eseguito delle query SELECT su quel database prima di tentare di eliminarlo. Ho la stessa situazione.

Ho il codice seguente:

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.

Inoltre, non è necessario chiudere la connessione al database e chiamare Garbage Collector. Tutto quello che dovevo fare era chiudere il lettore creato durante l'esecuzione della query SELECT


2

Credo che la chiamata a SQLite.SQLiteConnection.ClearAllPools()sia la soluzione più pulita. Per quanto ne so, non è corretto chiamare manualmente GC.Collect()nell'ambiente WPF. System.Data.SQLiteTuttavia , non ho notato il problema fino a quando non ho aggiornato a 1.0.99.0 in 3/2016


2

Forse non hai affatto bisogno di occuparti di GC. Per favore, controlla se tutto sqlite3_prepareè finalizzato.

Per ognuno sqlite3_prepare, hai bisogno di un corrispondente sqlite3_finalize.

Se non si finalizza correttamente, sqlite3_closela connessione non verrà chiusa.


1

Stavo lottando con il problema simile. Peccato per me ... ho finalmente capito che Reader non era chiuso. Per qualche ragione stavo pensando che il Reader verrà chiuso quando la connessione corrispondente sarà chiusa. Ovviamente, GC.Collect () non ha funzionato per me.
Anche avvolgere il Reader con l'istruzione "using:" è una buona idea. Ecco un rapido codice di prova.

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

0

Stavo usando SQLite 1.0.101.0 con EF6 e avevo problemi con il blocco del file dopo che tutte le connessioni e le entità erano state eliminate.

La situazione è peggiorata con gli aggiornamenti dall'EF mantenendo il database bloccato dopo il completamento. GC.Collect () è stata l'unica soluzione alternativa che ha aiutato e stavo cominciando a disperare.

In preda alla disperazione, ho provato ClearSQLiteCommandConnectionHelper di Oliver Wickenden (vedere la sua risposta dell'8 luglio). Fantastico. Tutti i problemi di blocco sono andati! Grazie Oliver.


Penso che questo dovrebbe essere un commento invece di una risposta
Kevin Wallis

1
Kevin, sono d'accordo, ma non mi è stato permesso di commentare perché ho bisogno di 50 reputazione (a quanto pare).
Tony Sullivan

0

Aspettare Garbage Collector potrebbe non rilasciare il database tutto il tempo e questo è successo a me. Quando si verifica un qualche tipo di eccezione nel database SQLite, ad esempio tentando di inserire una riga con un valore esistente per PrimaryKey, il file di database verrà conservato finché non lo si elimina. Il codice seguente rileva l'eccezione SQLite e annulla il comando problematico.

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

Se non gestisci le eccezioni dei comandi problematici, Garbage Collector non può fare nulla al riguardo perché ci sono alcune eccezioni non gestite su questi comandi, quindi non sono spazzatura. Questo metodo di gestione ha funzionato bene per me con l'attesa del garbage collector.


0

Questo funziona per me, ma ho notato che a volte i file journal -wal -shm non vengono eliminati quando il processo viene chiuso. Se si desidera che SQLite rimuova i file -wal -shm quando tutte le connessioni sono chiuse, l'ultima connessione chiusa DEVE ESSERE di sola lettura. Spero che questo possa aiutare qualcuno.


0

La migliore risposta che ha funzionato per me.

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

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

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.