【问题标题】:How can I catch UniqueKey Violation exceptions with EF6 and SQL Server?如何使用 EF6 和 SQL Server 捕获 UniqueKey Violation 异常?
【发布时间】:2015-10-09 12:47:45
【问题描述】:

我的一个表有一个唯一键,当我尝试插入重复记录时,它会按预期抛出异常。但是我需要将唯一键异常与其他异常区分开来,这样我就可以自定义违反唯一键约束的错误消息。

我在网上找到的所有解决方案都建议将ex.InnerException 转换为System.Data.SqlClient.SqlException 并检查Number 属性是否等于2601 或2627,如下所示:

try
{
    _context.SaveChanges();
}
catch (Exception ex)
{
    var sqlException = ex.InnerException as System.Data.SqlClient.SqlException;

    if (sqlException.Number == 2601 || sqlException.Number == 2627)
    {
        ErrorMessage = "Cannot insert duplicate values.";
    }
    else
    {
        ErrorMessage = "Error while saving data.";
    }
}

但问题是,将ex.InnerException 转换为System.Data.SqlClient.SqlException 会导致无效转换错误,因为ex.InnerException 实际上是System.Data.Entity.Core.UpdateException 的类型,而不是System.Data.SqlClient.SqlException。

上面的代码有什么问题?如何捕获违反唯一键约束的行为?

【问题讨论】:

    标签: c# sql-server exception-handling entity-framework-6 unique-key


    【解决方案1】:

    使用 EF6 和 DbContext API(用于 SQL Server),我目前正在使用这段代码:

    try
    {
      // Some DB access
    }
    catch (Exception ex)
    {
      HandleException(ex);
    }
    
    public virtual void HandleException(Exception exception)
    {
      if (exception is DbUpdateConcurrencyException concurrencyEx)
      {
        // A custom exception of yours for concurrency issues
        throw new ConcurrencyException();
      }
      else if (exception is DbUpdateException dbUpdateEx)
      {
        if (dbUpdateEx.InnerException != null
                && dbUpdateEx.InnerException.InnerException != null)
        {
          if (dbUpdateEx.InnerException.InnerException is SqlException sqlException)
          {
            switch (sqlException.Number)
            {
              case 2627:  // Unique constraint error
              case 547:   // Constraint check violation
              case 2601:  // Duplicated key row error
                          // Constraint violation exception
                // A custom exception of yours for concurrency issues
                throw new ConcurrencyException();
              default:
                // A custom exception of yours for other DB issues
                throw new DatabaseAccessException(
                  dbUpdateEx.Message, dbUpdateEx.InnerException);
            }
          }
    
          throw new DatabaseAccessException(dbUpdateEx.Message, dbUpdateEx.InnerException);
        }
      }
    
      // If we're here then no exception has been thrown
      // So add another piece of code below for other exceptions not yet handled...
    }
    

    正如你提到的UpdateException,我假设你正在使用ObjectContext API,但它应该是相似的。

    【讨论】:

    • 查看了您分享的代码后,我现在可以看到我的代码的问题非常明显。我应该写“ex.InnerException.InnerException as SqlException”而不是“ex.InnerException as SqlException”。
    • 有没有办法同时检测出哪一列违规?一张表中可能有多个唯一键...
    • 这不是打破了 ORM 的模式,创建了对数据库的直接依赖吗?这是否意味着每次使用其他数据库时,我都必须重新编程异常处理以识别特定代码?
    • @ken2k 此代码依赖于 SQL Server 实现,不适用于其他数据库。正如 Daniel Lobo 所说的那样,它打破了 ORM 的想法。
    • 我只是想知道有多少次有人将他们的应用程序移动到完全不同的数据库系统?我已经编程 30 年了。我们从未将应用程序迁移到不同的数据库系统
    【解决方案2】:

    在我的例子中,我使用的是 EF 6,并用以下方式装饰了我的模型中的一个属性:

    [Index(IsUnique = true)]
    

    为了发现违规行为,我使用 C# 7 执行以下操作,这变得更加容易:

    protected async Task<IActionResult> PostItem(Item item)
    {
      _DbContext.Items.Add(item);
      try
      {
        await _DbContext.SaveChangesAsync();
      }
      catch (DbUpdateException e)
      when (e.InnerException?.InnerException is SqlException sqlEx && 
        (sqlEx.Number == 2601 || sqlEx.Number == 2627))
      {
        return StatusCode(StatusCodes.Status409Conflict);
      }
    
      return Ok();
    }
    

    注意,这只会捕获唯一索引约束违规。

    【讨论】:

      【解决方案3】:
      // put this block in your loop
      try
      {
         // do your insert
      }
      catch(SqlException ex)
      {
         // the exception alone won't tell you why it failed...
         if(ex.Number == 2627) // <-- but this will
         {
            //Violation of primary key. Handle Exception
         }
      }
      

      编辑:

      您也可以只检查异常的消息组件。像这样的:

      if (ex.Message.Contains("UniqueConstraint")) // do stuff
      

      【讨论】:

      • 不幸的是,catch(SqlException ex) 没有捕获唯一键违规异常并抛出此错误:EntityFramework.dll 中发生“System.Data.Entity.Infrastructure.DbUpdateException”类型的异常,但是未在用户代码中处理
      • 检查错误消息中的“UniqueConstraint”应该可以,但这似乎不是最好的方法。
      【解决方案4】:
      try
      {
         // do your insert
      }
      catch(Exception ex)
      {
         if (ex.GetBaseException().GetType() == typeof(SqlException))
         {
             Int32 ErrorCode = ((SqlException)ex.InnerException).Number;
             switch(ErrorCode)
             {
                case 2627:  // Unique constraint error
                    break;
                case 547:   // Constraint check violation
                    break;
                case 2601:  // Duplicated key row error
                    break;
                default:
                    break;
              }
          }
          else
          {
             // handle normal exception
          }
      }
      

      【讨论】:

      • 我喜欢这样一个事实,即这增加了额外的错误代码,并且开关使代码看起来更干净,但我认为将InnerException 转换为ErrorCode 存在一个小错误。我想你想打电话给GetBaseException() 而不是使用InnerException。
      • 此外,这会吞噬所有 SqlException 类型,而不仅仅是唯一键违规。我认为您的default 案子可能应该重新提出。
      【解决方案5】:

      我认为展示一些代码可能很有用,不仅可以处理重复行异常,还可以提取一些可用于编程目的的有用信息。例如。编写自定义消息。

      这个Exception 子类使用正则表达式来提取数据库表名、索引名和键值。

      public class DuplicateKeyRowException : Exception
      {
          public string TableName { get; }
          public string IndexName { get; }
          public string KeyValues { get; }
      
          public DuplicateKeyRowException(SqlException e) : base(e.Message, e)
          {
              if (e.Number != 2601) 
                  throw new ArgumentException("SqlException is not a duplicate key row exception", e);
      
              var regex = @"\ACannot insert duplicate key row in object \'(?<TableName>.+?)\' with unique index \'(?<IndexName>.+?)\'\. The duplicate key value is \((?<KeyValues>.+?)\)";
              var match = new System.Text.RegularExpressions.Regex(regex, System.Text.RegularExpressions.RegexOptions.Compiled).Match(e.Message);
      
              Data["TableName"] = TableName = match?.Groups["TableName"].Value;
              Data["IndexName"] = IndexName = match?.Groups["IndexName"].Value;
              Data["KeyValues"] = KeyValues = match?.Groups["KeyValues"].Value;
          }
      }
      

      DuplicateKeyRowException 类很容易使用...只需像之前的答案一样创建一些错误处理代码...

      public void SomeDbWork() {
          // ... code to create/edit/update/delete entities goes here ...
          try { Context.SaveChanges(); }
          catch (DbUpdateException e) { throw HandleDbUpdateException(e); }
      }
      
      public Exception HandleDbUpdateException(DbUpdateException e)
      {
          // handle specific inner exceptions...
          if (e.InnerException is System.Data.SqlClient.SqlException ie)
              return HandleSqlException(ie);
      
          return e; // or, return the generic error
      }
      
      public Exception HandleSqlException(System.Data.SqlClient.SqlException e)
      {
          // handle specific error codes...
          if (e.Number == 2601) return new DuplicateKeyRowException(e);
      
          return e; // or, return the generic error
      }
      

      【讨论】:

        【解决方案6】:

        如果你想捕捉唯一约束

        try { 
           // code here 
        } 
        catch(Exception ex) { 
           //check for Exception type as sql Exception 
           if(ex.GetBaseException().GetType() == typeof(SqlException)) { 
             //Violation of primary key/Unique constraint can be handled here. Also you may //check if Exception Message contains the constraint Name 
           } 
        }
        

        【讨论】:

          【解决方案7】:

          您在编写代码时必须非常具体。

               try
               {
                   // do your stuff here.
               {
               catch (Exception ex)
               {
                   if (ex.Message.Contains("UNIQUE KEY"))
                   { 
                      Master.ShowMessage("Cannot insert duplicate Name.", MasterSite.MessageType.Error);
                   }
                   else { Master.ShowMessage(ex.Message, MasterSite.MessageType.Error); }
               }
          

          我刚刚更新了上面的代码,它对我有用。

          【讨论】:

            【解决方案8】:

            SQL Server 的错误信息可以用这个语句捕获。

            try
             {
                 //trying to insert unique key data      
             }
             catch (Exception ex)
             {
                var exp = ((SqlException)ex.InnerException.InnerException).Message;
                // exp hold error message generated by sql
             }
            

            【讨论】:

            • 好吧,ex.InnerException.InnerException 可以是任何东西,或者抛出一个 NRE。这不安全。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-09-24
            • 2013-03-17
            • 1970-01-01
            • 2021-10-25
            • 2016-11-16
            • 2017-09-26
            • 1970-01-01
            相关资源
            最近更新 更多