捕获数据库约束错误的最佳方法

本文关键字:最佳 方法 错误 约束 数据库 | 更新日期: 2023-09-27 18:05:16

我正在调用一个存储过程,从c#中插入数据到sql server数据库。我对表有许多约束,如唯一列等。目前我有以下代码:

try
{
   // inset data
}
catch (SqlException ex)
{
    if (ex.Message.ToLower().Contains("duplicate key"))
    {
        if (ex.Message.ToLower().Contains("url"))
        {
            return 1;
        }
        if (ex.Message.ToLower().Contains("email"))
        {
            return 2;
        }
    }
    return 3;
}
在c#中插入数据之前,或者在存储过程中,或者让异常发生并像上面那样处理,检查列是否唯一是更好的做法吗?我不是上述的粉丝,但在寻找这个领域的最佳实践。

捕获数据库约束错误的最佳方法

我认为数据库约束是最后的手段。(也就是说,无论如何,它们都应该作为维护数据完整性的备份方式出现在你的模式中。)但我想说的是,在保存到数据库之前,数据应该是有效的。如果没有其他原因,那么因为提供关于无效输入的反馈是UI关注的问题,并且数据有效性错误真的不应该每次都在整个层堆栈上上下浮动。

此外,您希望对数据的形状做出许多类型的断言,而这些断言不能轻易地使用约束来表示。(例如,一个顺序的状态转换。"订单只能从PAID转到SHIPPED"或更复杂的场景。)也就是说,你需要使用基于过程语言的检查,这些检查会复制更多的业务逻辑,然后让它们报告一些错误代码,并在你的应用程序中包含更多的复杂性,只是为了在模式定义中进行所有的数据验证。

验证本来就很难放在应用程序中,因为它涉及UI 与模型模式耦合,但我倾向于在UI附近进行验证。

我看到了两个问题,这是我的看法…

数据库约束好吗?对于大型系统,它们是独立的。大多数大型系统都有多个前端,并且并不总是使用可以共享中间层或UI数据检查逻辑的兼容语言。它们也可能只使用Transact-SQL或PL/SQL进行批处理。在前端重复检查是可以的,但在多用户应用程序中,真正检查唯一性的唯一方法是插入记录并查看数据库显示的内容。外键约束也是一样,只有当你尝试插入/更新/删除时,你才会真正知道。

应该允许抛出异常,还是应该替换返回值?下面是问题中的代码:

    try
    {
       // inset data
    }
    catch (SqlException ex)
    {
        if (ex.Message.ToLower().Contains("duplicate key"))
        {
            if (ex.Message.ToLower().Contains("url"))
            {
                return 1; // Sure, that's one good way to do it
            }
            if (ex.Message.ToLower().Contains("email"))
            {
                return 2; // Sure, that's one good way to do it
            }
        }
        return 3; // EVIL! Or at least quasi-evil :)
    }

如果你能保证调用程序实际上会根据返回值采取行动,我认为return 1return 2最好留给你自己判断。对于这样的情况(例如DuplicateEmailException),我更喜欢重新抛出一个自定义异常,但这只是我的想法——返回值也可以做到这一点。毕竟,消费者类可以像忽略返回值一样容易地忽略异常。

我反对return 3。这意味着出现了意外异常(数据库关闭、连接不良等)。这里有一个未指定的错误,唯一的诊断信息是:"3"。想象一下,在SO上发布一个问题,上面写着我试图插入一行,但系统说'3'。请建议。它将在几秒钟内关闭:)

如果你不知道如何处理数据类中的异常,那么数据类的消费者就无法处理它。此时,您已经非常紧张了,所以我说记录错误,然后尽可能优雅地退出,并显示"意外错误"消息。

我知道我对意外异常有点抱怨,但我处理过太多的支持事件,程序员只是后遗症数据库异常,当意外出现时,应用程序要么静默失败,要么下游失败,留下零诊断信息。非常调皮。

我更喜欢在将数据扔给SQL Server并让约束冒泡产生错误之前检查潜在违规的存储过程。原因与性能有关:

  • 不同错误处理技术对性能的影响

  • 在进入SQL Server TRY和CATCH逻辑之前检查潜在的约束违规

有些人认为数据库层的约束是不必要的,因为你的程序可以做任何事情。我不会完全依赖于您的c#程序来检测重复的原因是,人们会找到不通过您的c#程序就能影响数据的方法。你以后可以介绍其他的节目。您可能会让人们编写自己的脚本或直接与数据库交互。你真的想因为他们不遵守你的业务规则而让桌子不受保护吗?我也不认为c#程序应该只是把数据扔到桌子上,然后期待最好的结果。

如果你的业务规则改变了,你真的想要重新编译你的应用程序(或所有的多个应用程序)吗?我想这取决于您的数据库保护得有多好,以及您的业务规则更改的可能性/频率。

我是这样做的:

public class SqlExceptionHelper
{
    public SqlExceptionHelper(SqlException sqlException)
    {
        // Do Nothing.
    }
    public static string GetSqlDescription(SqlException sqlException)
    {
        switch (sqlException.Number)
        {
             case 21:
                 return "Fatal Error Occurred: Error Code 21.";
             case 53:
                 return "Error in Establishing a Database Connection: 53.";
             default
                 return ("Unexpected Error: " + sqlException.Message.ToString());
         }
     }
}

这允许它是可重用的,它将允许您从SQL中获得错误代码。

然后实现:

public class SiteHandler : ISiteHandler
{
     public string InsertDataToDatabase(Handler siteInfo)
     {
          try
          {
              // Open Database Connection, Run Commands, Some additional Checks.
          }
          catch(SqlException exception)
          {
             SqlExceptionHelper errorCompare = new SqlExceptionHelper(exception);
             return errorCompare.ToString();
          }
     }
}

然后它为常见的错误提供一些特定的错误。但如上所述;您确实应该确保在将数据输入数据库之前对其进行了测试。这样就不会出现不匹配的约束。

希望它能给你指明一个好的方向。

取决于你想做什么。一些值得考虑的事情:

  • 你想在哪里处理你的错误?我建议尽可能接近数据。
  • 你想知道谁的错误?你的用户是否需要知道"你已经使用了那个ID"?
  • 等。

同样——约束可以是好的——我不完全同意millimoose在这一点上的回答——我的意思是,我在应该是这样的/更好的性能理想——但实际上,如果你不能控制你的开发者/qc,特别是当它涉及到强制规则可能会破坏你的数据库(或以其他方式,打破依赖对象,如报告等,如果一个重复的键出现在某处,您需要一些屏障来防止(例如)重复的键项。