如何在SQL Server中抛出相同的异常


86

我想在try块中重新抛出SQL Server中刚刚发生的相同异常。我可以抛出相同的消息,但我想抛出相同的错误。

BEGIN TRANSACTION
    BEGIN TRY
        INSERT INTO Tags.tblDomain (DomainName, SubDomainId, DomainCode, Description)
            VALUES(@DomainName, @SubDomainId, @DomainCode, @Description)
        COMMIT TRANSACTION
    END TRY
    
    BEGIN CATCH
        declare @severity int; 
        declare @state int;

        select @severity=error_severity(), @state=error_state();

        RAISERROR(@@Error,@ErrorSeverity,@state);
        ROLLBACK TRANSACTION
    END CATCH

RAISERROR(@@Error, @ErrorSeverity, @state);

该行将显示错误,但是我想要类似的功能。这会引发错误号为50000的错误,但是我希望我传递的错误号被抛出@@error

我想在前端捕获此错误。

catch (SqlException ex)
{
    if ex.number==2627
    MessageBox.show("Duplicate value cannot be inserted");
}

我想要这个功能。使用无法实现raiseerror。我不想在后端提供自定义错误消息。

RAISEERROR 当我通过ErrorNo时,应该返回下面提到的错误

Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,

第14行违反UNIQUE KEY约束“ UK_DomainCode”。无法在对象“ Tags.tblDomain”中插入重复的密钥。该语句已终止。

编辑:

考虑到存储过程包含多个需要执行的查询,如果我希望在前端处理异常,不使用try catch块有什么缺点?

Answers:


120

这是一个功能齐全的干净代码示例,用于在发生错误时回滚一系列语句并报告错误消息。

begin try
    begin transaction;

    ...

    commit transaction;
end try
begin catch
    if @@trancount > 0 rollback transaction;
    throw;
end catch

在SQL 2012之前

begin try
    begin transaction;
    
    ...
    
    commit transaction;
end try
begin catch
    declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
    select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
    if @@trancount > 0 rollback transaction;
    raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
end catch

8
我在存储过程的中间使用了它,发现它将在raiserrorc之后继续执行,这与c#在a之后退出的方式不同throw。因此,我添加了一个returncatch因为我想匹配该行为。
Brian J

@BogdanBogdanov我回滚您的编辑,因为这段代码的点是最小的,而不是减损到位的进入了真正的代码...
奔Gripka

好的,没问题,@ Ben Gripka。我尝试使其在屏幕上更具可读性。感谢您指出进行回滚的原因。
Bogdan Bogdanov

1
@BrianJ:通常,执行是否停止取决于原始错误的严重性。如果严重性> = 11,则应停止执行。这真的很奇怪,因为catch块内的严重性> = 11的raiserror不再停止执行。您的观察非常好,它显示了至少在2008r2之前sql server多么令人头疼。较新的版本似乎更好。
哥斯达黎加

1
@costa请参阅RAISERROR()文档。≥11的严重性仅在CATCH块内部时才跳转到该TRY块。因此,如果要影响流控制,则必须具有BEGIN TRY…END CATCH环绕代码RAISERROR()
宾基

137

SQL 2012引入了throw语句:

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

如果指定的THROW语句不带参数,则它必须出现在CATCH块内。这导致引发捕获的异常。

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH

2
当心,看来此解决方案仅适用于sql Server 2012及更高版本:msdn.microsoft.com/en-us/library/ee677615.aspx
Adi

3
@BogdanBogdanov这样您就可以记录该错误,并可能处理某些情况,但是如果不能,则希望重新抛出该错误,以便任何更高级别的尝试/追赶都将有机会来解决它
Robert McKee

是的,@ Robert McKee。我知道了。抱歉,忘了清除此评论。
Bogdan Bogdanov

3
ROLLBACK在线上的分号很重要!没有它,您可能会得到一个SQLException: Cannot roll back THROW
idontevenseethecode

5

在CATCH块内重新抛出(SQL2012之前的代码,对SQL2012及更高版本使用THROW语句):

DECLARE
    @ErrorMessage nvarchar(4000) = ERROR_MESSAGE(),
    @ErrorNumber int = ERROR_NUMBER(),
    @ErrorSeverity int = ERROR_SEVERITY(),
    @ErrorState int = ERROR_STATE(),
    @ErrorLine int = ERROR_LINE(),
    @ErrorProcedure nvarchar(200) = ISNULL(ERROR_PROCEDURE(), '-');
SELECT @ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 'Message: ' + @ErrorMessage;
RAISERROR (@ErrorMessage, @ErrorSeverity, 1, @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine)

4

我认为您的选择是:

  • 不要抓住错误(让它冒泡)
  • 提出一个自定义的

在某些时候,SQL可能会引入一个reraise命令,或者仅捕获某些错误的能力。但是现在,使用一种解决方法。抱歉。


7
在sql 2012中,您可以使用新的
THROW

5
是。当然,当问这个问题时这是不可用的。
罗伯·法利

捕获并抛出一个新错误比不捕获并让它“冒泡”更为重要,因为您可能需要一些清理,纠正措施和关闭活动才能正确处理异常。一个明显的例子是关闭并处理游标。其他示例可能是执行日志记录过程或重置某些数据。
安东尼·布斯

1

您不能:只有引擎抛出的错误小于50000。您所能做的就是抛出一个看起来像这样的异常...

请在这里查看我的答案

这里的提问者使用客户端事务来完成他想要的事情,我认为这有点愚蠢...


0

好的,这是一种解决方法... :-)

DECLARE @Error_Number INT
BEGIN TRANSACTION 
    BEGIN TRY
    INSERT INTO Test(Id, Name) VALUES (newID(),'Ashish') 
    /* Column 'Name' has unique constraint on it*/
    END TRY
    BEGIN CATCH

            SELECT ERROR_NUMBER()
            --RAISERROR (@ErrorMessage,@Severity,@State)
            ROLLBACK TRAN
    END CATCH

如果您注意到catch块,它不是在引发错误,而是返回实际的错误号(也将回滚事务)。现在,在您的.NET代码中,如果使用ExecuteScalar(),而不是捕获异常,您将获得所需的实际错误号并显示适当的错误号。

int errorNumber=(int)command.ExecuteScalar();
if(errorNumber=<SomeNumber>)
{
    MessageBox.Show("Some message");
}

希望这可以帮助,

编辑:-仅需注意,如果您想获取受影响的记录数并尝试使用ExecuteNonQuery,则上述解决方案可能对您不起作用。否则,我认为它会满足您的需求。让我知道。


@Ashish Gupta:谢谢,但是我需要从数据库抛出异常到前端,否则我打开了许多选项,例如print error_number(),return error_number和建议的1 u
Shantanu Gupta 2010年

0

在发生错误后停止存储过程中的执行并将错误冒泡回调用程序的方法是,遵循每个可能使用此代码引发错误的语句:

If @@ERROR > 0
Return

我很惊讶自己发现存储过程可以在发生错误后继续执行-不知道这样做会导致某些难以跟踪的错误。

这种类型的错误处理并行(.Net之前)Visual Basic6。期待SQL Server 2012中的Throw命令。


0

鉴于您还没有搬到2012年,实现原始错误代码的一种方法是使用您从catch块抛出(抛出)的异常的文本消息部分。请记住,它可以包含某些结构,例如,供您的调用者代码解析在其catch块中的XML文本。


0

当您希望在事务内执行SQL语句并将错误馈入代码时,也可以为这些方案创建包装存储过程。

CREATE PROCEDURE usp_Execute_SQL_Within_Transaction
(
    @SQL nvarchar(max)
)
AS

SET NOCOUNT ON

BEGIN TRY
    BEGIN TRANSACTION
        EXEC(@SQL)
    COMMIT TRANSACTION
END TRY

BEGIN CATCH
    DECLARE @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int
    SELECT @ErrorMessage = N'Error Number: ' + CONVERT(nvarchar(5), ERROR_NUMBER()) + N'. ' + ERROR_MESSAGE() + ' Line ' + CONVERT(nvarchar(5), ERROR_LINE()), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE()
    ROLLBACK TRANSACTION
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH

GO

-- Test it
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1/0; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'EXEC usp_Another_SP'

-2

从设计的角度来看,使用原始错误号和自定义消息引发异常的目的是什么?在某种程度上,它破坏了应用程序和数据库之间的接口协定。如果要捕获原始错误并以更高的代码处理它们,请不要在数据库中处理它们。然后,当您发现异常时,可以将显示给用户的消息更改为所需的任何内容。但是我不会这样做,因为这会使您的数据库代码“不正确”。正如其他人所说,您应该定义一组自己的错误代码(大于50000),然后抛出它们。然后,您可以将完整性问题(“不允许重复的值”)与潜在的业务问题分开处理-“邮政编码无效”,“找不到符合条件的行”等等。


9
使用原始错误号和自定义消息引发异常的目的是什么?假设您想直接在catch块中处理一个或两个特定的(预期的)错误,其余的留给更高的层。因此,您需要能够抛出未处理的异常……最好不必诉诸其他特殊方式来报告和处理错误。
詹达2012年

1
除了@Jenda解释的内容之外,我还喜欢使用try-catch来确保代码在异常之后不会继续执行,就像C#:中的do一样,在try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }此处throw;只会引发原始异常及其原始上下文。
R. Schreurs

我捕获错误并在SQL中重新抛出自定义错误消息,以添加描述错误发生在哪一行的详细信息或其他详细信息(例如尝试插入的数据),以帮助以后查找错误。
罗素·汉金斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.