2009-12-10 23 views
7

Tôi đang sử dụng khối TRY CATCH trong quy trình được lưu trữ nơi tôi có hai lệnh INSERT.Máy chủ SQL: Theo dõi lại ngoại lệ với số ngoại lệ ban đầu

Nếu xảy ra sự cố, khối CATCH sẽ xử lý tất cả các thay đổi được thực hiện và hoạt động tốt, ngoại trừ một điều!

Ngoại lệ bị bắt bởi ứng dụng ASP.NET của tôi là SqlException với số 50000. Đây không phải là số gốc! (số tôi mong đợi là 2627)

Trong thuộc tính Thư của ngoại lệ, tôi có thể thấy số ngoại lệ ban đầu và thông báo được tạo thành.

Làm cách nào để có được số ngoại lệ ban đầu?

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

Hiện tại giá trị trả lại đã được sử dụng. Tôi đoán rằng tôi có thể sử dụng một tham số đầu ra để có được số ngoại lệ, nhưng đó là một ý tưởng tốt?

Tôi có thể làm gì để lấy số ngoại lệ? Cảm ơn

PS: Điều này là cần thiết vì tôi có hai hướng dẫn INSERT.

Trả lời

0

Cảm ơn các bạn đã trả lời. Nhận được lỗi từ thông điệp của excetpion tái ném là một cái gì đó tôi đã làm.

@gbn Tôi cũng thích câu trả lời gbn, nhưng tôi sẽ trả lời câu trả lời này vì nó là một trong những công trình tốt nhất và tôi đăng nó ở đây hy vọng nó cũng sẽ hữu ích cho người khác.

Câu trả lời là sử dụng giao dịch trong ứng dụng. Nếu tôi không nắm bắt được ngoại lệ trong thủ tục được lưu trữ, tôi sẽ nhận được số gốc trong đối tượng SqlException. Sau khi đánh bắt các ngoại lệ ban đầu trong việc áp dụng, tôi viết đoạn mã sau

transaction.Rollback(); 

Nếu không:

transaction.Commit(); 

Sẽ đơn giản hơn nhiều so với tôi trước hết là mong đợi!

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

+1

Tôi không nghĩ rằng bạn thậm chí cần giao dịch nếu bạn chỉ muốn bắt lỗi ban đầu: chỉ cần không sử dụng TRY/CATCH hoặc RAISERROR() ở phía T-SQL. Ngoài ra, cách tiếp cận TransactionScope hoạt động tốt cho các truy vấn đơn giản hoặc SP, nhưng có các trường hợp góc với các SP phức tạp hơn, nơi nó không làm điều đúng. – RickNZ

+0

@RickNZ, tôi quên đề cập đến trong câu hỏi rằng tôi đang sử dụng hai lệnh INSERT và đó là lý do tại sao giao dịch là cần thiết. Đóng góp tốt mặc dù! –

0

tôi sử dụng mô hình sau:

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

Xin lỗi, nhưng phải làm gì? Điều này vẫn ném 50000 và không được khuyến khích cho SQL Server 2005. OP cũng đề cập đến sử dụng TRY/CATCH đã – gbn

+0

@gbn Bạn có bất kỳ ý tưởng về cách chúng tôi có thể làm điều này? –

11

Bạn có thể có thể rethrow nó như thế này:

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

Tuy nhiên, nhiều khả năng bạn bị mất một mất ý nghĩa vì mục% s vv giữ chỗ trong các hàng sys.messages cho ERROR_NUMBER()

Bạn có thể làm điều gì đó như thế này để bao gồm số và tính lại thông báo gốc

.. 
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 ký tự đầu tiên là số gốc.

Nhưng nếu bạn có mã lồng nhau, thì bạn sẽ kết thúc bằng "0Văn bản lỗi".

Cá nhân, tôi chỉ xử lý các số Exception SQL để tách các lỗi của tôi (50000) khỏi lỗi Engine (ví dụ: các tham số bị thiếu) trong đó mã của tôi không chạy.

Cuối cùng, bạn có thể chuyển nó ra giá trị trả về.

Tôi hỏi một câu hỏi về vấn đề này: SQL Server error handling: exceptions and the database-client contract

8

Nếu bạn sử dụng BEGIN TRY/BEGIN CATCH trong T-SQL bạn mất động cơ ban đầu đưa ra ngoại lệ. Bạn không phải tự nâng cao lỗi hệ thống, vì vậy bạn không thể nâng lại số lỗi ban đầu 2627. Xử lý lỗi T-SQL không giống với xử lý lỗi C#/C++, không có cách nào để ném lại bản gốc ngoại lệ. Có một số lý do tại sao hạn chế này tồn tại, nhưng đủ để nói rằng đó là tại chỗ và bạn không thể bỏ qua nó.

Tuy nhiên, không có giới hạn để tăng mã lỗi của riêng bạn, miễn là chúng nằm trên phạm vi 50000.Bạn đăng ký thông điệp của riêng bạn sử dụng sp_addmessage, khi ứng dụng được cài đặt:

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

và trong T-SQL của bạn, bạn sẽ làm tăng lỗi mới:

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

Trong đoạn code C# bạn sẽ tìm kiếm các số lỗi 50001 thay vì 2627:

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

tôi whish đã có một câu trả lời đơn giản hơn, nhưng tiếc là đây là cách mọi việc. Xử lý ngoại lệ T-SQL không tích hợp seamlesly vào xử lý ngoại lệ CLR.

+1

Từ MSDN .... "Các khối CATCH có thể sử dụng RAISERROR để trả về lỗi đã gọi khối CATCH bằng cách sử dụng các chức năng hệ thống như ERROR_NUMBER và ERROR_MESSAGE để truy xuất thông tin lỗi ban đầu. @@ ERROR được đặt thành 0 theo mặc định cho các thư có mức độ nghiêm trọng từ 1 đến 10 " Vì vậy, rethrowing là hợp lệ cho 2005+ http://msdn.microsoft.com/en-us/library/ms178592.aspx – maguy

+0

Nó chỉ được phép tạo tin nhắn của người dùng> 50000 NHƯNG một trong những cũng có thể nâng cao thông báo lỗi từ 13000 đến 49999. –

1

Đây là mã tôi sử dụng để giải quyết vấn đề này (được gọi từ CATCH). Nó nhúng số lỗi ban đầu vào nội dung tin nhắn:

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 

Sau đó, bạn có thể kiểm tra SqlException.Message.Contains("2627:"), ví dụ.

+0

Điều đó cũng có thể là doable, nhưng bằng cách nào đó lấy một số từ một chuỗi với những thứ khác không có vẻ như thực hành tốt cho tôi. –

1

Tôi nghĩ về chủ đề này trong một thời gian và đã đưa ra một giải pháp rất đơn giản mà tôi không nhìn thấy trước vì vậy tôi muốn chia sẻ điều này:

Vì nó là không thể rethrow các cùng một lỗi, người ta phải ném một lỗi rất dễ dàng để ánh xạ tới lỗi ban đầu, ví dụ bằng cách thêm một số cố định như 100000 vào mọi lỗi hệ thống.

Sau những thông điệp mới được ánh xạ được bổ sung vào cơ sở dữ liệu có thể ném bất kỳ lỗi hệ thống với một cố định bù đắp của 100000.

Đây là đoạn mã để tạo các thông điệp ánh xạ (điều này đã được thực hiện chỉ có một . thời gian cho cả Instance SQL server Tránh đụng độ với những thông điệp người dùng định nghĩa khác bằng cách thêm một thích hợp bù đắp như 100000 trong trường hợp này):

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 

Và đây là đoạn code để nâng cao mã lỗi mới được tạo ra có một sửa chữa bù đắp từ mã gốc:

SELECT 
     @ErrorNumber = ERROR_NUMBER(), 
     @ErrorSeverity = ERROR_SEVERITY(), 
     @ErrorState = ERROR_STATE() 

    set @MappedNumber = @ErrorNumber + 100000; 

    RAISERROR 
     (
     @MappedNumber, 
     @ErrorSeverity, 
     1    
     ); 

Có một cảnh báo nhỏ: Bạn không thể tự cung cấp thư trong trường hợp này.Nhưng điều này có thể được phá vỡ bằng cách thêm một% s bổ sung trong cuộc gọi sp_addmessage hoặc bằng cách thay đổi tất cả các thư được ánh xạ thành mẫu của riêng bạn và cung cấp các tham số đúng trong lệnh gọi raiseerror. Điều tốt nhất để thiết lập tất cả các thông báo giống như '% s (dòng:% d thủ tục:% s)% s', vì vậy bạn có thể cung cấp thông điệp ban đầu làm tham số đầu tiên và nối thêm thủ tục và dòng thực và của riêng bạn thông báo như các thông số khác.

Trong ứng dụng khách, bây giờ bạn có thể thực hiện tất cả xử lý ngoại lệ thông thường như các thư gốc sẽ bị ném, bạn chỉ phải nhớ thêm bù đắp sửa lỗi. Bạn thậm chí có thể xử lý ngoại lệ ban đầu và rethrown với cùng mã như thế này:

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

Vì vậy, bạn thậm chí không cần phải biết nếu nó là một rethrown hoặc lỗi ban đầu, đó là luôn luôn đúng, ngay cả khi bạn quên xử lý lỗi của bạn và lỗi gốc bị trượt qua.

Có một số tính năng nâng cao được đề cập ở những nơi khác liên quan đến việc nâng cao các thư bạn không thể nâng cao hoặc cho biết bạn không thể sử dụng. Những người bị bỏ lại ở đây để chỉ cho thấy cốt lõi của ý tưởng.

1

Vì SQL Server < 2010 không có khả năng quay lại, đúng cách để thực hiện việc này là sử dụng giao dịch và kiểm tra rõ ràng trạng thái lỗi (suy nghĩ 'C' hơn 'C++/C#') .

Ví dụ: cơ thể của SP sẽ giống như thế:

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; 

Đây là một giải pháp mà sẽ làm việc trong một môi trường lưu trữ (nơi bạn có thể sẽ không có khả năng để tạo ra các thông báo lỗi của riêng bạn).

Mặc dù không thuận tiện như sử dụng ngoại lệ, cách tiếp cận này sẽ bảo toàn các mã lỗi hệ thống. Nó cũng có (dis) lợi thế của việc có thể trả lại nhiều lỗi cho mỗi thực thi.

Nếu bạn chỉ muốn ném bom-out về lỗi đầu tiên, hoặc chèn tuyên bố trả lại hoặc nếu bạn đang cảm thấy dũng cảm, một GOTO một khối lỗi (nhớ: Go To Statement Considered Harmful), ví dụ:

(Elements này lấy từ quản lý tài khoản 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; 
Các vấn đề liên quan