ドキュメントとコードで見たものから、意図した動作である可能性があります。Action
指定された forはRunInTransaction
、トランザクション内に全体としてカプセル化されます。SQLite は一度に 1 つのトランザクションしかサポートしないため、トランザクションが存在しない場合は新しいトランザクションが作成され、そうでない場合は、既に実行中のトランザクション内にセーブ ポイントが作成されます。
つまり、コミットを呼び出す必要はなく、それを行うことは許可されていません。RunInTransaction
お世話になります。これは 1 つのブロックでは正常に機能しますが、コードのようにネストされたブロックでは一貫性のない動作が発生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");
}
}