3

System.Transactionsの詳細を学ぶことができるように、簡単なダミーアプリを作成しようとしています。このアプリは、2つの異なるSQLExpressDBと相互作用します。コンポーネントサービスでトランザクション統計を取得すると、2番目の接続が開かれたときにouterScopeでトランザクションが開始されていることがわかります。failOuterがtrueの場合、トランザクションは中止されますが、例外はスローされません。failInnerがtrueの場合、TransactionAbortedExceptionがスローされます。

MSDNから:

アプリケーションがトランザクションで実行したいすべての作業を完了したら、Completeメソッドを1回だけ呼び出して、トランザクションをコミットできることをトランザクションマネージャーに通知する必要があります。usingブロックの最後のステートメントとしてCompleteの呼び出しを行うことは非常に良い習慣です。

このメソッドを呼び出さないと、トランザクションマネージャーはこれをシステム障害、またはトランザクションのスコープ内でスローされた例外と同等であると解釈するため、トランザクションを中止します。

スコープがトランザクションを作成し、トランザクションが中止されると、TransactionAbortedExceptionがスローされます。

これに基づいて、failOuterをtrueに設定してアプリを実行するたびに、トランザクション統計に中止されたトランザクションが表示されるため、outerScopeがTransactionAbortedExceptionをスローすることを期待します。トランザクションが中止されても例外はスローされないため、私のメソッドは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;
    }
4

1 に答える 1

7

通常、TransactionScope がスコープ外に出て破棄される前に、TransactionScope.Complete() の呼び出しに失敗しても例外がスローされることはありません。トランザクションは静かにロールバックします。

外側の TransactionScope で Complete を呼び出そうとしているため、例外が発生しています。内側の 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
}

一般的な使用法のよりクリーンなパターンは、トランザクションの失敗に固有のことをしたい場合は、TransactionScope 内で常に Complete() を呼び出し、結果の例外を処理することだと思います。

于 2009-04-24T18:38:19.017 に答える