SQL Server:使用原始exception编号的Rethrowexception

我在存储过程中使用TRY CATCH块,其中有两个INSERT指令。

如果出现问题,CATCH块会负责回滚所做的所有更改并且工作正常,除了一件事!

我的ASP.NET应用程序捕获的exception是一个数字为50000的SqlException。这不是原始数字! (我期待的数字是2627)

在exception的Message属性中,我可以看到原始的exception编号和格式化的消息。

我怎样才能获得原始的例外号码?

try { // ... code } catch (SqlException sqlException) { switch (sqlException.Number) { // Name already exists case 2627: throw new ItemTypeNameAlreadyExistsException(); // Some other error // As the exception number is 50000 it always ends here!!!!!! default: throw new ItemTypeException(); } } 

现在,返回值已被使用。 我想我可以使用输出参数来获取exception编号,但这是个好主意吗?

我该怎么做才能获得例外号码? 谢谢

PS:这是必需的,因为我有两个INSERT指令。

您可以像这样重新抛出它:

 .. END TRY BEGIN CATCH DECLARE @errnum int; SELECT @errnum = ERROR_NUMBER(); RAISERROR (@errnum, 16, 1); END CATCH 

但是,由于ERROR_NUMBER()的sys.messages行中的%s等占位符,您很可能会失去意义。

你可以做这样的事情来包括数字并重新抛出原始信息

 .. END TRY BEGIN CATCH DECLARE @errnum nchar(5), @errmsg nvarchar(2048); SELECT @errnum = RIGHT('00000' + ERROR_NUMBER(), 5), @errmsg = @errnum + ' ' + ERROR_MESSAGE(); RAISERROR (@errmsg, 16, 1); END CATCH 

前5个字符是原始数字。

但是如果你有嵌套代码,那么你最终会得到“00123 00456错误文本”。

就个人而言,我只处理SQLexception数字来将我的错误(50000)与我的代码未运行的引擎错误(例如缺少参数)分开。

最后,您可以将其传递给返回值。

我问了一个问题: SQL Servererror handling:exception和数据库 – 客户端合同

如果在T-SQL中使用BEGIN TRY / BEGIN CATCH,则会丢失原始引擎引发的exception。 您不应该手动引发系统错误,因此您无法重新提出原始错误号2627.T-SQLerror handling与C#/ C ++error handling不相似,没有办法重新抛出原始错误例外。 存在这种限制的原因有很多,但足以说明了这一点并且你不能忽视它。

但是,只要它们高于50000范围,就不会限制自己的错误代码。 安装应用程序时,使用sp_addmessage注册自己的消息:

 exec sp_addmessage 50001, 16, N'A primary key constraint failed: %s'; 

在你的T-SQL中你会引发新的错误:

 @error_message = ERROR_MESSAGE(); raiserror(50001, 16, 1, @error_message; 

在C#代码中,您将查找错误号50001而不是2627:

 foreach(SqlError error in sqlException.Errors) { switch (error.Number) { case 50001: // handle PK violation case 50002: // } } 

我有一个更简单的答案,但遗憾的是这就是事情的方式。 T-SQLexception处理没有将seamlesly集成到CLRexception处理中。

这是我用来解决这个问题的代码(从CATCH调用)。 它在消息文本中嵌入原始错误号:

 CREATE PROCEDURE [dbo].[ErrorRaise] AS BEGIN DECLARE @ErrorMessage NVARCHAR(4000) DECLARE @ErrorSeverity INT SET @ErrorMessage = CONVERT(VARCHAR(10), ERROR_NUMBER()) + ':' + ERROR_MESSAGE() SET @ErrorSeverity = ERROR_SEVERITY() RAISERROR (@ErrorMessage, @ErrorSeverity, 1) END 

然后,您可以检查SqlException.Message.Contains("2627:") ,例如。

我想了一下这个话题,想出了一个我以前没见过的非常简单的解决方案,所以我想分享一下:

因为不可能重新抛出相同的错误,所以必须抛出一个很容易映射到原始错误的错误,例如通过向每个系统错误添加一个固定数字(如100000)。

将新映射的消息添加到数据库后,可能会抛出固定偏移量为100000的任何系统错误。

以下是用于创建映射消息的代码(对于整个SQL Server实例,这只需要执行一次。通过在这种情况下添加适当的偏移量,例如100000,避免与其他用户定义的消息发生冲突):

  DECLARE messageCursor CURSOR READ_ONLY FOR select message_id + 100000 as message_id, language_id, severity, is_event_logged, [text] from sys.messages where language_id = 1033 and message_id < 50000 and severity > 0 DECLARE @id int, @severity int, @lang int, @msgText nvarchar(1000), @withLog bit, @withLogString nvarchar(100) OPEN messageCursor FETCH NEXT FROM messageCursor INTO @id, @lang, @severity, @withLog, @msgText WHILE (@@fetch_status <> -1) BEGIN IF (@@fetch_status <> -2) BEGIN set @withLogString = case @withLog when 0 then 'false' else 'true' end exec sp_addmessage @id, @severity, @msgText, 'us_english', @withLogString, 'replace' END FETCH NEXT FROM messageCursor INTO @id, @lang, @severity, @withLog, @msgText END CLOSE messageCursor DEALLOCATE messageCursor 

这是用于引发新创建的错误代码的代码,该代码具有与原始代码的修复偏移量:

  SELECT @ErrorNumber = ERROR_NUMBER(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE() set @MappedNumber = @ErrorNumber + 100000; RAISERROR ( @MappedNumber, @ErrorSeverity, 1 ); 

有一个小警告:在这种情况下,您无法自己提供消息。 但是这可以通过在sp_addmessage调用中添加额外的%s或通过将所有映射的消息更改为您自己的模式并在raiseerror调用中提供正确的参数来规避。 最好的事情是将所有消息设置为相同的模式,如’%s(行:%d过程:%s)%s’,这样您就可以将原始消息作为第一个参数提供并附加实际过程和行以及您自己的消息消息作为其他参数。

在客户端中,您现在可以执行所有普通的exception处理,就像抛出原始消息一样,您只需要记住添加修复偏移量。 您甚至可以使用相同的代码处理原始和重新抛出的exception,如下所示:

 switch(errorNumber) { case 8134: case 108134: { } } 

因此,您甚至不必知道它是重新抛出还是原始错误,它始终是正确的,即使您忘记处理错误并且原始错误也会消失。

其他地方提到了一些增强function,用于提高您无法提出的消息或指出您无法使用的消息。 这些被遗漏在这里只是为了表明这个想法的核心。

由于SQL Server <2010无法重新抛出,正确的方法是使用事务并显式检查错误状态(比'C ++ / C#'更多地考虑'C')。

例如,SP的主体看起来像:

 CREATE PROCEDURE [MyProcedure] @argument1 int, @argument2 int, @argument3 int AS BEGIN DECLARE @return_code int; IF (@argument1 < 0) BEGIN RAISERROR ("@argument1 invalid", 16, 1); END; /* Do extra checks here... */ /* Now do what we came to do. */ IF (@@ERROR = 0) BEGIN BEGIN TRANSACTION; INSERT INTO [Table1](column1, column2) VALUES (@argument1, @argument2); IF (@@ERROR = 0) BEGIN INSERT INTO [Table2](column1, column2) VALUES (@argument1, @argument3); END; IF (@@ERROR = 0) BEGIN COMMIT TRANSACTION; SET @return_code = 0; END ELSE BEGIN ROLLBACK TRANSACTION; SET @return_code = -1; /* Or something more meaningful... */ END; END ELSE BEGIN SET @return_code = -1; END; RETURN @return_code; END; 

这是一个可以在托管环境中工作的解决方案(您可能无法创建自己的错误消息)。

虽然不如使用exception那么方便,但这种方法将保留系统错误代码。 它还具有(dis)的优点,即每次执行都能返回多个错误。

如果你只是想在第一个错误上弹出,要么插入return语句,要么你感觉很勇敢,GOTO是一个错误块(记住: Go To Statement认为有害 ),例如:

(这取自ASP.NET帐户管理的元素)

 CREATE PROCEDURE [MyProcedure] @argument1 int, @argument2 int, @argument3 int AS BEGIN DECLARE @return_code int = 0; DECLARE @tranaction_started bit = 0; /* Did we start a transaction? */ IF (@argument1 < 0) BEGIN RAISERROR ("@argument1 invalid", 16, 1); RETURN -1; /* Or something more specific... */ /* Alternatively one could: SET @return_code = -1; GOTO ErrorCleanup; */ END; /* Do extra checks here... */ /* Now do what we came to do. */ /* If no transaction exists, start one. * This approach makes it safe to nest this SP inside a * transaction, eg in another SP. */ IF (@@TRANCOUNT = 0) BEGIN BEGIN TRANSACTION; SET @transaction_started = 1; END; INSERT INTO [Table1](column1, column2) VALUES (@argument1, @argument2); IF (@@ERROR <> 0) BEGIN SET @return_code = -1; /* Or something more specific... */ GOTO ErrorCleanup; END; INSERT INTO [Table2](column1, column2) VALUES (@argument1, @argument3); IF (@@ERROR <> 0) BEGIN SET @return_code = -1; /* Or something more specific... */ GOTO ErrorCleanup; END; IF (@transaction_started = 1) BEGIN /* ONLY commit the transaction if we started it! */ SET @transaction_started = 0; COMMIT TRANSACTION; END; RETURN @return_code; ErrorCleanup: IF (@transaction_started = 1) BEGIN /* We started the transaction, so roll it back */ ROLLBACK TRANSACTION; END; RETURN @return_code; END; 

我使用以下模式:

 CreatePROCEDURE [dbo].[MyProcedureName] @SampleParameter Integer, [Other Paramaeters here] As Set NoCount On Declare @Err Integer Set @Err = 0 Declare @ErrMsg VarChar(300) -- ---- Input parameter value validation ------ Set @ErrMsg = ' @SampleParameter ' + 'must be either 1 or 2.' If @SampleParameter Not In (1, 2) Goto Errhandler -- ------------------------------------------ Begin Transaction Set @ErrMsg = 'Failed to insert new record into TableName' Insert TableName([ColumnList]) Values [ValueList]) Set @Err = @@Error If @Err <> 0 Goto Errhandler -- ------------------------------------------ Set @ErrMsg = 'Failed to insert new record into Table2Name' Insert TableName2([ColumnList]) Values [ValueList]) Set @Err = @@Error If @Err <> 0 Goto Errhandler -- etc. etc.. Commit Transaction Return 0 /* *************************************************/ /* ******* Exception Handler ***********************/ /* *************************************************/ /* *************************************************/ ErrHandler: If @@TranCount > 0 RollBack Transaction -- ------------------------------------ RaisError(@ErrMsg, 16, 1 ) If @Err = 0 Set @Err = -1 Return @Err 

谢谢你们的回答。 从重新抛出的exception消息中获取错误是我已经完成的事情。

@gbn我也很喜欢gbn的答案,但我会坚持这个答案,因为它是最好的,我在这里发帖,希望它对其他人也有用。

答案是在应用程序中使用事务。 如果我没有在存储过程中捕获exception,我将在SqlException对象中获取原始数字。 在应用程序中捕获原始exception后,我编写以下代码

 transaction.Rollback(); 

除此以外:

 transaction.Commit(); 

这比我最初预期的要简单得多!

http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.aspx