2009-12-10 70 views
7

我使用存儲過程的try catch塊,我有兩個INSERT指令。SQL服務器:重新拋出異常與原來的異常號

如果出現錯誤,catch塊需要回滾所做的所有更改的關心和它工作得很好,除了一件事!

我的ASP.NET應用程序中捕捉到的異常是數50000。SQLEXCEPTION這不是原來的號碼! (我期待的數目是一個2627)

在異常我可以看到格式化原始異常號和消息的消息屬性。

我怎樣才能得到原始的例外號碼?

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(); 
    } 
} 

現在,返回值已被使用。我想我可以使用輸出參數來獲取例外數字,但這是一個好主意嗎?

我能做些什麼來獲取異常號?由於

PS:這是必要的,因爲我有兩個INSERT指令。

回答

0

謝謝你們的答案。從重新拋出的excelpion的信息中獲得錯誤是我已經完成的事情。

@gbn我也喜歡gbn的答案,但我會堅持這個答案,因爲它是最好的一個,我在這裏發佈它希望它也會對其他人有用。

答案是在應用程序中使用事務。如果我沒有捕獲存儲過程中的異常,我將在SqlException對象中獲取原始數字。在應用程序中捕獲原始異常後,我寫了下面的代碼

transaction.Rollback(); 

否則:

transaction.Commit(); 

這是簡單得多,比我預期的首先!

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

+1

我不認爲你甚至需要事務,如果你只是想捕獲原始錯誤:只是不要在T-SQL端使用TRY/CATCH或RAISERROR()。此外,TransactionScope方法對於簡單查詢或SP來說工作得很好,但是有些情況下更復雜的SP不適合做正確的事情。 – RickNZ 2009-12-11 13:53:06

+0

@RickNZ,我忘了在提問中提到我使用了兩條INSERT指令,這就是爲什麼需要這個事務。雖然貢獻良多! – 2009-12-12 16:28:43

0

我用下面的模式:

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 
+0

對不起,但要做什麼?這仍然會拋出50000,不推薦用於SQL Server 2005.OP還提到使用TRY/CATCH已經 – gbn 2009-12-10 18:35:05

+0

@gbn您對我們如何做到這一點有任何想法嗎? – 2009-12-10 18:39:42

11

你也許可以重新拋出這樣的:

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

但是,你最有可能失去失去了意義,因爲%S的等佔位符在sys.messages行的ERROR_NUMBER()

你可以做這樣的事情,包括數量和重新拋出原始消息

.. 
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個字符是原始數字。

但是,如果你有嵌套的代碼,那麼你會用「0錯誤文本」而告終。

就個人而言,我只能用SQL異常處理的數字在我的代碼不能運行分開我從引擎錯誤錯誤(50000)(例如缺少參數)。

最後,你可以通過它的返回值。

我問這個問題:SQL Server error handling: exceptions and the database-client contract

8

如果使用BEGIN TRY/CATCH BEGIN在T-SQL你失去原來的引擎引發的異常。您不應該手動引發系統錯誤,因此您無法重新提高原始錯誤號2627.T-SQL錯誤處理與C#/ C++錯誤處理並不相似,因此無法重新引發原始錯誤例外。存在這種限制的原因有很多,但足以說明這一點,並且你不能忽視它。

但也有以提高自己的錯誤代碼,只要他們是高於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-SQL異常處理不會將無情地集成到CLR異常處理中。

+1

來自MSDN ....「CATCH塊可以使用RAISERROR通過使用諸如ERROR_NUMBER和ERROR_MESSAGE之類的系統函數來重新調用調用CATCH塊的錯誤來檢索原始錯誤信息。默認情況下,@@ ERROR被設置爲0嚴重程度從1到10.「所以rethrowing是有效的2005+ http://msdn.microsoft.com/en-us/library/ms178592.aspx – maguy 2013-08-19 15:48:55

+0

它只允許創建用戶消息> 50000但是也可以引發錯誤信息從13000到49999. – 2017-05-04 19:22:55

1

下面是我用來解決此問題的代碼(從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:"),例如。

+0

這也是可行的,但以某種方式從字符串中檢索一個數字與其他事物似乎對我來說不是好習慣。 – 2009-12-12 16:27:44

1

我想過這個話題了一會兒,用一個非常簡單的解決方案,我沒有看到過上來,所以我想分享這個:

因爲它是不可能重新拋出同樣的錯誤,人們必須拋出一個很容易映射到原始錯誤的錯誤,例如通過爲每個系統錯誤添加一個固定的數字,例如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(line:%d procedure:%s)%s',這樣您可以提供原始消息作爲第一個參數,並附加實際程序和行以及您自己的消息作爲其他參數。

在客戶端中,您現在可以執行所有普通異常處理,例如原始消息將被拋出,您只需要記住添加修訂偏移。你甚至可以處理原來並重新拋出異常使用相同的代碼是這樣的:

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

所以你甚至不必知道它是一個重新拋出還是原來的錯誤,它總是正確的,即使你忘了處理您的錯誤,並且原始錯誤通過。

在其他地方提到了一些有關提升您無法提出或聲明不能使用的消息的增強功能。那些被遺漏在這裏僅僅展示了這個想法的核心。

1

與SQL Server < 2010不必重新擲的能力,要做到這一點正確的方法是使用一個交易並明確檢查錯誤狀態(想更多的「C」比'C++/C#) 。

E.g. 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; 

這是將在託管環境中(你可能不會能夠創建自己的錯誤消息)工作的解決方案。

雖然不如使用異常方便,但此方法將保留系統錯誤代碼。它還具有能夠在每次執行時返回多個錯誤的(不)優點。

如果你只想轟炸出的第一個錯誤,無論是插入return語句或如果你感覺勇敢,一個GOTO錯誤塊(記住:Go To Statement Considered Harmful),例如:

(這個元素取自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, e.g. 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;