【问题标题】:best way to catch database constraint errors捕获数据库约束错误的最佳方法
【发布时间】:2013-03-22 18:43:00
【问题描述】:

我正在调用一个存储过程,该过程将数据从 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# 或存储过程中插入数据之前检查列是否唯一等是否更好,或者让异常发生并像上面一样处理?我不是上述内容的粉丝,但正在寻找该领域的最佳实践。

【问题讨论】:

标签: c# asp.net sql sql-server


【解决方案1】:

我将数据库约束视为最后的手段。 (即,无论如何,它们应该作为维护数据完整性的备份方式出现在您的架构中。)但我想说,在您尝试将其保存在数据库中之前,数据应该真的是有效的。。如果没有其他原因,那么因为提供有关无效输入的反馈是 UI 问题,并且数据有效性错误确实不应该每次都在整个层堆栈上上下冒泡。

此外,您想要对数据的形状做出多种断言,这些断言无法使用约束轻松表达。 (例如,订单的状态转换。“订单只能从 PAID 转到 SHIPPED”或更复杂的场景。)也就是说,您需要使用涉及基于过程语言的检查,这些检查会重复更多您的业​​务逻辑,然后让它们也报告某种错误代码,并在您的应用程序中包含更多复杂性,只是为了在架构定义中进行所有数据验证。

验证本质上很难放置在应用程序中,因为它涉及 UI 并且与模型架构耦合,但我倾向于在 UI 附近进行。

【讨论】:

  • 我完全同意。约束错误表明您的代码中存在错误 - 您的代码允许用户输入数据库拒绝的数据。
  • 您正在处理的代码是约束引用的数据的唯一入口点吗?如果是这样,那么绝对依赖您的代码层来定义可以存储哪些值。我问的唯一原因是因为我在遗留系统上工作过,在这些系统中,表的多个入口点不一定在一组代码或另一组代码中定义(参见:sprawling classic ASP applications),并且有一个检查约束确保遗留代码库不违反参照完整性或唯一性约束是很有价值的。
  • 完全同意——尽管约束应该存在,只是为了保护上层验证失败的情况。我也对此进行了一些性能测试:sqlperformance.com/2012/08/t-sql-queries/error-handlingmssqltips.com/sqlservertip/2632/…
  • @antinescience “遗留系统”总是一个巨大的痛苦。在您描述的场景中,理想的解决方案是将对数据的访问封装在处理验证的一致服务层后面,并提供一组您可以控制并且客户端可以正常处理的错误代码。如果不存在这样的事情,那么使用约束就可以了,我相信我将其称为“最后的手段”解决方案。
  • @millimoose 哦,绝对。只是确保有一种意识,不幸的是,有时您无法完全控制何时进入数据库。 ;]
【解决方案2】:

我在这里看到两个问题,这是我的看法...

数据库约束好吗?对于大型系统,它们是不可或缺的。大多数大型系统都有不止一个前端,而且并不总是使用可以共享中间层或 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”。请告知。它会在几秒钟内关闭:)

如果您不知道如何处理数据类中的异常,则数据类的消费者 无法处理它。在这一点上,你已经非常紧张了,所以我说记录错误,然后尽可能优雅地退出,并显示“意外错误”消息。

我知道我对意外异常有点抱怨,但我处理了太多支持事件,程序员只是对数据库异常进行后续处理,当出现意外情况时,应用程序要么静默失败,要么下游失败,留下零诊断信息.很淘气。

【讨论】:

    【解决方案3】:

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

    有些人会主张数据库层的约束是不必要的,因为你的程序可以做任何事情。我不会仅仅依靠您的 C# 程序来检测重复项的原因是人们会找到影响数据的方法,而无需通过您的 C# 程序。您可以稍后介绍其他程序。您可能让人们编写自己的脚本或直接与数据库交互。您是否真的想要让桌子不受保护,因为他们不遵守您的业务规则?而且我认为 C# 程序也不应该只是将数据扔到桌面上并希望获得最好的结果。

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

    【讨论】:

      【解决方案4】:

      我做了这样的事情:

      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();
                }
           }
      }
      

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

      希望它能为您指明一个好的方向。

      【讨论】:

        【解决方案5】:

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

        • 您想在哪里处理您的错误?我建议尽可能接近数据。
        • 您希望谁知道该错误?您的用户是否需要知道“您已经使用过该 ID”...?

        另外——约束可能很好——我不 100% 同意millimoose 在这一点上的回答——我的意思是,我这样做 应该是这种方式/更好的性能理想 - - 但实际上,如果您无法控制您的开发人员/质量控制,尤其是在执行可能会炸毁您的数据库的规则时(或者,如果重复键要破坏报告等依赖对象等)在某处出现,您需要一些障碍来防止(例如)重复的键条目。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-10-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-09-04
          相关资源
          最近更新 更多