2010-10-12 58 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。该过程要求您生成常量列表,并且要执行的过程有点长。但会很容易维护。