web-dev-qa-db-ja.com

SQL Server 2014でトランザクションを含むストアドプロシージャの周りにTransactionScopeを使用する

ASP.Netアプリでトランザクションを実行するために、C#とADO.NetをTransactionScopeと共に使用しています。このトランザクションは、複数のテーブルにまたがって一部のデータを保存してから、サブスクライバーにEメールを送信することになっています。

質問:SQL Server 2014で独自のトランザクションを持つストアドプロシージャへの呼び出しが含まれる場合、TransactionScopeの有効な使用ですか、それともSQLトランザクションステートメントを削除する必要がありますか? begin trancommit tranおよびrollback tranストアドプロシージャからのステートメントがこのTransactionScope内で呼び出されていますか?

このシナリオのC#コードとストアドプロシージャのT-SQLコードの両方を以下に示します。

TransactionScopeを使用したC#コード:

  try 
    {
        using (TransactionScope scope = new TransactionScope())
        {
            using (SqlConnection connection1 = new SqlConnection(connectString1))
            {
                // Opening the connection automatically enlists it in the  
                // TransactionScope as a lightweight transaction.
                connection1.Open();

                // SaveEmailData is a stored procedure that has a transaction within it
                SqlCommand command1 = new SqlCommand("SaveEmailData", connection1);
                command1.CommandType = CommandType.StoredProcedure;
                command1.ExecuteNonQuery();

            }

            //Send Email using the helper method
            EmailHelper.SendCustomerEmails(customerIds);

            // The Complete method commits the transaction. If an exception has been thrown, 
            // Complete is not  called and the transaction is rolled back.
            scope.Complete();

        }
    }
    catch( Exception ex)
    {
       Logger.Log(ex);
    }

ストアドプロシージャのT-SQL SaveEmailData

SET NOCOUNT ON

    BEGIN TRY
        DECLARE @emailToUserId BIGINT

        BEGIN TRAN
        -- //update statement. detail statement omitted
        UPDATE TABLE1...

         --update statement. detail statement omitted
        UPDATE TABLE2...

        IF @@trancount > 0
        BEGIN
            COMMIT TRAN
        END
    END TRY

    BEGIN CATCH

        IF @@TRANCOUNT > 0
        BEGIN
            ROLLBACK TRAN
        END

        EXEC Error_RaiseToADONET

    END CATCH
14
Sunil

はい、TSQL BEGIN / COMMIT TRANSACTIONまたはADO SqlConnection.BeginTransactionをラップする場合、TransactionScopeは引き続き機能します。単一の接続をラップする場合の動作は、ネストと同様です。 Sqlのトランザクション:

  • @@TranCountBEGIN TRANごとに増加します

  • COMMIT TRANは単に@@TRANCOUNTをデクリメントします。トランザクションは、@@TRANCOUNTがゼロに達した場合にのみコミットされます。

しかしながら:

  • ROLLBACK TRANは、トランザクション全体を中止します(つまり @@ TRANCOUNT to zero )。ただし、 Save Points を使用している場合を除きます(つまり、SAVE TRANSACTION xx ... ROLLBACK TRANSACTION xx
  • ストアドプロシージャを使用する場合、SPROCを終了するときに接続の@@TRANCOUNTがSPROCの開始時の値と異なる場合、エラーが発生します。

その結果、通常、トランザクションセマンティクスをTransactionScopeのままにして、手動のBEGIN TRAN / COMMIT TRANロジックをTSQLが乱雑にしないようにする方がはるかに簡単です。

編集-以下のコメントの明確化

  • OPの場合、SPROCはネストされたトランザクション(つまり、Sqlまたは.Net外部トランザクションによってラップされているかどうか)を考慮して記述されていません。具体的には、BEGIN CATCHブロックのROLLBACKは、 @@TRANCOUNTルールが遵守されていないため、外部トランザクション全体で、外部TransactionScopeでさらにエラーが発生する可能性があります。 このようなネストされたトランザクションパターン は、SPROCがネストされたトランザクションとスタンドアロンのトランザクションの両方で動作する必要がある場合に注意する必要があります。

  • セーブポイントは分散トランザクションでは機能しません 、そしてTransactionScopeは簡単にできます 分散トランザクションにエスカレート 例:別の接続文字列を使用している場合、またはトランザクションスコープで他のリソースを制御している場合。

その結果、PROCを「幸せな」コア/内部ケースにリファクタリングし、トランザクションスコープからこの内部プロシージャを呼び出し、そこで例外処理とロールバックを実行することをお勧めします。アドホックSQLからもプロシージャを呼び出す必要がある場合は、例外処理を備えた外部ラッパーProcを提供します。

-- Just the happy case. This is called from .Net TransactionScope
CREATE PROC dbo.InnerNonTransactional
  AS
    BEGIN 
      UPDATE TABLE1...
      UPDATE TABLE2 ....
    END;

-- Only needed if you also need to call this elsewhere, e.g. from AdHoc Sql
CREATE PROC dbo.OuterTransactional
  AS
    BEGIN
      BEGIN TRY
        BEGIN TRAN
            EXEC dbo.InnerNonTransactional
        COMMIT TRAN
      END TRY
      BEGIN CATCH
         -- Rollback and handling code here.
      END CATCH
    END;
14
StuartLC