我正在将一些存储过程链接在一起,并且在使错误处理正常工作时遇到了一些问题。由于其中一些存储过程长期运行,我在存储过程中使用了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异常?
答案 0 :(得分:1)
似乎这个问题源于DataReader的工作方式。我第一次看到Nelson Rothermel在this answer中对一个单独的SO问题的评论。
然后我进一步阅读了this thread中描述的这个问题的细节,其中Richard McKinnon给出了以下示例作为解决问题的方法(我的重点);
尝试使用相同的严重性> 10(我使用11)并将SELECT行添加到SP(我使用NorthWind DB检查)。你会看到的 当你有一个SELECT检索数据时,你永远不会得到 异常。
[剪辑原始邮件]
在这种情况下,ExecuteReader()将开始处理第一组 结果,SELECT语句。为了得到下一组结果, RAISERROR,你需要调用NextResult()。
所以,只要查看我将之前的代码更改为错误的错误 看起来像这样;
SqlDataReader dr = command.ExecuteReader();的 dr.NextResult(); 强> dr.Close();
我尝试将dr.NextResult()添加到我的代码中,似乎确实为我解决了这个问题。
这允许我从我的存储过程中获取信息消息,但也允许我捕获存储过程中引发的错误。