围绕SqlTransaction回滚的exception处理

我有两个存储过程,我想在事务中执行包装。 由于各种原因,我需要在我的应用程序代码中而不是在数据库中处理事务。

目前,我的代码如下所示:

try { using (SqlConnection conn = Connection()) { conn.Open(); using (SqlTransaction sqlTrans = conn.BeginTransaction()) { try { using (SqlCommand cmd1 = new SqlCommand("Stored_Proc_1", conn, sqlTrans)) { cmd1.CommandType = CommandType.StoredProcedure; cmd1.ExecuteNonQuery(); } using (SqlCommand cmd2 = new SqlCommand("Stored_Proc_2", conn, sqlTrans)) { cmd2.CommandType = CommandType.StoredProcedure; cmd2.ExecuteNonQuery(); } sqlTrans.Commit(); } catch { sqlTrans.Rollback(); throw; } } conn.Close(); } } catch (SqlException ex) { // exception handling and logging code here... } 

当其中一个存储过程引发错误时,我看到的exception消息如下所示:

 Error message from raiserror within stored procedure. Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count = 1, current count = 0. 

这是有道理的,因为在第一次捕获时,事务尚未回滚。

但我想要一个“干净”错误(没有tran计数消息 – 我对此不感兴趣,因为我正在回滚事务)我的exception处理代码。 有没有办法可以重构我的代码来实现这一目标?

编辑:

我存储过程的基本结构如下所示:

 create proc Stored_Proc_1 as set nocount on begin try begin transaction raiserror('Error raised by Stored_Proc_1', 16, 1) commit end try begin catch if (@@trancount > 0) rollback declare @ErrMsg nvarchar(4000), @ErrSeverity int, @ErrProc sysname, @ErrLine varchar(10) select @ErrMsg = ERROR_MESSAGE(), @ErrSeverity = ERROR_SEVERITY(), @ErrProc = ERROR_PROCEDURE(), @ErrLine = ERROR_LINE() -- log the error -- sql logging code here... raiserror(@ErrMsg, @ErrSeverity, 1) end catch 

更新:我已经从我的存储过程中进行了事务处理,这似乎已经解决了问题。 显然我做错了 – 但我仍然想知道如何做对。 从存储过程中删除事务是最好的解决方案吗?

好吧, conn.Close()可以继续 – 它会被using关闭(如果你想一想,奇怪的是我们只在exception后Close()Close() )。

您的任何一个存储过程是否在其自身内部执行任何事务代码(未回滚/已提交)? 听起来就是问题所在……? 如果有的话,错误消息告诉我,其中一个存储过程正在执行COMMIT即使它没有启动事务 – 可能是由于(不正确)方法:

 -- pseduo-TSQL IF @@TRANCOUNT = 0 BEGIN TRAN -- ... IF @@TRANCOUNT > 0 COMMIT TRAN -- or maybe = 1 

(如果你在TSQL中进行条件事务,你应该跟踪(通过bool标志)你是否创建了事务 – 如果你这样做则只执行COMMIT

另一种选择是使用TransactionScope – 更容易使用(您不需要针对每个命令设置它等),但效率稍低

 using(TransactionScope tran = new TransactionScope()) { // create command, exec sp1, exec sp2 - without mentioning "tran" or // anything else transaction related tran.Complete(); } 

(注意没有回滚等;如果需要, Dispose() (通过using )将执行回滚。

如果在应用程序中执行此操作,请不要在数据库/存储过程中执行事务! 这肯定会造成混乱。 选择一层并坚持下去。 确保您有一个很好的规范化数据库,exception应该向上渗透。

我同意Marc的看法,问题可能在于存储过程本身。 这里有一篇非常有趣的文章,概述了一些问题。

如果存储过程包含如下代码:

 BEGIN TRY SET @now = CAST(@start AS datetime2(0)) END TRY BEGIN CATCH SET @now = CURRENT_TIMESTAMP END CATCH 

并且你将’now’作为@start传递,try中的CAST将失败。 这标志着事务只是回滚,即使已经捕获并处理了错误本身。 因此,虽然您没有从上面的代码中获得任何exception,但无法提交事务。 如果您的存储过程具有这样的代码,则需要重写它以避免try / catch。