System.Data.SQLite Close () no libera el archivo de base de datos

96

Tengo problemas para cerrar mi base de datos antes de intentar eliminar el archivo. El código es solo

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

Y Delete lanza una excepción de que el archivo todavía está en uso. He vuelto a probar Delete () en el depurador después de unos minutos, por lo que no es un problema de tiempo.

Tengo un código de transacción, pero no se ejecuta en absoluto antes de la llamada Close (). Así que estoy bastante seguro de que no es una transacción abierta. Los comandos sql entre abrir y cerrar son solo selecciones.

ProcMon muestra mi programa y mi antivirus mirando el archivo de la base de datos. No muestra que mi programa libere el archivo db después de close ().

Visual Studio 2010, C #, System.Data.SQLite versión 1.0.77.0, Win7

Vi un error de dos años como este, pero el registro de cambios dice que está arreglado.

¿Hay algo más que pueda comprobar? ¿Hay alguna forma de obtener una lista de los comandos o transacciones abiertos?


Nuevo código de trabajo:

 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);
Tom Cerul
fuente
¿Intentaste: myconnection.Close (); myconnection.Dispose (); ?
UGEEN
1
Cuando use sqlite-net , puede usar SQLiteAsyncConnection.ResetPool(), consulte este problema para obtener más detalles.
Uwe Keim

Respuestas:

111

Encontré el mismo problema hace un tiempo mientras escribía una capa de abstracción de base de datos para C # y nunca logré averiguar cuál era el problema. Terminé lanzando una excepción cuando intentaste eliminar una base de datos SQLite usando mi biblioteca.

De todos modos, esta tarde lo estaba revisando todo de nuevo y pensé que intentaría averiguar por qué estaba haciendo eso de una vez por todas, así que esto es lo que he encontrado hasta ahora.

Lo que sucede cuando llama SQLiteConnection.Close()es que (junto con una serie de comprobaciones y otras cosas) SQLiteConnectionHandlese elimina el que apunta a la instancia de la base de datos SQLite. Esto se hace mediante una llamada a SQLiteConnectionHandle.Dispose(), sin embargo, esto en realidad no libera el puntero hasta que el recolector de basura de CLR realiza una recolección de basura. Dado que SQLiteConnectionHandleanula la CriticalHandle.ReleaseHandle()función a llamar sqlite3_close_interop()(a través de otra función), esto no cierra la base de datos.

Desde mi punto de vista, esta es una muy mala manera de hacer las cosas, ya que el programador no está realmente seguro de cuándo se cierra la base de datos, pero así es como se ha hecho, así que supongo que tenemos que vivir con eso por ahora, o comprometernos. algunos cambios en System.Data.SQLite. Cualquier voluntario puede hacerlo, desafortunadamente no tengo tiempo para hacerlo antes del próximo año.

TL; DR La solución es forzar un GC después de su llamada ay SQLiteConnection.Close()antes de su llamada a File.Delete().

Aquí está el código de ejemplo:

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

Buena suerte con eso y espero que ayude

Benjamin Pannell
fuente
1
¡Si! ¡Gracias! Parece que el GC podría necesitar un poco para hacer su trabajo.
Tom Cerul
1
Es posible que también desee ver C # SQLite, acabo de mover todo mi código para usarlo. Por supuesto, si está ejecutando algo de rendimiento crítico, entonces C probablemente sea más rápido que C #, pero soy un fanático del código administrado ...
Benjamin Pannell
1
Sé que esto es viejo, pero gracias por ahorrarme un poco de dolor. Este error también afecta a la versión Windows Mobile / Compact Framework de SQLite.
StrayPointer
2
¡Buen trabajo! Resolvió mi problema de inmediato. En 11 años de desarrollo de C # nunca tuve la necesidad de usar GC.Collect: Este es el primer ejemplo que me veo obligado a hacerlo.
Pilsator
10
GC.Collect (); funciona, pero System.Data.SQLite.SQLiteConnection.ClearAllPools (); trata el problema utilizando la API de la biblioteca.
Aaron Hudon
57

Simplemente GC.Collect()no funcionó para mí.

Tuve que agregar GC.WaitForPendingFinalizers()después GC.Collect()para continuar con la eliminación del archivo.

Batiati
fuente
5
Esto no es tan sorprendente, GC.Collect()simplemente inicia una recolección de basura que es asincrónica, por lo que para asegurarse de que todo se haya limpiado, debe esperar explícitamente.
ChrisWue
2
Experimenté lo mismo, tuve que agregar GC.WaitForPendingFinalizers (). Esto fue en 1.0.103
Vort3x
18

En mi caso, estaba creando SQLiteCommandobjetos sin eliminarlos explícitamente.

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

Envolví mi comando en una usingdeclaración y solucionó mi 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();
        }
    }
}

La usingdeclaración garantiza que se llame a Dispose incluso si se produce una excepción.

Entonces también es mucho más fácil ejecutar comandos.

value = connection.ExecuteScalar(commandText)
// Command object created and disposed
Nate
fuente
6
Recomiendo encarecidamente no tragar excepciones como esta
Tom McKearney
17

Tuve un problema similar, aunque la solución del recolector de basura no lo solucionó.

La eliminación de objetos SQLiteCommandy SQLiteDataReaderobjetos después de su uso me salvó de usar el recolector de basura.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
la ola
fuente
2
Exactamente. Asegúrese de desechar TODOS, SQLiteCommandincluso si recicla una SQLiteCommandvariable más adelante.
Bruno Bieri
Esto funcionó para mí. También me aseguré de deshacerme de cualquier transacción.
Jay-Nicolas Hackleman
1
¡Excelente! Me ahorraste bastante tiempo. Se solucionó el error cuando agregué command.Dispose();a todos los SQLiteCommandque se ejecutaron.
Ivan B
Además, asegúrese de liberar (es decir .Dispose()) otros objetos como SQLiteTransaction, si tiene alguno.
Ivan B
13

Lo siguiente funcionó para mí:

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

Más información : SQLite agrupa las conexiones para mejorar el rendimiento, lo que significa que cuando llamas al método Close en un objeto de conexión, es posible que la conexión a la base de datos todavía esté activa (en segundo plano) para que el siguiente método Open sea más rápido. ya no desea una nueva conexión, llamar a ClearAllPools cierra todas las conexiones que están vivas en segundo plano y se liberan los identificadores de archivo para el archivo db. Luego, el archivo db puede ser eliminado, eliminado o utilizado por otro proceso.

Arvin
fuente
1
¿Podría agregar una explicación de por qué esta es una buena solución al problema?
Matas Vaitkevicius
También puede utilizar SQLiteConnectionPool.Shared.Reset(). Esto cerrará todas las conexiones abiertas. En particular, esta es una solución si usa SQLiteAsyncConnectionque no tiene un Close()método.
Lorenzo Polidori
9

Estaba teniendo un problema similar, probé la solución con GC.Collectpero, como se señaló, puede pasar mucho tiempo antes de que el archivo no se bloquee.

Encontré una solución alternativa que implica la eliminación de los SQLiteCommands subyacentes en los TableAdapters; consulte esta respuesta para obtener información adicional.

edymtt
fuente
¡usted tenía razón! En algunos casos, el simple 'GC.Collect' funcionó para mí, en otros tuve que eliminar cualquier SqliteCommands asociado con la conexión antes de llamar a GC.Collect o de lo contrario no funcionará.
Eitan HS
1
Llamar a Dispose en SQLiteCommand funcionó para mí. Como comentario al margen, si llama a GC.Collect, está haciendo algo mal.
Natalie Adams
@NathanAdams cuando trabaja con EntityFramework, no hay un solo objeto de comando que pueda deshacerse. Entonces, el EntityFramework en sí o el contenedor SQLite para EF también están haciendo algo mal.
springy76
Tu respuesta debe ser la correcta. Muchas gracias.
Ahmed Shamel
5

Prueba esto ... este prueba todos los códigos anteriores ... funcionó para mí

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

Espero que ayude

Bishnu Dev
fuente
1
WaitForPendingFinalizers hizo toda la diferencia para mí
Todd
5

He tenido el mismo problema con EF y System.Data.Sqlite .

Para mí, encontré SQLiteConnection.ClearAllPools()y GC.Collect()reduciría la frecuencia con la que se produciría el bloqueo de archivos, pero aún así sucedía ocasionalmente (alrededor del 1% del tiempo).

He estado investigando y parece ser que algunos SQLiteCommandmensajes de correo electrónico que crea EF no se eliminan y aún tienen su propiedad Connection establecida en la conexión cerrada. Intenté eliminarlos, pero Entity Framework arrojaría una excepción durante la siguiente DbContextlectura; parece que EF a veces todavía los usa después de que se cierra la conexión.

Mi solución fue asegurarme de que la propiedad Connection esté configurada Nullcuando la conexión se cierre en estos SQLiteCommands. Esto parece ser suficiente para liberar el bloqueo del archivo. He estado probando el siguiente código y no he visto ningún problema de bloqueo de archivos después de varios miles de pruebas:

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

Para usarlo, simplemente llame ClearSQLiteCommandConnectionHelper.Initialise();al inicio de la carga de la aplicación. Esto entonces mantendrá una lista de comandos activos y establecerá su Conexión Nullcuando apunten a una conexión que está cerrada.

Hallupa
fuente
También tuve que establecer la conexión en nula en la parte DisposingCommand de esto, o ocasionalmente obtendría ObjectDisposedExceptions.
Elliot
Esta es una respuesta subestimada en mi opinión. Resolvió mis problemas de limpieza que no podía hacer yo mismo debido a la capa EF. Muy feliz de usar esto en ese feo truco de GC. ¡Gracias!
Jason Tyler
Si usa esta solución en un entorno multiproceso, la lista de OpenCommands debe ser [ThreadStatic].
Bero
3

Utilizar GC.WaitForPendingFinalizers()

Ejemplo:

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

Tuvo un problema similar. Llamar al recolector de basura no me ayudó. Después encontré una manera de resolver el problema

El autor también escribió que hizo consultas SELECT a esa base de datos antes de intentar eliminarla. Yo tengo la misma situación.

Tengo el siguiente código:

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.

Además, no necesito cerrar la conexión de la base de datos y llamar a Garbage Collector. Todo lo que tenía que hacer era cerrar el lector que se creó al ejecutar la consulta SELECT

Schullz
fuente
2

Creo que la llamada a SQLite.SQLiteConnection.ClearAllPools()es la solución más limpia. Hasta donde yo sé, no es apropiado llamar manualmente GC.Collect()en el entorno WPF. Aunque, no noté el problema hasta que actualicé a System.Data.SQLite1.0.99.0 en 3/2016

Jona Varque
fuente
2

Quizás no necesites lidiar con GC en absoluto. Por favor, compruebe si todo sqlite3_prepareestá finalizado.

Para cada uno sqlite3_prepare, necesita un corresponsal sqlite3_finalize.

Si no finaliza correctamente, sqlite3_closeno cerrará la conexión.

João Monteiro
fuente
1

Estaba luchando con un problema similar. Qué vergüenza ... Finalmente me di cuenta de que Reader no estaba cerrado. Por alguna razón, estaba pensando que el Reader se cerrará cuando se cierre la conexión correspondiente. Obviamente, GC.Collect () no funcionó para mí.
También es una buena idea envolver el lector con la declaración "using:". Aquí hay un código de prueba rápido.

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();
        }
    }   
}
Mike Znaet
fuente
0

Estaba usando SQLite 1.0.101.0 con EF6 y tenía problemas con el archivo bloqueado después de eliminar todas las conexiones y entidades.

Esto empeoró con las actualizaciones de EF que mantenían la base de datos bloqueada después de que se habían completado. GC.Collect () fue la única solución que ayudó y estaba comenzando a desesperarme.

Desesperado, probé ClearSQLiteCommandConnectionHelper de Oliver Wickenden (vea su respuesta del 8 de julio). Fantástico. ¡Todos los problemas de bloqueo desaparecieron! Gracias Oliver.

Tony Sullivan
fuente
Creo que esto debería ser un comentario en lugar de una respuesta
Kevin Wallis
1
Kevin, estoy de acuerdo, pero no se me permitió comentar porque necesito 50 reputación (aparentemente).
Tony Sullivan
0

Es posible que esperar al recolector de basura no libere la base de datos todo el tiempo y eso me sucedió. Cuando se produce algún tipo de excepción en la base de datos SQLite, por ejemplo, al intentar insertar una fila con un valor existente para PrimaryKey, se mantendrá el archivo de la base de datos hasta que lo deseche. El siguiente código detecta la excepción SQLite y cancela el comando problemático.

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

Si no maneja las excepciones de comandos problemáticos, Garbage Collector no puede hacer nada al respecto porque hay algunas excepciones no manejadas sobre estos comandos, por lo que no son basura. Este método de manejo funcionó bien para mí esperando al recolector de basura.

Muhammed Kadir
fuente
0

Esto funciona para mí, pero noté que a veces los archivos de diario -wal -shm no se eliminan cuando se cierra el proceso. Si desea que SQLite elimine los archivos -wal -shm cuando todas las conexiones estén cerradas, la última conexión cerrada DEBE SER de no solo lectura. Espero que esto ayude a alguien.

ekalchev
fuente
0

La mejor respuesta que funcionó para mí.

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

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

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
kazem fallahi
fuente