围绕SqlTransaction回滚的异常处理

时间:2009-06-03 09:45:42

标签: c# .net sql transactions

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

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

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...
}

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

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计数消息 - 我对此不感兴趣,因为我回滚事务)我的异常处理代码。 有没有办法可以重构我的代码来实现这一目标?

修改

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

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

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

4 个答案:

答案 0 :(得分:5)

好吧,conn.Close()无论如何都可能会被using关闭(如果你想一想,奇怪的是我们在异常后只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)将进行回滚。

答案 1 :(得分:4)

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

答案 2 :(得分:1)

我同意Marc的看法,问题很可能出在存储过程中。 有一篇非常有趣的文章概述了一些问题here

答案 3 :(得分:0)

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

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

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