2010-10-12 59 views
3

我有一個使用MS SQL存儲過程來強制執行某些業務規則的現有應用程序。當檢測到錯誤時,它將作爲例外使用RAISERROR返回到我的.Net應用程序。將SqlException轉換爲自定義異常處理程序

然後.Net應用程序可以使用Try/Catch塊來捕獲異常和執行以及業務邏輯。問題是在單個存儲過程中有多個業務規則已經過驗證。這可能會引發不同的例外。捕獲這些SQL異常並將它們轉換爲自定義.Net異常處理程序的最佳方法是什麼?

例如,我的存儲過程可能會爲RuleA和RuleB引發異常。在我的.Net代碼中,我只能捕獲SqlException。 RuleA或RuleB的自定義錯誤消息在SqlException內部異常中返回。我可以解析消息字符串,但這是醜陋的,如果有人更改存儲過程中的實現。我的邏輯不會拿起它。

將泛型SqlException轉換爲MyRuleAException或MyRuleBException的首選方法是什麼?

回答

3

正常情況下,它的方法是在.Net代碼中定義錯誤常量,然後檢查異常處理代碼中的值。你可以使用常量使代碼更易讀,這樣的事情:

/// <summary> 
/// Represents the error code returned from stored procedure when entity could not be found. 
/// </summary> 
private const int SQL_ERROR_CODE_ENTITY_NOT_FOUND = 50001; 

/// <summary> 
/// Represents the error code returned from stored procedure when entity to be updated has time mismatch. 
/// </summary> 
private const int SQL_ERROR_CODE_TIME_MISMATCH = 50002; 

/// <summary> 
/// Represents the error code returned from stored procedure when a persistence exception occurs (ex. 
/// billing flag is invalid, child records exist which prevent a delete, etc.). 
/// </summary> 
private const int SQL_ERROR_CODE_PERSISTENCE_ERROR = 50003; 

然後,您可以處理這樣的例外,它使你的代碼更具可讀性和可維護性:

if (e.InnerException is SqlException) 
    { 
     // verify exception code from SP and throw proper exception if required 
     var sqlException = (SqlException)e.InnerException; 
     if (sqlException.Number == SQL_ERROR_CODE_ENTITY_NOT_FOUND) 
     { 
      e = new EntityNotFoundException(e.Message, e); 
     } 
     else if (sqlException.Number == SQL_ERROR_CODE_TIME_MISMATCH) 
     { 
      e = new EntityTimestampMismatchException(e.Message, e); 
     } 
     else if (sqlException.Number == SQL_ERROR_CODE_PERSISTENCE_ERROR) 
     { 
      e = new EntityServicePersistenceException(e.Message, e); 
     } 
    } 

這在我看來可以做得很乾淨,但它仍然可以,因爲你在一個地方定義了錯誤代碼,所以如果有什麼改變,你只需改變一個常量。

,提高了錯誤,你可以做在T-SQL是這樣的:

-- record wasn't found, raise an error 
DECLARE @l_error NVARCHAR(1000) 
SET @l_error = 'Record with ' + @p_IdFieldName + ' = ' + CONVERT(VARCHAR(128), @p_id) 
    + ' does not exist in table [' + @p_TableName + ']' 
EXEC sp_addmessage @msgnum=50001, @severity=16, @[email protected]_error, @replace='replace' 
RAISERROR(50001, 16, 1) 

的50001表示將在SqlException.Number錯誤編號。

+0

仔細研究一下,看起來現有的代碼在SQL代碼中使用msg_str來引發錯誤,而不是msg_id。根據RAISERROR文檔「當指定msg_str時,RAISERROR產生錯誤消息,錯誤號爲50000.」我想我們必須將存儲過程中的實現切換爲基於ID而不是基於字符串的實現。還看起來我們將不得不使用「sp_addmessage」將錯誤號預加載到數據庫中。 – Jay 2010-10-12 14:30:50

+0

我想dcp是說你不應該定義'msg_str',它可以讓'msg_id'成爲你想要的東西。 – 2010-10-12 14:34:44

+0

@Jay - Abe是對的,我調用了RAISEERROR的方式,我傳遞了50001,而不是消息字符串。 – dcp 2010-10-12 14:43:44

0

當你引發錯誤時,你能指定一個msg_id嗎?如果是這樣,我相信這可以在SqlException.Number成員中找到。然後你可以做一個if/else。我只是確保在存儲過程中很好地記錄它。

UPDATE:

在仔細檢查我想你可能會更好指定不同的誤差水平,當你調用RAISERROR,然後檢查通過SqlException.Class成員這一水平。例如:

--Rule A 
RAISERROR (N'Rule A violation.', -- Message text. 
      10, -- Severity, 
      1, -- State) 

--Rule B 
RAISERROR (N'Rule B violation.', -- Message text. 
      9, -- Severity, 
      1, -- State) 

--Rule C 
RAISERROR (N'Rule C violation.', -- Message text. 
      8, -- Severity, 
      1, -- State) 

然後在代碼:

catch(SqlException qex) 
{ 
    if(qex.Class == 10){} 
    else if(qex.Class == 9){} 
    else if(qex.Class == 8){} 
} 
+0

只有11或更高的嚴重級別導致代碼在SQL中留下BEGIN CATCH塊。只有沒有系統管理員角色的人才能使用0-18的嚴重程度。所以只留下了8(18-10)個可能的自定義錯誤:( – Jay 2010-10-12 14:37:12

+0

)好,我沒有意識到這一點。在一個存儲過程中你有超過8個RAISERROR嗎? – 2010-10-12 14:44:34

1

我同意DCP。該過程要求您生成常量列表,並且要執行的過程有點長。但會很容易維護。