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

时间:2013-03-22 18:43:01

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

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

5 个答案:

答案 0 :(得分:6)

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

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

验证本身很难放在应用程序中,因为它涉及UI 都与模型架构相关联,但我更倾向于在UI附近进行操作。

答案 1 :(得分:3)

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

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

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

我知道我对这个意外的异常进行了一些讨论,但是我处理了太多的支持事件,程序员只是对数据库异常进行了后续处理,当出现意外情况时,应用程序无声地失败或下游失败,留下零诊断信息。非常顽皮。

答案 2 :(得分:1)

我更喜欢一个存储过程,它会在SQL Server中抛出数据并让约束冒出错误之前检查潜在的违规行为。其原因与性能有关:

http://www.sqlperformance.com/2012/08/t-sql-queries/error-handling

http://www.mssqltips.com/sqlservertip/2632/checking-for-potential-constraint-violations-before-entering-sql-server-try-and-catch-logic/

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

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

答案 3 :(得分:0)

我做了类似的事情:

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

然后它为常见事件提供了一些特定的错误。但如上所述;你真的应该确保在将数据输入数据库之前测试了数据。这样就没有不匹配的约束表面或存在。

希望它指出你的方向。

答案 4 :(得分:0)

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

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

另外 - 约束可能是好的 - 我不是100%同意millimoose在这一点上的答案 - 我的意思是,我在中做的应该是这样/更好的性能理想 - - 但实际上,如果你无法控制你的开发人员/ qc,特别是当执行可能会破坏你的数据库的规则时(或者,如果重复的密钥是,则打破报告等依赖对象)在某个地方开通,你需要一些屏障来对抗(例如)重复的密钥条目。