Sql异常没有传递给C#Catch

时间:2017-05-01 22:16:10

标签: c# sql-server

我有一个用C#编写的自动脚本,它在SQL Server 2014上运行存储过程。存储过程运行多个select,update和insert语句,并利用try catch回滚模式捕获并回滚整个事务。 #39;是个例外。

看起来与此类似:

BEGIN TRY
    BEGIN TRANSACTION TransName
    --Lots of SQL!
    COMMIT TRANSACTION TransName
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION TransName;
    THROW
END CATCH    

调用该过程的我的C#看起来与此类似:

using (SqlCommand Command = new SqlCommand(query, Connection))
{
    // Retry several times if the query fails.
    for (var retry = 0; retry < 5 && !Success; ++retry)
    {
        try
        {
            Command.ExecuteNonQuery();
            Success = true;
        }
        catch (SqlException e)
        {
            // Handling for Timeout or deadlocks.
            // If not a timeout or deadlock and retry hasn't happened 4 times already.
            if (!(e.Number == 1205 || e.Number == 1204 || e.Number == -2) || retry == 4)
            {
                LogException(e);
            }
            else if (e.Number == 1205 || e.Number == 1204)
            {
                // Wait to avoid hammering the database.
                Thread.Sleep(500);
            }
            else if (e.Number == -2)
            {
                // Wait to avoid hammering the database.
                Thread.Sleep(5000);
            }

            Success = false;
        }
    }
}

我已经进行了循环,以确保在遇到死锁或超时时SQL会通过,因为它是一个自动脚本。

在我的脚本日志中,我可以看到存储过程没有记录任何异常,但是程序触及的表中没有任何数据存在我的问题:

是否可以在T-SQL中捕获异常,然后使用T-SQL THROW语句再次抛出异常,但是C#客户端不会抛出异常?

如果我能澄清任何事情,请告诉我。谢谢!

4 个答案:

答案 0 :(得分:3)

SQL中的try...catch的工作方式略有不同,我过去所做的就是在存储过程中使用OUTPUT变量:

ALTER PROCEDURE dbo.yourStoredProcedure
    (-- your parameters
     @errNumber  INT OUTPUT,
     @errLine    INT OUTPUT,
     @errMessage VARCHAR(MAX) OUTPUT)
AS
BEGIN

    SET @errNumber  = 0
    SET @errLine    = 0
    SET @errMessage = ''

    BEGIN TRY
        BEGIN TRANSACTION TransName
        --Lots of SQL!
        COMMIT TRANSACTION TransName
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION TransName;

        SELECT   @errNumber  = ERROR_NUMBER()
        ,        @errLine    = ERROR_LINE()
        ,        @errMessage = ERROR_MESSAGE()
    END CATCH   
END
GO

您需要调整C#中的try以添加参数并读取返回值

    try
    {
        SqlParameter errNumber = new SqlParameter("@errNumber", 0);
        SqlParameter errLine = new SqlParameter("@errLine", 0);
        SqlParameter errMessage = new SqlParameter("@errMessage", "");

        Command.ExecuteNonQuery();

        int SqlError = (int)(errNumber.Value);
        int SqlLine = (int)(errNumber.Value);
        string SqlMessage = (string)errMessage.Value;

        if (SqlError == 0 ) { Success = true; }
        else {
            Success = false;
            // whatever else you want to do with the error data
        }
    }

您的SqlException捕获仍会捕获不在过程TRY...CATCH中的错误,并且您还应该有一个通用Catch(Exception ex)块以及其他错误,最后不会忘记finally {}进行任何可能需要的清理工作。

更新05/03/2017

在大多数情况下,在try...catch内包装交易会导致无法承认的交易。因此,我们可以翻转包装以在事务中使用try-catch。如果发现错误,那么我们应该能够获得错误值,如果事务再次存在(@@transcount >0),它将被回滚,并且@@ transcount将减少为0.在try-catch块关闭之后我们再次检查@@ transount并提交(如果存在)

BEGIN TRANSACTION TransName

BEGIN TRY
    --Lots of SQL!
END TRY

BEGIN CATCH
    SELECT   @errNumber  = ERROR_NUMBER()
    ,        @errLine    = ERROR_LINE()
    ,        @errMessage = ERROR_MESSAGE()

    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION TransName
END CATCH

IF (@@TRANCOUNT > 0) COMMIT TRANSACTION TransName

答案 1 :(得分:1)

此博客对此内容进行了介绍:http://www.dbdelta.com/the-curious-case-of-undetected-sql-exceptions/

如果返回第一行后发生T-SQL错误,ExecuteScalar将不会引发异常。另外,如果由于行返回语句错误并且在T-SQL中捕获了错误而没有返回任何行,则ExecuteScalar返回空对象而不会引发异常。

由于相同的原因,ExecuteNonQuery可能发生相同的问题。

答案 2 :(得分:0)

您是否在路上捕获并记录其他异常类型?如果抛出SqlException以外的其他内容会发生什么?它被记录了吗?

关于重试逻辑 - 我也会处理InvalidOperationException。如果Connection未打开,ExecuteNonQuery将抛出InvalidOperationException。例如,由于短暂的网络中断或其他原因,连接可能会进入ConnectionState.Broken状态。类似于重试死锁和超时的方法,我会捕获InvalidOperationException,检查连接状态,如果它没有打开 - 重新打开它并重试。

答案 3 :(得分:0)

首先,谢谢@MadMyche的建议,添加输出参数帮助我知道捕获因为某些原因从未被击中。

发生的事情是,当查询运行时,它偶尔会超时并返回到C#代码的重试循环中,当发生这种情况时,查询中打开的事务没有被关闭。当重试循环最终循环回来并成功完成查询时,它将关闭sql连接,当发生这种情况时,SQL引擎会通过并关闭并对任何打开的事务执行回滚,这会删除已保存的数据

我发现这篇文章解释了会发生什么并提供解决方案:

http://weblogs.sqlteam.com/dang/archive/2007/10/20/Use-Caution-with-Explicit-Transactions-in-Stored-Procedures.aspx

然后我发现另一篇文章重新强化了解决方案:

http://www.sommarskog.se/error_handling/Part1.html

我要解决的是在proc的开头设置XACT_ABORT

SET XACT_ABORT ON;

设置XACT_ABORT“指定当Transact-SQL语句引发运行时错误时SQL Server是否自动回滚当前事务”(参见documentation

如果将XACT_ABORT设置为ON,则事务将在c#客户端关闭连接之前回滚,以便不会回滚任何打开的事务。