如何捕捉数据库错误并将其转化为对业务层有意义的信息?

Posted

技术标签:

【中文标题】如何捕捉数据库错误并将其转化为对业务层有意义的信息?【英文标题】:How to catch DB errors and translate them into meaningful information for the business layer? 【发布时间】:2011-11-15 19:01:57 【问题描述】:

通常我必须在数据库中插入一些数据,但无法插入,因为表有约束阻止我这样做。在我正在开发的应用程序中,一些业务规则(例如“没有两个具有相同 id 类型和编号的人”或“XXXX 产品已注册”)通过 UNIQUE 或复合键和其他机制强制执行。虽然我知道 DBMS 会抛出错误消息(如 ORA-6346 或),但我不知道如何在 .net 4.0 中捕获这些错误并将它们转换为对业务层有意义的错误。

举个例子:我见过一种插入机制,它询问数据库寄存器是否已经存在,如果不存在则继续插入数据。我只想使用查询并捕获数据库约束冲突错误来执行此操作,因为在我看来,第一种方法效率非常低(数据库可以提醒您有关重复的错误)。

我怎样才能实现这样的东西?

注意:我认为可以从数据库中捕获异常并使用其 ORA-xxxx 代码来尝试找出发生了什么。如果错误消息显示哪个约束(...的名称)已被破坏,我不记得很准确,但业务层代码可以包含带有约束名称的常量,并从中知道发生了什么。

【问题讨论】:

这听起来很像用 try/catch 包装方法调用,而不是在调用之前验证参数 如果你不想先检查register是否已经存在,你可以先尝试创建它,然后如果从db抛出异常你可以检查RegisterExists()是否返回true,为确定这是问题的根源,而不是 db 根本不工作。如果已经存在的情况非常罕见,那么您就不会经常受到这种性能影响。 【参考方案1】:

您应该在这里做什么取决于您的系统架构,以及您对业务逻辑布局的态度。

许多系统架构师更喜欢将数据库用作哑数据存储,并在中间/应用层实现您所说的错误处理和完整性检查类型。这是一种非常有效的方法,特别适用于需要定期较小版本的系统,其中业务逻辑会定期更改(在周中重新分发可执行文件比协调数据库版本更容易),以及数据模型相当简单。

另一种方法是将一些定义良好的半永久性业务逻辑放入数据库层。当数据模型更复杂并且您拥有优秀的 DBA 时,这一点尤其强大! ;)

我个人的意见是,企业数据库应该对其自身的完整性负责,因此我更喜欢在数据库层中使用逻辑来确保这一点 - 消除非数据库代码版本中引入的错误的任何漏洞。因此,在您的具体示例中,我肯定会捕获错误并将其有意义地报告给您的应用程序层。

Oracle 支持使用名称异常捕获各种类型的错误,允许您以有意义的方式向应用程序提出这些异常。例如:

PROCEDURE test() AS
  b VARCHAR2;
BEGIN

  -- if the following row exists, then DUP_VAL_ON_INDEX will be thrown 
  -- (assuming there is a primary key constraint)        

  INSERT INTO table(a,b,c)
  VALUES(1,2,3);  

  -- if there is no matching record, NO_DATA_FOUND will be thrown

  SELECT a
  INTO b
  FROM TABLE
  WHERE c = 'blah';  

EXCEPTION   -- both types of exception can be caught and embellished 
  WHEN DUP_VAL_ON_INDEX THEN
    raise_application_error(-20570, 'Attempted to insert a duplicate value', TRUE);
  WHEN NO_DATA_FOUND THEN
    raise_application_error(-20571, 'No matching row in table for value:' || 'blah', TRUE);
  WHEN OTHERS THEN
  rollback
END test;

您可以在这里找到更多信息:http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/errors.htm

希望这会有所帮助..

【讨论】:

你的想法或多或少像@mootinator提议的那样?...我不知道给谁支票...!都说合理的解决方案...:S 如有疑问,请参考大众意见 ;)【参考方案2】:

有几种方法,以下是我要做的大致内容:

    让错误从 DB 回调中冒泡到您的托管代码中。 使用组件检查SQL提供的错误信息,识别出对应的“用户/业务层友好”信息。

我同意 Mellamokb 的观点,即可以在存储过程中完成错误处理,但这并不完全适合您的场景,因为您特别希望提供业务层可以理解的东西——根据定义,数据层永远不应该知道。

对于#2,MS Enterprise Libraries 有一个错误处理块,(我认为)允许您通过配置处理这类事情;否则,它可能会让你接近。

【讨论】:

【参考方案3】:

我最近也在考虑同样的事情。我做了一个扩展方法,它从SqlException 获取消息并将其转换为对最终用户更有用的东西,使用正则表达式从错误消息中提取有用信息,并使用 String.Format 将该信息放入新消息中.

我使用第二个字典查找通过正则表达式找到的约束名称,并将其翻译成违反约束的英文描述。

此 SQL 错误消息:

Violation of UNIQUE KEY constraint 'uniq_ticket'. Cannot insert duplicate key in object 'dbo.TicketHeader'. The statement has been terminated.

返回这个结果:

Save to table dbo.TicketHeader failed: Ticket number must be unique.

我想它对于 Oracle 发送的异常的工作方式非常相似。

    public static class SqlExceptionExtension
    
        private static readonly Dictionary<string, string> Messages;
        private static readonly Dictionary<string, string> Constraints;
        static SqlExceptionExtension()
        
            Messages = new Dictionary<string, string> @"Violation of UNIQUE KEY constraint '(?<Constraint>.*)'. Cannot insert duplicate key in object '(.*)'. The statement has been terminated.", "Save to table 2 failed: 0";
            Constraints = new Dictionary<string, string>   "uniq_ticket", "Ticket number must be unique."  ;
        
        public static string BusinessLayerMessage(this Exception e)
        
            foreach(var reg in Messages.Keys)
            
                var match = Regex.Match(e.Message, reg);
                if (match.Success)
                
                    string friendlyConstraint = "";
                    if (match.Groups["Constraint"] != null)
                    
                        friendlyConstraint = Constraints[match.Groups["Constraint"].Value] ??
                                             match.Groups["Constraint"].Value;
                    
                    var groups = match.Groups.Cast<Group>().Select(x => x.Value);
                    var strings = new [] friendlyConstraint;
                    return String.Format(Messages[reg], strings.Concat(groups).ToArray());
                
            
            return "Unexpected Database error.";
        
    

我想您可以在您的例外中包含的个人 SqlErrorOracleErrors 上执行此操作以获得更可靠的结果,这只是一个概念证明。

【讨论】:

【参考方案4】:

我知道并使用过的一种方法是自己执行相同的验证并将有用的错误代码返回给您的应用程序。因此,例如,如果您有一个将记录插入数据库的存储过程,它还应该检查约束是否全部满足,如果不满足,则返回适当的错误代码:

(pseudo-sql)
function create-user
    @username, @password, @name, @email
as

if @username already exists return 1  --duplicate username
if @email already exists return 2   --duplicate email

insert user values (@username, @password, @name, @email)
return 0   -- success

【讨论】:

如果我们在谈论 oracle,我们可以先尝试插入,并且只有在它失败时执行额外的查询以找出实际错误的来源,它会比在插入之前执行几个选择更高效 【参考方案5】:

如果您正在寻找灵感,请查看 NHibernate 如何通过其ISQLExceptionConverter 界面处理此问题。你可以看到一个示例实现here。

【讨论】:

以上是关于如何捕捉数据库错误并将其转化为对业务层有意义的信息?的主要内容,如果未能解决你的问题,请参考以下文章

理解管理信息系统

将 JSON 字符串转换为有意义的完整数据以进行解析

理解管理信息系统

Laravel 4 - 如何获得有意义的错误

我应该如何从会话中检索登录用户并将其传递给我的业务层?

如何在 web3.js 中正确使用还原原因在 UI 中显示有意义的错误消息