System.Data.SQLite Close() no libera el archivo de la base de datos
sqlite uwp (15)
Tengo un problema para cerrar mi base de datos antes de intentar eliminar el archivo. El código es solo
myconnection.Close();
File.Delete(filename);
Y el Eliminar arroja 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 funciona 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 solo son seleccionables.
ProcMon muestra mi programa y mi antivirus mirando el archivo de la base de datos. No muestra mi programa liberando el archivo db después del cierre ().
Visual Studio 2010, C #, System.Data.SQLite versión 1.0.77.0, Win7
Vi un error de dos años así, pero el registro de cambios dice que está arreglado.
¿Hay algo más que pueda verificar? ¿Hay alguna forma de obtener una lista de 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);
Creo que la llamada a SQLite.SQLiteConnection.ClearAllPools()
es la solución más limpia. Por lo que sé, no es apropiado llamar manualmente a GC.Collect()
en el entorno WPF. Aunque no noté el problema hasta que me actualicé a System.Data.SQLite
1.0.99.0 en 3/2016
En mi caso, estaba creando objetos SQLiteCommand
sin eliminarlos explícitamente.
var command = connection.CreateCommand();
command.CommandText = commandText;
value = command.ExecuteScalar();
Envolví mi comando en una declaración de using
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 instrucción
using
asegura que se llama a Dispose incluso si se produce una excepción.
Entonces es mucho más fácil ejecutar comandos también.
value = connection.ExecuteScalar(commandText)
// Command object created and disposed
Encontré el mismo problema hace un tiempo cuando escribía una capa de abstracción de base de datos para C # y nunca llegué a averiguar cuál era el problema. Acabo de lanzar una excepción cuando intentaste eliminar una base de datos SQLite usando mi biblioteca.
De todos modos, esta tarde estaba repasando todo nuevamente y pensé que trataría de averiguar por qué lo estaba haciendo de una vez por todas, así que aquí está lo que he encontrado hasta ahora.
Lo que ocurre cuando se llama a SQLiteConnection.Close()
es que (junto con una serie de comprobaciones y otras cosas) se SQLiteConnectionHandle
el SQLiteConnectionHandle
que apunta a la instancia de la base de datos SQLite. Esto se hace a través de una llamada a SQLiteConnectionHandle.Dispose()
, sin embargo, esto no libera el puntero hasta que el recolector de basura de CLR realiza una recolección de basura. Como SQLiteConnectionHandle
anula la función CriticalHandle.ReleaseHandle()
para llamar a 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 forma de hacer las cosas ya que el programador no está seguro cuando la base de datos se cierra, pero así es como se hizo, así que supongo que tenemos que vivir con eso por el momento o comprometernos algunos cambios en System.Data.SQLite. Cualquier voluntario puede hacerlo, lamentablemente 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 a SQLiteConnection.Close()
y 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
Estaba luchando con el problema similar. Lástima de mí ... 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í.
Envolver el Reader con "using: statement" también es una buena idea. 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();
}
}
}
Estaba teniendo un problema similar, probé la solución con GC.Collect
pero, como noté, puede tomar mucho tiempo antes de que el archivo no se bloquee.
He encontrado una solución alternativa que implica la eliminación de los SQLiteCommand
s subyacentes en TableAdapters; consulte esta respuesta para obtener información adicional.
Estaba usando SQLite 1.0.101.0 con EF6 y tuve problemas con el archivo bloqueado después de eliminar todas las conexiones y entidades.
Esto empeoró con las actualizaciones del EF manteniendo la base de datos bloqueada después de que se completaran. GC.Collect () fue la única solución que ayudó y comencé a desesperarme.
Desesperado, probé ClearSQLiteCommandConnectionHelper de Oliver Wickenden (ver su respuesta del 8 de julio). Fantástico. ¡Todos los problemas de bloqueo se han ido! Gracias Oliver
He estado teniendo 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 ocurriría ocasionalmente (Alrededor del 1% del tiempo).
He estado investigando y parece ser que algunos SQLiteCommand
s que crea EF no están eliminados y aún tienen su propiedad de conexión establecida en la conexión cerrada. Traté de eliminarlos, pero Entity Framework arrojó una excepción durante la próxima lectura de DbContext
, parece que EF a veces todavía los usa después de que se cierra la conexión.
Mi solución fue asegurar que la propiedad Connection esté configurada en Null
cuando la conexión se cierre en estos SQLiteCommand
. Esto parece ser suficiente para liberar el bloqueo de archivos. He estado probando el código a continuación y no he visto ningún problema de bloqueo de archivos después de unas 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 usar simplemente llame a ClearSQLiteCommandConnectionHelper.Initialise();
al inicio de la carga de la aplicación. Esto mantendrá una lista de comandos activos y establecerá su conexión en Null
cuando apunten a una conexión que está cerrada.
Lo siguiente funcionó para mí:
MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()
Más información : SQLite combina las conexiones para mejorar el rendimiento. Significa que cuando se llama al método Close en un objeto de conexión, la conexión a la base de datos puede seguir activa (en segundo plano) para que el próximo método Open sea más rápido. ya no desea una nueva conexión, al llamar a ClearAllPools, se cierran todas las conexiones que están activas en segundo plano y se liberan los identificadores de archivo (s?) del archivo db. Luego, otro proceso puede eliminar, eliminar o usar el archivo db.
Prueba esto ... este intenta todos los códigos anteriores ... funcionó para mí
Reader.Close()
connection.Close()
GC.Collect()
GC.WaitForPendingFinalizers()
command.Dispose()
SQLite.SQLiteConnection.ClearAllPools()
Espero que ayude
Quizás no necesites lidiar con GC en absoluto. Por favor, compruebe si todo sqlite3_prepare
está finalizado.
Para cada sqlite3_prepare
, necesita un correspondiente sqlite3_finalize
.
Si no finaliza correctamente, sqlite3_close
no cerrará la conexión.
Solo GC.Collect()
no funcionó para mí.
Tuve que agregar GC.WaitForPendingFinalizers()
después de GC.Collect()
para continuar con la eliminación del archivo.
Tuve un problema similar, aunque la solución del recolector de basura no lo solucionó.
La eliminación de los objetos SQLiteCommand
y SQLiteDataReader
después del uso me ha salvado usando el recolector de elementos no utilizados.
SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
Tuve un problema similar. Llamar a Garbage Collector no me ayudó. Después encontré una manera de resolver el problema
El autor también escribió que hizo SELECCIONAR consultas a esa base de datos antes de tratar de eliminarla. 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 tuve que hacer es cerrar el lector que se creó al ejecutar la consulta SELECT
Use GC.WaitForPendingFinalizers()
Ejemplo:
Con.Close();
GC.Collect();`
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "//DATABASENAME.DB");
Waiting for Garbage Collector puede no lanzar la base de datos todo el tiempo y eso me sucedió a mí. Cuando se produce algún tipo de excepción en la base de datos SQLite, por ejemplo, al intentar insertar una fila con el valor existente para PrimaryKey, se mantendrá el archivo de la base de datos hasta que se deshaga de él. El siguiente código capta 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 los comandos problemáticos, Garbage Collector no puede hacer nada al respecto porque hay algunas excepciones no controladas sobre estos comandos, por lo que no son basura. Este método de manejo funcionó bien para mí al esperar al recolector de basura.