触发器中的Commit和Raiserror

时间:2011-03-17 17:23:14

标签: c# .net sql sql-server vb.net

也许有更好的方法来做到这一点。我想要的是让SQL Server向我提出两种类型的错误:更新表时来自触发器的WARNING和ERROR。如果SQL Server返回WARNING,则触发器应该COMMIT但向用户显示警告(使用.NET - 最好通过SQL异常,只有在严重性> 10时才会引发),如果是ERROR,则触发器应该ROLLBACK并显示对用户的错误(通过SQL异常)。我的尝试(不用说这不起作用)就是有这样一个触发器:

ALTER TRIGGER [dbo].[TR_TRANSACTION_UPDATE]
ON [dbo].[tTRANSACTION]
FOR UPDATE
AS
BEGIN
   ...

    BEGIN TRY
    DECLARE @id INT ,@maxid INT
    SELECT @id = 0 ,@maxid = MAX(transID) FROM INSERTED 
    WHILE @id < @maxid 
        BEGIN
            SELECT  @id = MIN([TransID]) FROM INSERTED WHERE [TransID] > @id
                EXEC dbo.sp_CheckTransaction @TransID = @id
            END
        COMMIT TRANSACTION
    END TRY
    BEGIN CATCH
        DECLARE @err_msg VARCHAR(MAX), @err_sev AS INT, @err_state AS INT
        SELECT @err_msg = ERROR_MESSAGE(), @err_sev = ERROR_SEVERITY(), @err_state = ERROR_STATE()
        IF @err_state <> 120  -- '120 is not a fatal error from STORED_PROC
              ROLLBACK TRANSACTION
        ELSE
              COMMIT TRANSACTION

        RAISERROR(@err_msg, @err_sev, @err_state)
    END CATCH
END

此触发器不起作用,因为它认为交易无法使用。

此外,sp_CheckTransaction可以引发两种类型的错误:

 RAISERROR(@msg, 15, 120)  -- warning
 or RAISERROR(@msg, 15, 121)  -- error

我无法使用严重性的原因&lt; 15是因为我想在.NET中显示警告(如果是警告或错误,我可以根据状态和严重性来决定)。仅针对严重性&gt;引发.NET SqlException 10.

3 个答案:

答案 0 :(得分:3)

 RAISERROR(@err_msg, @err_sev, @err_state)

你不能这样做。不允许您引发系统消息,仅允许用户消息(错误号大于50000)。你必须这样提高:

RAISERROR(N'An error occured in trigger: %s %d %d', 
      <severity>, <state>, @err_msg, @err_sev, @err_state);

[更新:实际上,因为@err_msg是消息,而不是错误编号,您的使用是正常的,所以请忽略此]

有关此主题的更长时间的讨论,请参阅Exception handling and nested transactions。你会看到那里解释了为什么你的实现存在缺陷(它不会检查XACT_STATE())并且链接的文章提供了更好的实现。

现在回到您的痛点:您需要了解SQL Server错误严重性模型。严重性超过10的错误是错误,ADO.Net将为它们引发异常。严重性低于10的错误是信息性消息和ADO.NEt将为他们举起SqlConnection.InfoMessage事件。另请参阅What do the different RAISERROR severity levels mean?

因此问题的真正根源不是您的触发器,而是sp_CheckTransaction存储过程。它应该针对错误提高严重性16,针对警告提高严重性0:

RAISERROR(@msg, 0, 120)  -- warning

RAISERROR(@msg, 16, 121)  -- error

使用SqlConnection.InfoMessage拦截警告。尝试将状态用作严重性将使您无法快速完成任务。

您的触发器可能会捕获并重新抛出错误,具体取决于多种因素,但您不太可能需要这样做。如果你确实捕获了异常,那么你需要引发一个 new 错误,并且需要适当的逻辑来处理严重性(错误增加16,警告增加0)。切勿在T-SQL代码中使用16和0以外的任何其他严重性。

作为旁注,从下一版本的SQL Server开始,您将能够使用简单的THROW;重新抛出原始异常,就像.Net异常处理一样。更多信息SQL Server v.Next (Denali) : Exploring THROW

答案 1 :(得分:1)

我会建议不要这样的设计。它会让你更难以测试你的代码,但是如果你出于某种原因而被这样做了,这就是如何做到的。

raiserror('foo',1,1)会引发错误,但不会导致触发器的执行停止。如果严重程度大于10,我相信它会使事务回滚。

http://msdn.microsoft.com/en-us/library/ms177497.aspx

然后您必须使用SqlConnection来显示错误消息。

答案 2 :(得分:0)

你试过了吗?

IF @err_state <> 120  -- '120 is not a fatal error from STORED_PROC'

而不是

IF @err_state <> 120  -- '120 is not a fatal error from STORED_PROC