在SQL Server 2014中使用TransactionScope围绕存储过程与事务

我使用C#和ADO.Net与TransactionScope在ASP.Net应用程序中运行事务。 此事务应该在多个表中保存一些数据,然后向订阅者发送电子邮件。

问题 :当它包含对SQL Server 2014中具有自己的事务的存储过程的调用时,它是否是TransactionScope的有效使用,或者我是否应该从存储中删除SQL事务语句,即begin trancommit tranrollback 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为零 ),除非您使用保存点 (即SAVE TRANSACTION xxROLLBACK TRANSACTION xx
  • 使用存储过程时,如果连接的@@TRANCOUNT在退出SPROC时与输入SPROC时的值不同,则会收到错误。

因此,通常更容易将事务语义留给TransactionScope并删除任何手动BEGIN TRAN / COMMIT TRAN逻辑使您的TSQL混乱。

编辑 – 澄清以下评论

  • 在OP的情况下,SPROC没有考虑嵌套事务(即,是否由Sql或.Net外部事务包装),具体而言, BEGIN CATCH块中的ROLLBACK将中止整个外部事务并且可能会进一步导致外部TransactionScope中的错误,因为没有遵守@@TRANCOUNT规则。 如果SPROC需要以嵌套或独立事务方式运行,则应遵循此类嵌套事务模式 。

  • SavePoints不适用于分布式事务 ,并且TransactionScope可以轻松升级到分布式事务,例如,如果您在事务范围内使用不同的连接字符串或控制其他资源。

因此,我建议将PROC重构为一个“快乐”的核心/内部案例,从事务范围调用此内部proc,并在那里进行任何exception处理和回滚。 如果你还需要从Ad Hoc Sql调用proc,那么提供一个外部包装器Proc,它具有exception处理:

 -- 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, eg 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;