Попытка понять TransactionScope - PullRequest
3 голосов
/ 24 апреля 2009

Я пытаюсь сделать быстрое фиктивное приложение, чтобы я мог изучить все тонкости System.Transactions. Это приложение взаимодействует с 2 различными базами данных SQLExpress. Если я получаю статистику транзакций в службах компонентов, я вижу транзакцию, запускаемую в externalScope, когда открывается второе соединение. Если значение failOuter равно true, транзакция прерывается, но не вызывает никаких исключений. Когда failInner имеет значение true, создается исключение TransactionAbortedException.

Из MSDN:

Когда ваше приложение завершает всю работу, которую оно хочет выполнить в транзакции, вы должны вызывать метод Complete только один раз, чтобы сообщить менеджеру транзакций о том, что транзакция приемлема для принятия. Это очень хорошая практика - помещать вызов Complete как последний оператор в блоке using.

Если этот метод не вызывается, транзакция прерывается, поскольку диспетчер транзакций интерпретирует это как системный сбой или эквивалент исключения, выданного в рамках транзакции.

Исключение TransactionAbortedException генерируется, если область создает транзакцию и транзакция отменяется.

Исходя из этого, я ожидаю, что мой externalScope сгенерирует исключение TransactionAbortedException, поскольку моя статистика транзакций показывает прерванную транзакцию каждый раз, когда я запускаю свое приложение, для которого failOuter имеет значение true. Мой метод возвращает true, так как никакие исключения не генерируются, даже если транзакция прерывается. Если я не прерву внутреннюю транзакцию, она будет вести себя так, как я ожидаю. Любое разъяснение будет наиболее ценно.

public bool CreateNestedTransaction(bool failOuter, bool failInner)
    {
        try
        {
            using (TransactionScope outerScope = new TransactionScope())
            {

                /* Perform transactional work here */
                using (SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=test1"))
                {
                    SqlCommand myCommand = new SqlCommand();
                    myConnection.Open();
                    myCommand.Connection = myConnection;

                    myCommand.CommandText = "update test set Value = ((select Value from test where Id = (select max(Id) from test))+1) where Id = (select max(Id) from test)";
                    myCommand.ExecuteNonQuery();
                }


                using (SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=test1"))
                {
                    SqlCommand myCommand = new SqlCommand();
                    myConnection.Open();
                    myCommand.Connection = myConnection;

                    myCommand.CommandText = "update test set Value = Value";
                    myCommand.ExecuteNonQuery();
                }

                using (TransactionScope innerScope = new TransactionScope())
                {
                    using (SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=test2"))
                    {
                        SqlCommand myCommand = new SqlCommand();
                        myConnection.Open();
                        myCommand.Connection = myConnection;

                        myCommand.CommandText = "update test set Value = ((select Value from test where Id = (select max(Id) from test))+1) where Id = (select max(Id) from test)";
                        myCommand.ExecuteNonQuery();
                    }
                    if (failInner == false) { innerScope.Complete(); }
                }

                if (failOuter == false) { outerScope.Complete(); }
            }
        }

        catch (TransactionAbortedException)
        {
            return false;
        }

        return true;
    }

1 Ответ

7 голосов
/ 24 апреля 2009

Обычно вы не получаете исключение, если не вызываете TransactionScope.Complete () до того, как TransactionScope выходит из области действия и удаляется. Транзакция просто откатится.

Исключение в вашем случае происходит, потому что вы пытаетесь вызвать Complete на внешнем TransactionScope, и он не может завершиться должным образом, потому что внутренний TransactionScope уже вышел из строя - следовательно, это вызывает исключение.

Имеет ли это смысл?

Если вы хотите выполнить какую-либо операцию, если ваша внешняя транзакция прервана, вы можете попробовать что-то вроде этого:

// Inside each using TransactionScope(), hhok up the current transaction completed event
Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted);

// handle the event somewhere else
void Current_TransactionCompleted(object sender, TransactionEventArgs e)
{
  //  check the status of the transaction
  if(e.Transaction.TransactionInformation.Status == TransactionStatus.Aborted)
    // do something here
}

Хотя я думаю, что более понятным шаблоном для общего использования будет всегда вызывать Complete () внутри вашего TransactionScope и обрабатывать любое возникающее исключение, если вы хотите сделать что-то конкретное в случае сбоя транзакции.

...