savePoint недопустим и должен быть результатом вызова SaveTransactionPoint
database.RunInTransaction(() =>
{
if (dbVersion < DatabaseConstants.DATABASE_VERSION)
{
OnUpgrade();
}
});
database.Commit();
}
public void onUpgrade(){
//inserting list of person
database.RunInTransaction(() =>
{
//insert(TableNamePerson,PersonData)
});
database.Commit();
database.RunInTransaction(() =>
{
//insert(TableNameContacts,ContactData)
});
database.Commit();
}
При использовании вложенных транзакций я получаю следующее исключение:
savePoint недопустим и должен быть результатом вызова SaveTransactionPoint
1 ответ
Из того, что я вижу в документации и коде, это может быть предполагаемое поведение. Action
указано для 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");
}
}