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

}
Другие вопросы по тегам