如何使用EF6和SQL Server捕获UniqueKey Violation异常?

Sin*_*YAS 44 c# sql-server exception-handling unique-key entity-framework-6

我的一个表有一个唯一的密钥,当我尝试插入一个重复的记录时,它会按预期抛出异常.但我需要区分唯一的密钥异常,以便我可以为唯一的密钥约束违规自定义错误消息.

我发现所有的解决方案在网上提出投ex.InnerExceptionSystem.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.";
    }
}
Run Code Online (Sandbox Code Playgroud)

但问题是,铸造ex.InnerExceptionSystem.Data.SqlClient.SqlException原因无效的转换错误,因为ex.InnerException实际上是一个类型的System.Data.Entity.Core.UpdateException,不是System.Data.SqlClient.SqlException.

上面的代码有什么问题?如何捕获Unique Key Constraint违规?

ken*_*n2k 59

使用EF6和DbContextAPI(对于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...
}
Run Code Online (Sandbox Code Playgroud)

正如您所提到的UpdateException,我假设您正在使用ObjectContextAPI,但它应该是类似的.

  • 有没有办法检测发生了哪一列违规?一张表中可能有多个唯一键... (2认同)
  • 这不会打破 ORM 的模式,创建对数据库的直接依赖吗?这是否意味着每次我使用其他数据库时,我都必须重新编程异常处理以识别特定代码? (2认同)
  • @ken2k 此代码取决于 SQL Server 实现,不适用于其他数据库。正如 Daniel Lobo 所提到的,它打破了 ORM 的想法。 (2认同)
  • 我只是想知道有多少次有人将他们的应用程序移动到完全不同的数据库系统?我已经编程 30 年了。我们从未将应用程序迁移到不同的数据库系统 (2认同)

Shi*_*mmy 13

就我而言,我正在使用EF 6并在我的模型中修饰其中一个属性:

[Index(IsUnique = true)]
Run Code Online (Sandbox Code Playgroud)

为了捕获违规行为,我使用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();
}
Run Code Online (Sandbox Code Playgroud)

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


br3*_*3nt 7

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

该类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
}
Run Code Online (Sandbox Code Playgroud)


Des*_*ngh 5

// 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
   }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

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

if (ex.Message.Contains("UniqueConstraint")) // do stuff
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,catch(SqlException ex)没有捕获Unique Key违规异常并抛出此错误:EntityFramework.dll中发生了'System.Data.Entity.Infrastructure.DbUpdateException'类型的异常,但未在用户代码中处理 (3认同)

Hus*_*bir 5

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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这样一个事实:这添加了额外的错误代码,并且开关使代码看起来更干净,但我认为在将 `InnerException` 转换为 `ErrorCode` 时存在一个小错误。我认为你想调用 `GetBaseException()` 而不是使用 `InnerException`。 (2认同)