在 SQL Server 2014 中围绕带有事务的存储过程使用 TransactionScope
Using TransactionScope around a stored procedure with transaction in SQL Server 2014
我在 ASP.Net 应用程序中使用 C# 和 ADO.Net 以及 TransactionScope
到 运行 事务。此事务应该跨多个表保存一些数据,然后向订阅者发送电子邮件。
问题:当它包括对在 SQL 服务器中有自己的事务的存储过程的调用时,它是否是 TransactionScope
的有效使用2014,或者我应该删除 SQL 事务语句,即 begin tran
、commit tran
和 rollback tran
语句从此 TransactionScope
中调用的存储过程?
这个场景的C#代码和存储过程的T-SQL代码都在下面提到。
C# 代码使用 TransactionScope
:
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
是的,TransactionScope
在包装 TSQL BEGIN / COMMIT TRANSACTION
或 ADO SqlConnection.BeginTransaction
时仍然可以工作。包装单个连接时,行为类似于 Sql
:
中的嵌套事务
@@TranCount
将在每个 BEGIN TRAN
上递增
COMMIT TRAN
只会递减 @@TRANCOUNT
。仅当 @@TRANCOUNT
为零时才会提交事务。
但是:
ROLLBACK TRAN
将中止整个事务(即 @@TRANCOUNT to zero), unless you are using Save Points (即 SAVE TRANSACTION xx
... ROLLBACK TRANSACTION xx
.
- 使用存储过程时,如果退出 SPROC 时连接的
@@TRANCOUNT
与进入 SPROC 时的值不同,您将收到错误消息。
因此,通常更容易将事务语义留给 TransactionScope
并删除任何手动 BEGIN TRAN / COMMIT TRAN
逻辑以免使您的 TSQL 混乱。
编辑 - 澄清下面的评论
在 OP 的情况下,在编写 SPROC 时并未考虑嵌套事务(即是否由 Sql 或 .Net 外部事务包装),具体来说,[=26 BEGIN CATCH
块中的 =] 将中止整个外部事务,并且可能会导致外部 TransactionScope
中出现更多错误,因为未遵守 @@TRANCOUNT
规则。如果 SPROC 需要以嵌套或独立事务方式运行,则应观察到 nested transaction pattern such as this。
SavePoints do not work with Distributed transactions, and TransactionScope
can easily escalate into a distributed transaction 例如如果您在事务范围内使用不同的连接字符串或控制其他资源。
因此,我建议将 PROC 重构为 'happy' 核心/内部情况,从事务范围调用此内部过程,并在那里进行任何异常处理和回滚。如果您还需要从 Ad Hoc Sql 调用 proc,则提供一个具有异常处理的外部包装器 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;
我在 ASP.Net 应用程序中使用 C# 和 ADO.Net 以及 TransactionScope
到 运行 事务。此事务应该跨多个表保存一些数据,然后向订阅者发送电子邮件。
问题:当它包括对在 SQL 服务器中有自己的事务的存储过程的调用时,它是否是 TransactionScope
的有效使用2014,或者我应该删除 SQL 事务语句,即 begin tran
、commit tran
和 rollback tran
语句从此 TransactionScope
中调用的存储过程?
这个场景的C#代码和存储过程的T-SQL代码都在下面提到。
C# 代码使用 TransactionScope
:
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
是的,TransactionScope
在包装 TSQL BEGIN / COMMIT TRANSACTION
或 ADO SqlConnection.BeginTransaction
时仍然可以工作。包装单个连接时,行为类似于 Sql
:
@@TranCount
将在每个BEGIN TRAN
上递增
COMMIT TRAN
只会递减@@TRANCOUNT
。仅当@@TRANCOUNT
为零时才会提交事务。
但是:
ROLLBACK TRAN
将中止整个事务(即 @@TRANCOUNT to zero), unless you are using Save Points (即SAVE TRANSACTION xx
...ROLLBACK TRANSACTION xx
.- 使用存储过程时,如果退出 SPROC 时连接的
@@TRANCOUNT
与进入 SPROC 时的值不同,您将收到错误消息。
因此,通常更容易将事务语义留给 TransactionScope
并删除任何手动 BEGIN TRAN / COMMIT TRAN
逻辑以免使您的 TSQL 混乱。
编辑 - 澄清下面的评论
在 OP 的情况下,在编写 SPROC 时并未考虑嵌套事务(即是否由 Sql 或 .Net 外部事务包装),具体来说,[=26
BEGIN CATCH
块中的 =] 将中止整个外部事务,并且可能会导致外部TransactionScope
中出现更多错误,因为未遵守@@TRANCOUNT
规则。如果 SPROC 需要以嵌套或独立事务方式运行,则应观察到 nested transaction pattern such as this。SavePoints do not work with Distributed transactions, and
TransactionScope
can easily escalate into a distributed transaction 例如如果您在事务范围内使用不同的连接字符串或控制其他资源。
因此,我建议将 PROC 重构为 'happy' 核心/内部情况,从事务范围调用此内部过程,并在那里进行任何异常处理和回滚。如果您还需要从 Ad Hoc Sql 调用 proc,则提供一个具有异常处理的外部包装器 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;