25

我的表中有一个表具有唯一键,当我尝试插入重复记录时,它会按预期抛出异常。但是我需要区分唯一的关键异常和其他异常,以便我可以自定义错误消息来解决唯一的关键约束违规问题。如何使用EF6和SQL Server捕获UniqueKey违例异常?

我在网上找到

所有的解决方案,建议投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."; 
    } 
} 

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

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

回答

38

随着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,但它应该是相似的。

+0

查看您分享的代码后,我现在可以看到我的代码问题非常明显。我应该写“ex.InnerException.InnerException as SqlException”而不是“ex.InnerException as SqlException”。 –

+1

有没有办法来检测哪个列违规发生?在一个表中可能有多个唯一键... – Learner

+0

@Learner我能想到的唯一方法是解析错误消息(其中声明约束/列的名称),但它不会是一个很好的解决方案(错误消息将来可能会更新,更重要的是,会翻译成多种语言) – ken2k

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 
    } 
} 

编辑:

你也只是检查异常的消息组件。事情是这样的:

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

不幸的是,catch(SqlException ex)没有捕获到唯一键违例异常并引发此错误:在EntityFramework.dll中发生类型为“System.Data.Entity.Infrastructure.DbUpdateException”的异常,但未在用户中处理代码 –

+1

什么错误?第二个如果条件? –

+0

检查错误信息中的“UniqueConstraint”应该可行,但它似乎不是最好的方法。 –

2

如果你想赶上唯一约束

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 
    } 
} 
3

就我而言,我使用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(); 
} 

请注意,这只会猫ch唯一索引约束违规。