1

我将一些存储过程链接在一起,并且在使错误处理正常工作时遇到了一些问题。由于其中一些存储过程是长期运行的,因此我使用了存储过程中的SqlInfoMessageEventHandler代码RAISERROR('whatever',10,1) WITH NOWAIT来向用户报告操作的进度。为了做到这一点,我读到您不能使用 cmd.ExecuteNonQuery(),而必须使用 cmd.ExecuteReader()。我已经尝试过了,似乎确实如此;我没有看到来自 ExecuteNonQuery 的任何消息。

我发现的问题是,当我使用 ExecuteReader 时,如果我的存储过程抛出任何错误,那么它们将被忽略。我的意思是我的 .NET 应用程序从 try 块调用此存储过程,当存储过程遇到错误(如 SELECT 1/0)时,执行永远不会进入 catch 块,而是提交我的事务。这方面的一个例子如下;

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TempTstTbl]') AND type in (N'U'))
DROP TABLE [dbo].[TempTstTbl]

CREATE TABLE [dbo].[TempTstTbl] (
    Step INT,
    Val  VARCHAR(50)
)

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Child]') AND type in (N'P', N'PC'))

DROP PROCEDURE [dbo].[Child]
GO 
CREATE PROCEDURE [dbo].[Child]
AS
BEGIN
BEGIN TRY
    INSERT INTO [dbo].[TempTstTbl] (Step, Val) VALUES (1, 'FROM CHILD BEFORE FAULT')
    SELECT 1/0
    --RAISERROR ('This should really fail', 16, 2)
    INSERT INTO [dbo].[TempTstTbl] (Step, Val) VALUES (2, 'FROM CHILD AFTER FAULT')
END TRY
BEGIN CATCH
    DECLARE @ErrorMessage NVARCHAR(4000);
    DECLARE @ErrorSeverity INT;
    DECLARE @ErrorState INT;

    SELECT 
        @ErrorMessage = ERROR_MESSAGE(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE();

    -- Use RAISERROR inside the CATCH block to return error
    -- information about the original error that caused
    -- execution to jump to the CATCH block.
    RAISERROR (@ErrorMessage, -- Message text.
               @ErrorSeverity, -- Severity.
               @ErrorState -- State.
               );
END CATCH;
END
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Parent]') AND type in (N'P', N'PC'))

DROP PROCEDURE [dbo].[Parent]
GO
CREATE PROCEDURE [dbo].[Parent]
AS
BEGIN
BEGIN TRY
    INSERT INTO [dbo].[TempTstTbl] (Step, Val) VALUES (1, 'FROM PARENT BEFORE CHILD')
    Exec [dbo].[Child]
    INSERT INTO [dbo].[TempTstTbl] (Step, Val) VALUES (2, 'FROM PARENT AFTER CHILD')
END TRY
BEGIN CATCH
    DECLARE @ErrorMessage NVARCHAR(4000);
    DECLARE @ErrorSeverity INT;
    DECLARE @ErrorState INT;

    SELECT 
        @ErrorMessage = ERROR_MESSAGE(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE();

    -- Use RAISERROR inside the CATCH block to return error
    -- information about the original error that caused
    -- execution to jump to the CATCH block.
    RAISERROR (@ErrorMessage, -- Message text.
               @ErrorSeverity, -- Severity.
               @ErrorState -- State.
               );
END CATCH;
END
GO

EXEC [dbo].[Parent]
SELECT * FROM [dbo].[TempTstTbl]

使用一些 .NET 代码,例如;

private void button4_Click(object sender, EventArgs e)
{
    using (SqlConnection conn = new SqlConnection(@"Data Source=XPDEVVM\XPDEV;Initial Catalog=MyTest;Integrated Security=SSPI;"))
    {
        conn.Open();
        using (SqlTransaction trans = conn.BeginTransaction())
        {
            using (SqlCommand cmd = conn.CreateCommand())
            {
                cmd.Transaction = trans;
                cmd.CommandText = "[cfg].[Parent]";
                cmd.CommandType = CommandType.StoredProcedure;

                try
                {
                    -- cmd.ExecuteReader(); -- Using this instead of ExecuteNonQuery means the divide by 0 error in the stored proc is ignored, and everything is committed :(
                    cmd.ExecuteNonQuery();
                    trans.Commit();
                }
                catch (Exception ex)
                {
                    trans.Rollback();
                }
            }
        }
    }
}

是否有人对如何从存储过程中获取进度消息有任何想法,但如果存储过程中发生错误,仍然会捕获 .NET 异常?

4

1 回答 1

1

这个问题似乎源于 DataReader 的工作方式。我第一次在 Nelson Rothermel 的评论中看到了一个提示,是对一个单独的 SO 问题的回答。

然后,我进一步阅读了此线程中描述的此问题的详细信息,其中 Richard McKinnon 给出了以下示例作为解决问题的一种方式(我的重点);

在严重性 > 10 时尝试相同(我使用的是 11)并将 SELECT 行添加到 SP(我使用 NorthWind DB 进行了检查)。您会看到,当您有一个检索数据的 SELECT 时,您永远不会遇到异常。

[原始消息剪辑]

在这种情况下,ExecuteReader() 将开始处理第一组结果,即 SELECT 语句。要获得下一组结果,RAISERROR,您需要调用 NextResult()

因此,仅查看错误,我会将之前的代码更改为如下所示;

SqlDataReader dr = command.ExecuteReader(); 博士下一个结果(); 博士关闭();

我尝试将 dr.NextResult() 添加到我的代码中,它似乎确实为我解决了这个问题。

然后,这允许我从我的存储过程中获取信息消息,但也允许我捕获存储过程中引发的错误。

于 2012-09-16T15:08:48.990 回答