即使在连接关闭后,SQLite也会使数据库保持锁定状态

时间:2012-09-21 14:40:02

标签: c# sqlite system.data.sqlite

我在ASP.NET应用程序(框架4.0)中使用System.Data.SQLite提供程序。 我遇到的问题是,当我在SQLite数据库的表中插入某些内容时,数据库会被锁定,即使在连接处理后锁也没有被释放。

尝试访问该文件时,错误是:“进程无法访问文件'catalog.sqlite',因为它正由另一个进程使用。”

我的代码非常简单,我打开连接,从SQLServer数据库读取一些数据,将数据插入SQLite(通过SQLiteDataAdapter),然后关闭连接并处置所有内容以保证安全。但是,当我在填充数据后尝试压缩文件时,我得到了这个错误。

我已经在StackOverflow上阅读了所有类型的建议,但没有一个能帮助解决问题(关闭防病毒软件,更改事务模型,在压缩文件之前等待几秒钟,将所有插入调用包装到交易等等。但没有人帮助解决这个问题。

也许有一些特定于ASP.NET的东西(多线程是问题?即使我在开发机器上测试它,只有一次调用该函数而没有并发?)

作为旁注,我尝试避免使用DataTable和SQLiteDataAdapter,并且直接使用SQLiteCommand,这样就可以实现魅力。当然,我可以继续将查询构建为字符串而不是使用数据适配器,但是当我为此构建一个框架时,我发现它有点尴尬。

12 个答案:

答案 0 :(得分:35)

使用随System.Data.Sqlite.dll版本1.0.82.0附带的设计器生成的数据集/ tableadapter,我遇到了同样的问题 - 关闭连接后,我们无法使用System.IO.FileStream读取数据库文件。我正确处理了连接和tableadapter,我没有使用连接池。

根据我的第一次搜索(例如thisthis thread),这似乎是库本身的问题 - 对象未正确发布和/或汇集问题(我不使用)。

在阅读完问题之后,我尝试仅使用SQLiteCommand对象来复制问题,并且我发现当您不处理它们时会出现问题。 更新2012-11-27 19:37 UTC this ticket对System.Data.SQLite进一步确认了这一点,其中开发人员解释说“ all 与连接关联的SQLiteCommand和SQLiteDataReader对象[应该]正确处理“。

然后我转回生成的TableAdapters,我发现没有Dispose方法的实现 - 所以实际上创建的命令没有被处理掉。我实现了它,负责处理所有命令,我没有问题。

这是C#中的代码,希望这会有所帮助。请注意,代码是从original in Visual Basic转换而来的,因此需要一些转换错误。

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

更新2013-07-05 17:36 UTC gorogm's answer突出了两件重要的事情:

  • 根据System.Data.SQLite官方网站上的changelog,从版本1.0.84.0开始,不需要上面的代码,因为库负责处理这个问题。我没有测试过这个,但在最坏的情况下你只需要这个片段:

    //In Table Adapter    
    protected override void Dispose(bool disposing)
    {
      base.Dispose(disposing);
    
      this.Adapter.Dispose();
    }
    
  • 关于Dispose的{​​{1}}调用的实现:最好将它放在一个部分类中,以便数据集重新生成不会影响此代码(以及任何您可能需要添加的其他代码)。

答案 1 :(得分:24)

我有同样的问题。我的方案是在获取SQLite数据库文件中的数据之后我想要删除该文件,但它总是引发错误“ ......使用其他进程 ”。即使我处理SqliteConnection或SqliteCommand仍然会发生错误。我通过调用GC.Collect()修正了错误。

代码段

public void DisposeSQLite()
{
    SQLiteConnection.Dispose();
    SQLiteCommand.Dispose();

    GC.Collect();
}

希望得到这个帮助。

答案 2 :(得分:9)

就我而言,我正在创建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)
    {
        // Added using
        using (var command = connection.CreateCommand())
        {
            command.CommandText = commandText;
            return command.ExecuteScalar();
        }
    }
}

然后你可以像这样使用它

connection.ExecuteScalar(commandText);

答案 3 :(得分:6)

以下对我有用: MySQLiteConnection.Close(); SQLite.SQLiteConnection.ClearAllPools()

答案 4 :(得分:2)

在大多数情况下,如果您没有妥善处理读者和命令,就会出现问题。在某种情况下,命令和读者将无法妥善处理。

场景1:如果您正在运行布尔函数。在到达结果之前,finally块中的代码不会超出。如果要在执行代码时评估函数isDataExists的结果,如果它适合结果,那么这是一个大问题,即

    if(isDataExists){
        // execute some code
    }

正在评估的功能

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

解决方案:将您的阅读器和命令放在try块中,如下所示

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

最后处理读者和命令以防万一出错

        finally
        {
            if (reader != null)
            {
                reader.Dispose();
            }
            CloseConnection();
        }

答案 5 :(得分:1)

我发现edymtt的答案是关于责备TableAdapters / Datasets,但是我没有修改每次重新生成的TableAdapter代码文件,而是找到了另一个解决方案:在TableAdapter的子代上手动调用.Dispose元素。 (在.NET 4.5中,最新的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());
}

答案 6 :(得分:1)

如前所述,必须销毁SQLite对象。但是,有一种奇怪的行为:在调用Dispose on命令期间必须打开连接。例如:

using(var connection = new SqliteConnection("source.db"))
{
    connection.Open();
    using(var command = connection.CreateCommand("select..."))
    {
        command.Execute...
    }
}

工作正常,但是:

using(var connection = new SqliteConnection("source.db"))
{
    connection.Open();
    using(var command = connection.CreateCommand("select..."))
    {
        command.Execute...
        connection.Close();
    }
}

给出相同的文件锁

答案 7 :(得分:0)

这是我在遇到此错误时找到的google搜索结果之一。但是,没有一个回复帮助了我,所以经过更多的搜索和谷歌搜索后,我想出了这个代码来自http://www.tsjensen.com/blog/post/2012/11/10/SQLite-on-Visual-Studio-with-NuGet-and-Easy-Instructions.aspx的一些代码

但是,我根本不必使用NuGet。我的程序所做的是每次打开时从服务器下载一个db文件。然后,如果用户更新该数据库,它将被上传给每个人,以便下次他们打开相同的程序。在更新本地文件并尝试将其上载到SharePoint后,我收到了文件正在使用的错误。现在它工作正常。

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

答案 8 :(得分:0)

确保正确处理任何IDisposable(例如,SQLiteConnection,SQLiteCommand等)可以解决此问题。我应该重申,必须使用“使用”作为习惯,以确保妥善处置一次性资源。

答案 9 :(得分:0)

我遇到了同样的问题而且只是通过在Locale locale = new Locale("zh","TW"); 声明中处理DbCommand来解决问题,但是using我的问题已得到修复!!

Pooling = true

答案 10 :(得分:0)

当我锁定计算机时,我只是遇到了这里提到的问题,即使在解锁之后,工作正常,否则很幸运,因为我刚刚开始锁定它并且软件刚刚在几天前发布,然后才有人知道它。

无论如何,我有所有的东西,如关闭连接和ClearAllPools等,但缺少aTableAdapter.Adapter.Dispose()并修复它。

答案 11 :(得分:0)

有同样的问题。 只需安装SQLite 1.0.111(通过nuget)即可解决。

不必更改我的代码,只需更新到该版本即可。以前,我使用1.0.85锁定了文件,尽管连接已关闭并被清除。