System.Data.SQLite Close()未释放数据库文件

System.Data.SQLite Close()未释放数据库文件,sqlite,system.data.sqlite,Sqlite,System.data.sqlite,我在尝试删除文件之前关闭数据库时遇到问题。代码只是 myconnection.Close(); File.Delete(filename); 并且Delete抛出一个异常,该文件仍在使用中。几分钟后,我在调试器中重新尝试了Delete(),因此这不是时间问题 我有事务代码,但在Close()调用之前它根本不运行。所以我相当肯定这不是一笔公开交易。打开和关闭之间的sql命令只是选择 ProcMon显示我的程序和我的防病毒程序,查看数据库文件。它不显示我的程序在关闭()后释放db文件

我在尝试删除文件之前关闭数据库时遇到问题。代码只是

 myconnection.Close();    
 File.Delete(filename);
并且Delete抛出一个异常,该文件仍在使用中。几分钟后,我在调试器中重新尝试了Delete(),因此这不是时间问题

我有事务代码,但在Close()调用之前它根本不运行。所以我相当肯定这不是一笔公开交易。打开和关闭之间的sql命令只是选择

ProcMon显示我的程序和我的防病毒程序,查看数据库文件。它不显示我的程序在关闭()后释放db文件

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

我看到了一个两年前的bug,就像这样,但是变更日志说它已经修复了

还有什么我可以查的吗?有没有办法获取任何打开的命令或事务的列表


新的工作代码:

 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);
db.Close();
GC.Collect();//是的,真的释放数据库
布尔工作=假;
int=1;
而((尝试<4)和(!工作))
{
尝试
{
线程。睡眠(尝试*100);
删除(文件名);
工作=真实;
}
catch(ioe异常)//delete仅在锁定时抛出此
{
尝试++;
}
}
如果(!工作)
抛出新IOException(“无法关闭文件”+文件名);

不久前在为C#编写DB抽象层时遇到了同样的问题,而我从来没有真正找到问题所在。当您试图使用我的库删除SQLite DB时,我抛出了一个异常

不管怎样,今天下午我又看了一遍,我想我会努力找出它为什么要这样做,这就是我迄今为止的发现

调用
SQLiteConnection.Close()
时发生的情况是,指向SQLite数据库实例的
SQLiteConnectionHandle
被释放(以及一些检查和其他事情)。这是通过调用
SQLiteConnectionHandle.Dispose()
完成的,但是在CLR的垃圾收集器执行一些垃圾收集之前,这实际上不会释放指针。由于
SQLiteConnectionHandle
重写
CriticalHandle.ReleaseHandle()
函数调用
sqlite3\u close\u interop()
(通过另一个函数),因此不会关闭数据库

从我的观点来看,这是一种非常糟糕的方法,因为程序员实际上不确定数据库何时关闭,但这就是它的方式,所以我想我们现在必须接受它,或者对System.Data.SQLite进行一些更改。欢迎任何志愿者参加,不幸的是,我没有时间在明年之前参加

TL;DR 解决方案是在调用
SQLiteConnection.Close()
之后和调用
File.Delete()之前强制执行GC

以下是示例代码:


祝您好运,我希望这会有所帮助。我遇到了类似的问题,我尝试了使用
GC.Collect
的解决方案,但是,如前所述,文件未锁定可能需要很长时间


我找到了一个替代解决方案,涉及在TableAdapters中处理底层的
SQLiteCommand
s,请参阅以获取更多信息。

在我的例子中,我创建
SQLiteCommand
对象时没有显式地处理它们

var command = connection.CreateCommand();
command.CommandText = commandText;
value = command.ExecuteScalar();
我将命令包装在一个语句中,它解决了我的问题

static public class SqliteExtensions
{
    public static object ExecuteScalar(this SQLiteConnection connection, string commandText)
    {
        using (var command = connection.CreateCommand())
        {
            command.CommandText = commandText;
            return command.ExecuteScalar();
        }
    }
}
using
语句确保即使发生异常也调用Dispose

这样执行命令就容易多了

value = connection.ExecuteScalar(commandText)
// Command object created and disposed
只是
GC.Collect()
对我不起作用


我必须在
GC.Collect()
之后添加
GC.WaitForPendingFinalizers()
,才能继续删除文件。

以下操作对我有效:

MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()
更多信息
SQLite对连接进行池化以提高性能。这意味着当您对连接对象调用Close方法时,到数据库的连接可能仍然处于活动状态(在后台),以便下一个打开的方法变得更快。当您知道不再需要新连接时,调用ClearAllPools会关闭后台所有活动的连接,并释放到db文件的文件句柄。然后,db文件可能会被删除、删除或被另一个进程使用。

也有类似的问题,尽管垃圾收集器解决方案没有修复它

在使用垃圾回收器保存我之后,发现正在处理
SQLiteCommand
SQLiteDataReader
对象

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

我相信调用
SQLite.SQLiteConnection.ClearAllPools()
是最干净的解决方案。据我所知,在WPF环境中手动调用
GC.Collect()
是不合适的。尽管如此,直到我在2016年3月升级到
System.Data.SQLite
1.0.99.0,我才注意到这个问题我在EF和
System.Data.SQLite上也遇到了同样的问题

对我来说,我发现
SQLiteConnection.clearlallpools()
GC.Collect()
会减少文件锁定的发生频率,但偶尔也会发生(大约1%的时间)

我一直在调查,似乎EF创建的一些
SQLiteCommand
s没有被释放,仍然将它们的连接属性设置为closed Connection。我试着处理这些,但实体框架会在下一次
DbContext
read期间抛出一个异常-似乎EF有时在连接关闭后仍然使用它们

我的解决方案是确保当连接在这些
SQLiteCommand
s上关闭时,Connection属性设置为
Null
。这似乎足以释放文件锁。我已经
SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
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);
                }
            }
        }
    }
}
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();
        }
    }   
}
    Reader.Close()
    connection.Close()
    GC.Collect()
    GC.WaitForPendingFinalizers()
    command.Dispose()
    SQLite.SQLiteConnection.ClearAllPools()
Con.Close();  
GC.Collect();`
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
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.
SQLiteCommand insertCommand = connection.CreateCommand();
try {
    // some insert parameters
    insertCommand.ExecuteNonQuery();
} catch (SQLiteException exception) {
    insertCommand.Cancel();
    insertCommand.Dispose();
}
dbConnection.Close();
System.Data.SQLite.SQLiteConnection.ClearAllPools();

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

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