如何在 SQL Server 中重新引发相同的异常
Posted
技术标签:
【中文标题】如何在 SQL Server 中重新引发相同的异常【英文标题】:How to rethrow the same exception in SQL Server 【发布时间】:2011-01-29 16:35:00 【问题描述】:我想在 SQL Server 中重新引发我的 try 块中刚刚发生的相同异常。我可以抛出相同的消息,但我想抛出相同的错误。
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 传递给 catch 时
Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,
第 14 行 违反 UNIQUE KEY 约束“UK_DomainCode”。无法插入 对象中的重复键 '标签.tblDomain'。 声明已终止。
编辑:
考虑到存储过程包含多个需要执行的查询,如果我希望在前端处理异常,那么不使用 try catch 块会有什么缺点?
【问题讨论】:
【参考方案1】: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
【讨论】:
当心,看起来此解决方案仅适用于 sql server 2012 及更高版本:msdn.microsoft.com/en-us/library/ee677615.aspx @BogdanBogdanov 这样你就可以记录错误,可能处理某些情况,但如果你不能,那么你想重新抛出错误,以便任何更高的 try/catch 有机会处理它 是的,@罗伯特·麦基。我明白了。抱歉,忘记清除这条评论了。ROLLBACK
行上的分号很重要!没有它,您可能会收到SQLException: Cannot roll back THROW
。【参考方案2】:
这是一个功能齐全的干净代码示例,用于在发生错误时回滚一系列语句并报告错误消息。
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
【讨论】:
我在一个存储过程的中间使用了这个,发现它在raiserror
之后会继续执行,这和c#在throw
之后退出的方式不同。所以我在catch
中添加了return
,因为我想匹配这种行为。
@BogdanBogdanov 我回滚了您的编辑,因为此代码的重点是最小化,并且不减损代替 ...
好的,没问题,@Ben Gripka。我试图让它在屏幕上更具可读性。感谢您指出回滚的原因。
@BrianJ:通常情况下,执行是否停止取决于原始错误的严重程度。如果严重性 >= 11,则应停止执行。这真的很奇怪,因为严重性> = 11 的 catch 块内的 raiserror 不再停止执行。您的观察非常好,它显示了 sql server 至少是 2008r2 的脑死亡。新版本似乎更好。
@costa 见RAISERROR()
’s docs。如果CATCH
位于TRY
块内,则≥11 的严重性只会跳转到它。因此,如果您希望 RAISERROR()
影响流控制,您必须在代码周围有 BEGIN TRY…END CATCH
。【参考方案3】:
在 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 命令,或者只捕获某些错误的能力。但是现在,使用一种解决方法。对不起。
【讨论】:
在 sql 2012 中,您可以使用新的 THROW 关键字重新引发异常 是的。当然,在问这个问题时,它不可用。 捕获并抛出一个新错误比不捕获它并让它“冒泡”更重要,因为您可能需要一些清理、纠正措施和关闭活动来处理异常适当地。一个明显的例子是关闭和处理游标。其他示例可能是执行日志记录过程,或重置某些数据。【参考方案5】:你不能:只有引擎可以抛出小于 50000 的错误。你所能做的就是抛出一个看起来像它的异常......
See my answer here please
这里的提问者使用客户端事务来做他想做的事,我认为这有点傻......
【讨论】:
【参考方案6】:好的,这是一种解决方法...:-)
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:谢谢帮助,但我需要从数据库向前端抛出异常,否则我有很多选项打开,例如打印 error_number()、返回 error_number 和 1 u 建议跨度> 【参考方案7】:在发生错误后停止执行存储过程并将错误返回给调用程序的方法是跟踪可能使用此代码引发错误的每个语句:
If @@ERROR > 0
Return
我自己很惊讶地发现存储过程中的执行可以在出错后继续 - 没有意识到这会导致一些难以追踪的错误。
这种类型的错误处理类似于(.Net 之前)Visual Basic 6。期待 SQL Server 2012 中的 Throw 命令。
【讨论】:
【参考方案8】:鉴于您尚未移至 2012 年,实现原始错误代码冒泡的一种方法是使用您从 catch 块中(重新)抛出的异常的文本消息部分。请记住,它可以包含一些结构,例如,供调用方代码在其 catch 块中解析的 XML 文本。
【讨论】:
【参考方案9】:当您希望在事务中执行 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'
【讨论】:
【参考方案10】:从设计的角度来看,抛出带有原始错误号和自定义消息的异常有什么意义?在某种程度上,它打破了应用程序和数据库之间的接口契约。 如果您想捕获原始错误并在更高代码中处理它们,请不要在数据库中处理它们。然后,当您捕获异常时,您可以将呈现给用户的消息更改为您想要的任何内容。不过我不会这样做,因为它会使您的数据库代码嗯“不正确”。正如其他人所说,您应该定义一组自己的错误代码(高于 50000)并抛出它们。然后,您可以将完整性问题(“不允许重复值”)与潜在的业务问题分开处理 - “邮政编码无效”、“未找到符合条件的行”等等。
【讨论】:
用原始错误号和自定义消息抛出异常有什么意义?假设您想直接在 catch 块中处理一两个特定(预期)错误,并将其余错误留给更高层。因此,您需要能够重新抛出您没有处理的异常......最好不必诉诸其他特殊方式报告和处理错误。 除了@Jenda 解释的内容之外,我喜欢使用try-catch 来确保代码在异常后不会继续执行,就像在C# 中的那样:try code(); catch (Exception exc) log(exc); throw; finally cleanup();
,其中throw;
只会引发原始异常及其原始上下文。
我在 SQL 中捕获错误并重新抛出自定义错误消息,以添加描述错误发生的行或其他详细信息(例如尝试插入的数据)的详细信息,以帮助我稍后追踪错误.以上是关于如何在 SQL Server 中重新引发相同的异常的主要内容,如果未能解决你的问题,请参考以下文章
如何从 PowerShell 中的 catch 块中重新引发异常?
将 UDF 从 MS SQL Server 移植到 MySQL 会引发异常不正确的双精度值