System.Data.SQLite的Close()方法不能释放数据库文件

125

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

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

执行Delete()方法时抛出了“文件仍在使用”的异常。我在调试器中等待几分钟后重新尝试了Delete()方法,因此这不是时间问题。

我有事务代码,但在Close()方法调用之前没有运行它。所以我相当确定它不是一个未关闭的事务。在Open()和Close()之间的SQL命令只是查询语句。

ProcMon显示我的程序和杀毒软件正在查看数据库文件。它没有显示我的程序在Close()方法之后释放数据库文件。

使用的工具为Visual Studio 2010,编程语言是C#,System.Data.SQLite版本为1.0.77.0,操作系统为Win7。

我看到了一个两年前的类似问题,但更新日志中说已经修复了。

还有其他我可以检查的吗?是否有一种方法可以获取所有打开的命令或事务的列表?


新的、可行的代码:

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

1
你尝试过以下代码吗: myconnection.Close(); myconnection.Dispose(); - Przemysław Michalski
1
使用 sqlite-net 时,您可以使用 SQLiteAsyncConnection.ResetPool(),有关详细信息,请参见此问题 - Uwe Keim
18个回答

4

最佳答案对我很有用。

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

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

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

3

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


3

我遇到了类似的问题。调用垃圾回收器没有帮助我。后来我找到了解决方法。

作者还写道,他在尝试删除数据库之前对该数据库进行了SELECT查询。我也有同样的情况。

我的代码如下:

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.

此外,我不需要关闭数据库连接或调用垃圾收集器。我所需要做的就是关闭执行SELECT查询时创建的读取器(reader)。

2
这对我有用,但我注意到有时候当进程关闭时,日志文件-wal-shm不会被删除。如果你希望SQLite在所有连接关闭时删除-wal-shm文件,则必须关闭最后一个连接且该连接不是只读的。希望这能帮助某些人。

更具体地说,似乎在打开标志ReadOnly | SharedCache的组合下,-wal和-shm文件将会保留。仅使用ReadOnly将允许其完全关闭(至少在我的情况下)。回想起来,这是有道理的,因为SQL驱动程序无法知道我们是否完成了共享缓存。 - Maxim Paperno
编辑:噫,现在还要看是否在同一数据库上使用了异步连接...如果是的话,那么仅仅将最后一个连接设置为“只读”(带或不带“共享缓存”)仍然会留下额外的文件...而ResetPool()也没有帮助。 - Maxim Paperno

2
也许您根本不需要处理GC。请检查所有sqlite3_prepare是否已完成。对于每个sqlite3_prepare,您需要一个相应的sqlite3_finalize。如果您没有正确完成,sqlite3_close将无法关闭连接。

2

我曾经也遇到过类似的问题。真是太丢脸了...我最终意识到 Reader 没有被关闭。出于某些原因,我一直认为在相应的连接关闭时,读取器会被关闭。显然,GC.Collect() 对我并没有起作用。
使用“using”语句包装读取器也是一个好主意。这是一个快速测试代码。

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

0

我使用EF6和SQLite 1.0.101.0时遇到了问题,即在所有连接和实体被处理后文件仍然被锁定。

随着EF的更新,这个问题变得更加严重,因为它们完成后数据库仍然被锁定。GC.Collect()是唯一有用的解决方法,但我已经开始绝望了。

在绝望中,我尝试了Oliver Wickenden的ClearSQLiteCommandConnectionHelper(请参见他7月8日的回答)。太棒了!所有锁定问题都解决了!感谢Oliver。


我认为这应该是一条评论,而不是一个答案。 - wake-0
1
Kevin,我同意你的观点,但是由于我的声望不足50分(显然),所以我无法发表评论。 - Tony Sullivan

0

等待垃圾收集器可能不会始终释放数据库,这种情况曾经发生在我身上。例如,在 SQLite 数据库中尝试插入具有现有主键值的行时,如果发生某些类型的异常,它将保留数据库文件直到您处理它。以下代码捕获 SQLite 异常并取消问题命令。

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

如果您不处理有问题的命令异常,垃圾收集器无法对它们做任何事情,因为这些命令存在一些未处理的异常,所以它们不是垃圾。这种处理方法在等待垃圾收集器方面对我很有效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接