2011-12-14 24 views
56

Sto riscontrando un problema nel chiudere il database prima di tentare di eliminare il file. Il codice è soloSystem.Data.SQLite Close() non rilascia file di database

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

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

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

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 la chiusura().

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

vidi un bambino di due anni bug, proprio come questo, ma il changelog dice che è fisso.

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


Nuovo, il codice di lavoro:

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

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

+1

Quando si utilizza [sqlite-net] (https://github.com/praeclarum/sqlite-net/), è possibile utilizzare 'SQLiteAsyncConnection.ResetPool()', vedere [questo problema] (https://github.com/ praeclarum/sqlite-net/pull/399) per i dettagli. –

risposta

63

incontrato lo stesso problema qualche tempo fa durante la scrittura di un livello di astrazione DB per C# e non ho mai realmente avuto intorno per scoprire che cosa il problema era. Ho appena finito di lanciare un'eccezione quando hai tentato di cancellare un DB SQLite usando la mia libreria.

In ogni caso, questo pomeriggio stavo guardando tutto da capo e ho pensato di provare a scoprire perché lo stava facendo una volta per tutte, quindi ecco cosa ho trovato finora.

Ciò che accade quando si chiama SQLiteConnection.Close() è che (insieme a un numero di controlli e altre cose) lo SQLiteConnectionHandle che punta all'istanza del database SQLite è disposto. Ciò avviene tramite una chiamata a SQLiteConnectionHandle.Dispose(), tuttavia in realtà questo non rilascia il puntatore fino a quando Garbage Collector di CLR esegue una procedura di garbage collection. Dal SQLiteConnectionHandle si ignora la funzione CriticalHandle.ReleaseHandle() per 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 dal momento che il programmatore non è sicuro quando il database viene chiuso, ma è così che è stato fatto quindi suppongo che dobbiamo conviverci per ora, o invia alcune modifiche a System.Data.SQLite. Tutti i volontari sono invitati a farlo, sfortunatamente sono fuori tempo per farlo prima del prossimo anno.

TL; DR La soluzione è quella di forzare un GC dopo la vostra chiamata a SQLiteConnection.Close() e prima della chiamata a File.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 con esso, e spero che aiuta

+1

Sì! Grazie! Sembra che il GC abbia bisogno di un po 'di tempo per completare il lavoro. –

+1

Si potrebbe anche voler guardare C# SQLite, ho appena spostato tutto il mio codice per usarlo. Naturalmente, se stai eseguendo qualcosa di critico per le prestazioni, allora C è probabilmente più veloce di C#, ma sono un fan del codice gestito ... –

+1

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

8

ho avuto un problema simile, ho provato la soluzione con GC.Collect ma, come notato, può richiedere molto tempo prima che il file non venga bloccato.

Ho trovato una soluzione alternativa che prevede lo smaltimento dei valori SQLiteCommand sottostanti nei TableAdapters, vedere this answer per ulteriori informazioni.

+0

avevi ragione! In alcuni casi semplice "GC.Collect" ha funzionato per me, in altri ho dovuto eliminare qualsiasi SqliteCommands associato alla connessione prima di chiamare GC.Collect altrimenti non funzionerà! –

+1

Chiamare Dispose su SQLiteCommand ha funzionato per me. Come commento a parte - se chiami GC.Collect stai facendo qualcosa di sbagliato. –

+0

@NathanAdams quando si lavora con EntityFramework non esiste un singolo oggetto comando che si possa mai disporre. Quindi anche EntityFramework o SQLite for EF wrapper fanno qualcosa di sbagliato. – springy76

14

Nel mio caso stavo creando oggetti SQLiteCommand senza eliminarli esplicitamente.

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

ho avvolto il mio comando in una dichiarazione using e fissa 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'affermazione using assicura che Dispose viene chiamato anche se si verifica un'eccezione.

Quindi è molto più semplice eseguire anche i comandi.

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

Mi raccomando molto contro la deglutizione di eccezioni come questa –

+0

Sono d'accordo. Rimosso. – Nate

45

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

Ho dovuto aggiungere dopo GC.Collect() per procedere con la cancellazione del file.

+3

Questo non è sorprendente, 'GC.Collect()' avvia semplicemente una garbage collection 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

+0

Ho dovuto aggiungerlo anch'io, grazie per averlo condiviso :) –

8

quanto segue funzionato per me:

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

Maggiori informazioni: connessioni sono raggruppati da SQLite al fine di migliorare performance.It significa quando si chiama Primo metodo su un oggetto di connessione, il collegamento alla banca dati può essere ancora vivo (in background) in modo che il prossimo metodo Open diventi più veloce. Quando sai che non vuoi più una nuova connessione, chiamare ClearAllPools chiude tutte le connessioni che sono vivi nello sfondo e l'handle di file (s?) a il file db viene rilasciato. Quindi il file db può essere rimosso, cancellato o utilizzato da un altro processo.

+1

Potresti aggiungere una spiegazione al motivo per cui questa è una buona soluzione al problema. –

+0

È anche possibile utilizzare 'SQLiteConnectionPool.Shared.Reset()'. Questo chiuderà tutte le connessioni aperte. In particolare, questa è una soluzione se si usa 'SQLiteAsyncConnection' che non ha un metodo' Close() '. –

8

Si è verificato un problema simile, sebbene la soluzione di garbage collector non lo risolvesse.

Trovato lo smaltimento di oggetti SQLiteCommand e SQLiteDataReader dopo l'uso mi ha salvato utilizzando il garbage collector.

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

Esattamente. Assicurarsi di disporre OGNI 'SQLiteCommand' anche se si ricicla una variabile' SQLiteCommand' in seguito. –

1

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. Sebbene, non ho notato il problema fino a quando non ho aggiornato a System.Data.SQLite 1.0.99.0 in 3/2016

2

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

Per me ho trovato SQLiteConnection.ClearAllPools() e GC.Collect() ridurre la frequenza con cui si verificherà il blocco dei file, ma potrebbe verificarsi occasionalmente (circa l'1% delle volte).

Ho indagato e sembra che alcuni SQLiteCommand che EF crea non siano disposti e abbiano ancora la loro proprietà Connection impostata sulla connessione chiusa. Ho provato a smaltirli, ma Entity Framework lanciava un'eccezione durante la successiva lettura di DbContext - sembra che EF a volte li usi ancora dopo la connessione chiusa.

La mia soluzione era assicurarsi che la proprietà Connection sia impostata su Null quando la connessione si chiude su questi SQLiteCommand s. Questo sembra essere sufficiente per rilasciare il blocco dei file. Ho testato il codice qui sotto e non si vede tutti i problemi di blocco file dopo qualche migliaio 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 utilizzare basta chiamare ClearSQLiteCommandConnectionHelper.Initialise(); all'inizio del carico dell'applicazione. Ciò manterrà quindi un elenco di comandi attivi e imposterà la loro connessione su Null quando puntano a una connessione chiusa.

0

Stavo usando SQLite 1.0.101.0 con EF6 e ho problemi con il file bloccato dopo tutte le connessioni e le entità disposte.

Ciò è peggiorato con gli aggiornamenti dall'EF mantenendo il database bloccato dopo che erano stati completati. GC.Collect() è stata l'unica soluzione che mi 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.

+0

Penso che questo dovrebbe essere un commento invece di una risposta –

+1

Kevin, sono d'accordo, ma non mi è stato permesso di commentare perché ho bisogno di 50 reputazione (apparentemente). –

1

Ero alle prese con il problema simile. Vergogna su di me ... ho finalmente capito che il Reader non è stato chiuso. Per qualche ragione stavo pensando che il Reader sarà chiuso quando la connessione corrispondente sarà chiusa. Ovviamente, GC.Collect() non ha funzionato per me.
Avvolgendo il lettore con "utilizzando:. Affermazione è anche una buona idea qui è un codice rapido test

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

Prova questo ... questo si cerca tutti i suddetti codici ... ha lavorato per me.

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

Speranza che aiuta

+1

WaitForPendingFinalizers ha fatto la differenza per me – Todd

1

Forse non c'è bisogno di trattare con GC a tutti. vi preghiamo di controllare se tutto è finalizzato sqlite3_prepare.

Per ogni sqlite3_prepare, è necessario un corrispondente sqlite3_finalize.

Se non si finalizza correttamente, sqlite3_close non chiude la connessione.

2

Usa GC.WaitForPendingFinalizers()

Esempio:

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

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

L'autore ha anche scritto che ha fatto query SELECT a quel database prima di provare a 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 ho bisogno di chiudere la connessione al database e di chiamare Garbage Collector. Tutto quello che dovevo fare era chiudere il lettore creato durante l'esecuzione della query SELECT

0

L'attesa di Garbage Collector potrebbe non rilasciare il database tutto il tempo e quello che è successo a me. Quando si verifica un qualche tipo di Eccezione nel database SQLite, ad esempio se si tenta di inserire una riga con il valore esistente per PrimaryKey, il file del database verrà conservato fino a quando non lo si elimina. Il seguente codice intercetta 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 gestire le eccezioni comandi problematici di Garbage Collector non può fare nulla su di loro perché ci sono alcune eccezioni non gestite su questi comandi e quindi non sono spazzatura. Questo metodo di gestione ha funzionato bene per me con l'attesa per il garbage collector.

Problemi correlati