c# - SQLite mantiene la base de datos bloqueada incluso después de que se cierra la conexión
system.data.sqlite (11)
Estoy usando el proveedor de System.Data.SQLite en una aplicación ASP.NET (framework 4.0). El problema al que me estoy enfrentando es que cuando INSERTO algo en una tabla en la base de datos SQLite, la base de datos se bloquea y el bloqueo no se libera incluso después de que se elimina la conexión.
Al intentar acceder al archivo, el error es: "El proceso no puede acceder al archivo ''catalog.sqlite'' porque está siendo utilizado por otro proceso".
Mi código es bastante sencillo, abro la conexión, leo datos de una base de datos SQLServer, inserto esos datos en SQLite (a través de SQLiteDataAdapter) y luego cierro la conexión y dispongo todo para estar seguro. Pero aún así, obtengo ese error cuando intento comprimir el archivo una vez que se ha completado con los datos.
He leído todo tipo de sugerencias aquí en StackOverflow, pero ninguna de ellas me ha ayudado a resolver el problema (apagar el antivirus, cambiar el modelo de transacción, esperar unos segundos antes de subir el archivo, incluir todas las llamadas de inserción en una transacción, etc., pero ninguno ha ayudado a resolver este problema.
Quizás haya algo específico para ASP.NET (¿el problema es el de los subprocesos múltiples?) Aunque lo estoy probando en una máquina de desarrollo donde solo hay una llamada a esa función y no concurrencia?
Como nota al margen, intenté evitar DataTable y SQLiteDataAdapter y usar solo SQLiteCommand directamente y de esa manera funciona un hechizo. Por supuesto, puedo seguir creando mis consultas como cadenas en lugar de utilizar los adaptadores de datos, pero me resulta un poco incómodo cuando se construye un marco para hacerlo.
Como se dijo, los objetos SQLite anteriores deben ser destruidos. Sin embargo, hay un comportamiento extraño: la conexión debe estar abierta durante una llamada Dispose en los comandos. Por ejemplo:
using(var connection = new SqliteConnection("source.db"))
{
connection.Open();
using(var command = connection.CreateCommand("select..."))
{
command.Execute...
}
}
funciona bien, pero:
using(var connection = new SqliteConnection("source.db"))
{
connection.Open();
using(var command = connection.CreateCommand("select..."))
{
command.Execute...
connection.Close();
}
}
da el mismo bloqueo de archivo
En la mayoría de los casos, surgirá el problema si no dispone de sus lectores y comandos correctamente. Hay un escenario en el que los comandos y lectores no se desharán correctamente.
Escenario 1: en caso de que esté ejecutando una función booleana . antes de llegar a un resultado, el código en el bloque finally no se ejecutará. Este es un gran problema si va a evaluar los resultados de la función isDataExists mientras ejecuta el código si le conviene el resultado, es decir,
if(isDataExists){
// execute some code
}
La función que se evalúa
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;
}
Solución: elimine su lector y comando dentro del bloque try de la siguiente manera
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;
}
Finalmente disponga el lector y el comando por si acaso algo salió mal
finally
{
if (reader != null)
{
reader.Dispose();
}
CloseConnection();
}
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)
{
// Added using
using (var command = connection.CreateCommand())
{
command.CommandText = commandText;
return command.ExecuteScalar();
}
}
}
Entonces puedes usarlo así
connection.ExecuteScalar(commandText);
Encontré la respuesta de edymtt sobre culpar a TableAdapters / Datasets, pero en vez de modificar cada vez que volví a generar el archivo de código TableAdapter, encontré otra solución: llamar manualmente. Desinciar en los elementos hijos de TableAdapter. (En .NET 4.5, último 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());
}
Este fue uno de los principales resultados de Google que encontré cuando me encontré con este error. Sin embargo, ninguna de las respuestas me ayudó, así que después de buscar más y buscar en Google se me ocurrió este código que funciona a partir de algunos de los códigos de http://www.tsjensen.com/blog/post/2012/11/10/SQLite-on-Visual-Studio-with-NuGet-and-Easy-Instructions.aspx
Sin embargo, no tuve que usar NuGet en absoluto. Lo que hace mi programa es descargar un archivo db de un servidor cada vez que se abre. Entonces, si un usuario actualiza ese db, se cargará para que todos lo puedan obtener la próxima vez que abran el mismo programa. Recibí el error de que el archivo estaba en uso después de actualizar el archivo local y de intentar cargarlo en nuestro SharePoint. Ahora funciona 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
Garantizar que cualquier IDisposable (por ejemplo, SQLiteConnection, SQLiteCommand, etc.) se elimine adecuadamente resuelve este problema. Debo repetir que uno debe usar "usar" como un hábito para asegurar la eliminación adecuada de los recursos desechables.
Lo siguiente funcionó para mí: MySQLiteConnection.Close(); SQLite.SQLiteConnection.ClearAllPools()
MySQLiteConnection.Close(); SQLite.SQLiteConnection.ClearAllPools()
Solo estaba teniendo los problemas mencionados aquí cuando bloqueaba la computadora, incluso después de desbloquearla, funcionaba bien, de lo contrario, afortunada, porque recién comencé a bloquearla últimamente y el software que acaba de lanzar hace unos días antes de que nadie lo sepa.
De todos modos, tenía todas las cosas como cerrar las conexiones y ClearAllPools, etc. pero me faltaba unTableAdapter.Adapter.Dispose () y eso lo solucionó.
Tengo el mismo problema. Mi escenario fue después de obtener los datos dentro del archivo de la base de datos SQLite. Quiero eliminar ese archivo, pero siempre arroja un error " ... utilizando otro proceso ". Incluso dispongo de SqliteConnection o SqliteCommand, el error aún ocurre. GC.Collect()
el error llamando a GC.Collect()
.
Fragmento de código
public void DisposeSQLite()
{
SQLiteConnection.Dispose();
SQLiteCommand.Dispose();
GC.Collect();
}
Espero que esto ayude.
Tuve el mismo problema y solo se corrigió desechando el DbCommand
en la instrucción using
, pero con Pooling = true
mi problema se solucionó.
SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder
{
Pooling = true
};
Tuve el mismo problema al usar datasets / tableadapters generados con el diseñador incluido con System.Data.Sqlite.dll
versión 1.0.82.0 - después de cerrar la conexión no pudimos leer el archivo de base de datos usando System.IO.FileStream
. Estaba eliminando correctamente tanto la conexión como los adaptadores de tabla y no estaba usando la agrupación de conexiones.
De acuerdo con mis primeras búsquedas (por ejemplo, this y este hilo ) que parecían un problema en la biblioteca en sí, ya sea objetos no lanzados correctamente y / o problemas de agrupamiento (que no uso).
Después de leer su pregunta intenté replicar el problema utilizando solo objetos SQLiteCommand y encontré que el problema surge cuando no los elimina. Actualización 2012-11-27 19:37 UTC : esto se confirma con este ticket para System.Data.SQLite, en el que un desarrollador explica que " todos los objetos SQLiteCommand y SQLiteDataReader asociados con la conexión [deberían estar] correctamente eliminados".
Luego volví a encender los TableAdapters generados y vi que no había ninguna implementación del método Dispose
, por lo que, de hecho, los comandos creados no se eliminaron. Lo implementé, teniendo cuidado de eliminar todos los comandos, y no tengo ningún problema.
Aquí está el código en C #, espero que esto ayude. Tenga en cuenta que el código se convierte desde el original en Visual Basic , así que espere algunos errores de conversión.
//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();
}
}
}
}
Actualización 2013-07-05 17:36 UTC La respuesta de gorogm destaca dos cosas importantes:
de acuerdo con el changelog de changelog en el sitio oficial de System.Data.SQLite, a partir de la versión 1.0.84.0, el código anterior no debería ser necesario, ya que la biblioteca se ocupa de esto. No he probado esto, pero en el peor de los casos solo necesita este fragmento:
//In Table Adapter protected override void Dispose(bool disposing) { base.Dispose(disposing); this.Adapter.Dispose(); }
acerca de la implementación de la llamada
Dispose
delTableAdapter
: es mejor poner esto en una clase parcial, de modo que la regeneración de un conjunto de datos no afecte a este código (y a cualquier código adicional que pueda necesitar agregar).