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

时间:2011-12-14 21:28:42

标签: sqlite system.data.sqlite

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

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

删除会抛出文件仍在使用中的异常。几分钟后我在调试器中重新尝试了Delete(),所以这不是时间问题。

我有事务代码但在Close()调用之前根本没有运行。所以我很确定这不是一个开放的交易。打开和关闭之间的sql命令只是选择。

ProcMon显示我的程序和我的防病毒软件查看数据库文件。它没有显示我的程序在close()之后释放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);

17 个答案:

答案 0 :(得分:86)

在为C#编写数据库抽象层时,前一段时间遇到了同样的问题,我从未真正找到问题所在。当您尝试使用我的库删除SQLite数据库时,我刚刚抛出异常。

无论如何,今天下午我再次仔细研究了这一切,并想我会试着找出为什么一劳永逸地做到这一点,所以这就是我迄今为止所发现的。

当您调用SQLiteConnection.Close()时会发生什么(以及一些检查和其他事情)指向SQLite数据库实例的SQLiteConnectionHandle被释放。这是通过调用SQLiteConnectionHandle.Dispose()来完成的,但是在CLR的垃圾收集器执行一些垃圾收集之前,这实际上不会释放指针。由于SQLiteConnectionHandle会覆盖CriticalHandle.ReleaseHandle()函数以调用sqlite3_close_interop()(通过其他函数),因此不会关闭数据库。

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

<强> TL; DR 解决方案是在致电SQLiteConnection.Close()之后和致电File.Delete()之前强制使用GC。

以下是示例代码:

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

祝你好运,我希望它有所帮助

答案 1 :(得分:54)

只是GC.Collect()对我不起作用。

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

答案 2 :(得分:15)

就我而言,我正在创建SQLiteCommand个对象,而没有明确处理它们。

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

我在using语句中包含了我的命令,这解决了我的问题。

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

答案 3 :(得分:11)

有一个类似的问题,虽然垃圾收集器解决方案没有解决它。

在使用后发现处理SQLiteCommandSQLiteDataReader对象完全使用垃圾收集器保存了我。

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

答案 4 :(得分:10)

以下对我有用:

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

更多信息: 连接由SQLite汇集以提高性能。这意味着当您在连接对象上调用Close方法时,与数据库的连接可能仍然存在(在后台),以便下一个Open方法变得更快。当您知道您不知道时我想再建一个新的连接,调用ClearAllPools会关闭所有在后台处于活动状态的连接,并且db文件的文件句柄(s?)会被释放。然后另一个进程可能会删除,删除或使用db文件。

答案 5 :(得分:9)

我遇到了类似的问题,我已尝试使用GC.Collect解决方案,但如上所述,文件未锁定可能需要很长时间。

我找到了一个替代解决方案,涉及处理TableAdapters中的基础SQLiteCommand,有关其他信息,请参阅this answer

答案 6 :(得分:3)

试试这个...这个尝试以上所有代码 ...为我工作

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

希望有所帮助

答案 7 :(得分:3)

使用GC.WaitForPendingFinalizers()

示例:

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

答案 8 :(得分:2)

我和EF System.Data.Sqlite一直有同样的问题。

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

我一直在调查,似乎EF创建的某些SQLiteCommand没有被处理,并且仍然将其Connection属性设置为已关闭的连接。我尝试处理这些,但实体框架会在下一次DbContext读取期间抛出异常 - 似乎EF在连接关闭后仍然会使用它们。

我的解决方案是确保在这些Null上的连接关闭时,Connection属性设置为SQLiteCommand。这似乎足以释放文件锁。我一直在测试下面的代码,经过几千次测试后没有看到任何文件锁定问题:

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

在应用程序加载开始时只使用ClearSQLiteCommandConnectionHelper.Initialise();。 然后,这将保留一个活动命令列表,并在指向已关闭的连接时将其连接设置为Null

答案 9 :(得分:2)

也许你根本不需要处理GC。请检查是否所有sqlite3_prepare都已完成。

对于每个sqlite3_prepare,您需要一位通讯员sqlite3_finalize

如果您没有正确完成,sqlite3_close将不会关闭连接。

答案 10 :(得分:2)

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

作者还写道,在尝试删除之前,他对该数据库执行了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查询时创建的阅读器

答案 11 :(得分:1)

我相信对SQLite.SQLiteConnection.ClearAllPools()的呼吁是最干净的解决方案。据我所知,在WPF环境中手动调用GC.Collect()是不合适的。虽然,直到我在2016年3月升级到System.Data.SQLite 1.0.99.0

,我才注意到这个问题

答案 12 :(得分:1)

我正在努力解决类似的问题。对我感到羞耻...我终于意识到读者没有关闭。出于某种原因,我认为当相应的连接关闭时,Reader将被关闭。显然,GC.Collect()并不适合我 使用&#34;使用:语句包装Reader也是一个好主意。这是一个快速测试代码。

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

答案 13 :(得分:0)

对我有用的

最佳答案。

 public void cardClicked(final  Sport  currentSport){
            itemCard.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                        Intent intent = new Intent(context, DetailActivity.class);
                        intent.putExtra("Image",R.drawable.img_baseball);
                        intent.putExtra("Name",currentSport.getTitle());

                        context.startActivity(intent);
                    }
            });


        }

答案 14 :(得分:0)

这对我有用,但是我注意到有时关闭进程时,日记文件-wal -shm不会被删除。如果您希望SQLite在所有连接均关闭时删除-wal -shm文件,则最后关闭的连接必须为非只读。希望这会帮助某人。

答案 15 :(得分:0)

等待垃圾收集器可能无法一直释放数据库并且发生在我身上。当在SQLite数据库中发生某种类型的异常时,例如尝试插入具有PrimaryKey现有值的行时,它将保留数据库文件,直到您将其处置为止。以下代码捕获SQLite异常并取消有问题的命令。

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

如果你没有处理有问题的命令的异常,那么垃圾收集器就无法对它们做任何事情,因为这些命令有一些未处理的例外,因此它们不是垃圾。这种处理方法对我来说效果很好,等待垃圾收集器。

答案 16 :(得分:0)

我在使用带有EF6的SQLite 1.0.101.0时,在所有连接和实体都被处理后,文件被锁定时出现问题。

更新来自EF的更新使数据库在完成后保持锁定状态。 GC.Collect()是唯一有帮助的解决方法,我开始绝望了。

无奈之下,我尝试了Oliver Wickenden的ClearSQLiteCommandConnectionHelper(见他7月8日的回答)。太棒了。所有锁定问题都没了! 谢谢奥利弗。