从我在文档和代码中看到的内容来看,这可能是预期的行为。Action
指定的 for整体RunInTransaction
封装在事务中。由于 SQLite 一次只支持一个事务,如果不存在则创建一个新事务,否则在已经运行的事务中创建一个保存点。
也就是说,您不必调用 commit,也不允许这样做。RunInTransaction
会照顾你的。这适用于一个块,但与代码中的嵌套块有一些不一致的行为RunInTransaction
。我会认为这是一个错误。
如果发生异常,则整个事务将回滚并因此终止。这意味着即使是那些成功完成的块也会被回滚!显式或隐式之后的每个语句BEGIN
都消失了。即使您捕获了内部异常,所有外部RunInTransaction
块也会抛出 ArgumentExceptions:
savePoint is not valid, and should be the result of a call to SaveTransactionPoint
如果您想拥有更多控制权,则需要使用显式保存点而RollbackTo
不是Rollback
.
我创建了一个单元测试来说明会发生什么:
[Test]
public void InsertItemIntoTable()
{
using (var connection = Container.Resolve<IDatabase>().GetConnection())
{
var item1 = new Item { Id = 1, Description = "Test 1", Text = "Text for test 1" };
var item2 = new Item { Id = 2, Description = "Test 2", Text = "Text for test 2" };
var countAtStart = connection.Query<Item>("SELECT * FROM Item").Count;
connection.RunInTransaction(() => // transaction started
{
var saveTransactionPoint = connection.SaveTransactionPoint();
connection.Insert(item2);
// would fail as a commit would finish the transaction inside the action:
// connection.Commit();
// works as the transaction does not yet end
connection.RollbackTo(saveTransactionPoint);
Assert.IsTrue(connection.IsInTransaction);
});
Assert.IsFalse(connection.IsInTransaction);
try
{
connection.RunInTransaction(() => // transaction started
{
connection.Insert(item1);
var countAfter1stInsert = connection.Query<Item>("SELECT * FROM Item").Count;
Assert.AreEqual(countAtStart + 1, countAfter1stInsert);
connection.RunInTransaction(() => { connection.Insert(item2); });
var countAfter2ndInsert = connection.Query<Item>("SELECT * FROM Item").Count;
Assert.AreEqual(countAtStart + 2, countAfter2ndInsert);
// bad SQL statement provokes an exception: no such table: bar.
try
{
connection.RunInTransaction(() => // new save point within running transaction
{
connection.Execute("SELECT foo FROM bar", "will throw exception");
});
}
catch (Exception e)
{
// the whole transaction was rolled back already
Assert.IsFalse(connection.IsInTransaction);
// that is why the outer block will fail next
}
});
}
catch (Exception e)
{
// outer RunInTransaction could not release its own save point and crashes with:
// "savePoint is not valid, and should be the result of a call to SaveTransactionPoint"
}
var countAfterRollback = connection.Query<Item>("SELECT * FROM Item").Count;
Assert.AreEqual(countAtStart, countAfterRollback);
Assert.IsFalse(connection.IsInTransaction);
// new transaction point start a deferred transaction as no transaction is running
var point1 = connection.SaveTransactionPoint();
Assert.IsTrue(connection.IsInTransaction);
connection.Insert(item1);
var point2 = connection.SaveTransactionPoint();
connection.Insert(item2);
var point3 = connection.SaveTransactionPoint();
connection.Execute("INSERT INTO 'Item'('Id','Text','Description') VALUES (100,'Test 100',NULL);");
connection.RollbackTo(point3);
connection.RollbackTo(point2);
// will commit the first insert i.e. item1, which implictily began a transaction
connection.Commit();
Assert.IsFalse(connection.IsInTransaction);
var afterFinalRollback = connection.Query<Item>("SELECT * FROM Item").Count;
// thus item1 has made it to the database
Assert.AreEqual(countAtStart + 1, afterFinalRollback);
// but not for ever ;)
connection.Execute("delete from item where id > 0");
}
}