使用Massive和SQLite时出现“数据库已锁定”异常

3

最近我在浏览微型ORM,我喜欢Massive的SQLite版本(链接),因为它很简单。但是现在我遇到了一个问题。

我只是运行一些select语句,然后执行一个update语句,但是我遇到了一个异常。以下是我的代码:

 var tbl = new Cust();
            var customers = tbl.All(where: "CustomerID > @0", orderBy: "FirstName", columns: "CustomerID,FirstName", args: 4);
            var firstCustomerName= customers.First().FirstName;

            var c = tbl.Update(new { FirstName = "Updated2" }, 4); //Exception is here!

            //Same happens even when using another object
            //var tbl2 = new Cust();
            //tbl2.Update(new { FirstName = "UpdatedName" }, 4);//Exception is here!

异常消息是:"Database is locked",出现在 Massive.SQLite 源代码中以下方法中。
public virtual int Execute(IEnumerable<DbCommand> commands)
{
       var result = 0;
            using (var conn = OpenConnection())
            {
                using (var tx = conn.BeginTransaction())
                {
                    foreach (var cmd in commands)
                    {
                        cmd.Connection = conn;
                        cmd.Transaction = tx;
                        result += cmd.ExecuteNonQuery();
                    }
                    tx.Commit();//Here is the Exception!
                }
            }
            return result;     
}

当我查看 Massive.SQLite 源代码时,我发现 massive 从不关闭连接,而是依赖 using 语句来释放连接对象,就像上面的代码所示。

上面代码中的 OpenConnection() 是一个方法,每次调用都会返回一个新的连接。

 public virtual DbConnection OpenConnection()
 {
            var result = _factory.CreateConnection();
            result.ConnectionString = ConnectionString;
            result.Open();
            return result;
 }

如果问题在于Massive没有关闭连接,并且根据这个SO问题,Sqlite不擅长并发连接,我应该关闭它,但是我无法访问连接,那我该如何关闭它呢?

我想听听使用Massive和SQLite的开发人员的最佳实践。


除非打开事务,否则SQLite不应该持有锁。有什么正在持有事务吗?为什么? - Donal Fellows
Massive中的SELECT语句不使用事务,但似乎这个语句"var firstCustomerName= customers.First().FirstName;"会锁定数据库,导致后续的UPDATE无法工作; var c = tbl.Update(new { FirstName = "Updated2" }, 4); - antew
1个回答

4

SQlite喜欢有一个单一的打开连接。

Massive正确地管理了连接,但它在查询方法中保留了ExecuteReader的“打开”状态,这可能会引起问题:

Robert Simpson写道:

保留读取器可能会导致问题。这些不会被清理, 直到懒惰的垃圾回收器处理它们。无论如何,至少在读取器周围使用using()语句肯定是更好的选择。以下对象使用非托管资源,垃圾回收器将懒于清理:

SQLiteCommand、SQLiteConnection、SQLiteDataReader,以及如果我记得正确的话,可能还有SQLiteTransaction。

因此,在Query方法中的ExecuteReader()周围放置using,它应该可以正常工作:

public virtual IEnumerable<dynamic> Query(string sql, params object[] args)
{
    using (var conn = OpenConnection())
    {
        using (var rdr = CreateCommand(sql, conn, args).ExecuteReader())
        {
            while (rdr.Read())
            {
                yield return rdr.RecordToExpando(); ;
            }
        }
    }
}

以下是一些笔记和解决方法,不需要更改 Massive 源代码:

  • you can enable connection pooling in SQLite with Pooling setting:

    connectionString="Data Source=test.db;Version=3;Pooling=True;Max Pool Size=100;"
    
  • the Query usually works right if it the reads all the data from the reader. But you used First() which combined with the yield return left the reader open. So if you evaluate the query with ToArray() it will also work:

    var firstCustomerName= customers.ToArray().First().FirstName;
    

非常感谢,我已经有点发现yield return是问题所在(保持连接打开),但你向我确认了它! - antew

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