ASP.Netアプリでトランザクションを実行するために、C#とADO.NetをTransactionScope
と共に使用しています。このトランザクションは、複数のテーブルにまたがって一部のデータを保存してから、サブスクライバーにEメールを送信することになっています。
質問:SQL Server 2014で独自のトランザクションを持つストアドプロシージャへの呼び出しが含まれる場合、TransactionScope
の有効な使用ですか、それともSQLトランザクションステートメントを削除する必要がありますか? begin tran
、commit 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
はい、TSQL BEGIN / COMMIT TRANSACTION
またはADO SqlConnection.BeginTransaction
をラップする場合、TransactionScope
は引き続き機能します。単一の接続をラップする場合の動作は、ネストと同様です。 Sql
のトランザクション:
@@TranCount
はBEGIN TRAN
ごとに増加します
COMMIT TRAN
は単に@@TRANCOUNT
をデクリメントします。トランザクションは、@@TRANCOUNT
がゼロに達した場合にのみコミットされます。
しかしながら:
ROLLBACK TRAN
は、トランザクション全体を中止します(つまり @@ TRANCOUNT to zero )。ただし、 Save Points を使用している場合を除きます(つまり、SAVE TRANSACTION xx
... ROLLBACK TRANSACTION xx
。@@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;