如何使用 EF6和 SQLServer 捕获 UniqueKey 违规异常?

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

我在网上找到的所有解决方案都建议将 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

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

74205 次浏览
// 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

使用 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,但它应该是类似的。

如果要捕获唯一约束

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

在我的示例中,我使用 EF6并用以下方法修饰模型中的一个属性:

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

注意,这只会捕获唯一的索引约束冲突。

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

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

这个 Exception子类使用 regex 提取 db 表名、索引名和键值。

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
}

此语句可以捕获 SQLServer 的错误消息。

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

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

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

我只是更新了上面的代码一点点,它为我工作。