【问题标题】:SqlException conversion to a custom Exception handlerSqlException 转换为自定义异常处理程序
【发布时间】:2010-10-12 14:11:56
【问题描述】:

我有一个现有的应用程序,它使用 MS SQL 存储过程来执行一些业务规则。当检测到错误时,使用 RAISERROR 将其作为异常引发回我的 .Net 应用程序。

.Net 应用程序然后可以使用 Try/Catch 块来捕获异常并执行业务逻辑。问题是在单个存储过程中验证了多个业务规则。这可能会引发不同的异常。捕获这些 SQL 异常并将其转换为自定义 .Net 异常处理程序的最佳方法是什么。

例如,我的存储过程可能会为 RuleA 和 RuleB 抛出异常。在我的 .Net 代码中,我只能捕获 SqlException。我的 RuleA 或 RuleB 的自定义错误消息在 SqlException 内部异常中返回。我可以解析消息字符串,但这很丑陋,如果有人更改了存储过程中的实现。我的逻辑不会接受它。

将通用 SqlException 转换为 MyRuleAException 或 MyRuleBException 的首选方法是什么?

【问题讨论】:

    标签: .net exception-handling


    【解决方案1】:

    通常的方法是在您的 .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, @msgtext=@l_error, @replace='replace'
     RAISERROR(50001, 16, 1)
    

    50001 表示将在SqlException.Number 中的错误号。

    【讨论】:

    • 再看一下,似乎现有代码在 SQL 代码中使用 msg_str 来引发错误,而不是 msg_id。根据 RAISERROR 文档“指定 msg_str 时,RAISERROR 会引发错误消息,错误编号为 50000。”我想我们必须将存储过程中的实现切换为基于 ID 而不是基于字符串。看起来我们必须使用“sp_addmessage”将错误号预加载到数据库中。
    • 我认为 dcp 是在说你不应该定义 msg_str 这会让 msg_id 成为你想要的任何东西。
    • @Jay - Abe 是对的,按照我调用 RAISEERROR 的方式,我传递的是 50001,而不是消息字符串。
    • 谢谢,这似乎也是我阅读的最佳方式。唯一的缺点是错误必须存在于 sys.messages 中。我的 SQL 实例中有几个数据库。看来,如果试图对这种方法进行标准化,则需要在所有开发人员之间就所有数据库进行通信,这可能容易出错。另外,sp_addmessage 目前在我的环境中被锁定。我们必须让管理员提前加载任何错误代码并确保数字的唯一性。从要遵循的模式的角度来看是好的,坏的 = 管理员需要做更多的工作 :)
    【解决方案2】:

    我同意 dcp。该过程需要您生成常量列表,并且要实现的过程有点长。但是会很容易维护。

    【讨论】:

      【解决方案3】:

      您可以在引发错误时指定msg_id 吗?如果是这样,我相信这可以在 SqlException.Number 成员中找到。然后你可以做一个 if/else 。我会确保在存储过程中很好地记录它。

      更新:

      仔细检查后,我认为您最好在调用 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){}
      }
      

      【讨论】:

      • 只有 11 或更高的严重级别会导致代码在 SQL 中留下 BEGIN CATCH 块。没有系统管理员角色的人只能使用 0-18 的严重性。这样只会给我留下 8 (18-10) 个可能的自定义错误:(
      • 很好,我没有意识到这一点。一个存储过程中是否有超过 8 个 RAISERROR?
      猜你喜欢
      • 2011-07-13
      • 2015-10-18
      • 2023-03-04
      • 2023-03-07
      • 1970-01-01
      • 2015-12-05
      • 1970-01-01
      • 1970-01-01
      • 2020-09-26
      相关资源
      最近更新 更多