Как я могу поймать исключения исключения UniqueKey с EF6 и SQL Server?
Одна из моих таблиц имеет уникальный ключ, и когда я пытаюсь вставить дублируемую запись, она генерирует исключение, как ожидалось. Но мне нужно отличать исключительные ключевые исключения от других, так что я могу настроить сообщение об ошибке для уникальных нарушений ограничения ключа.
Все решения, которые я нашел в Интернете, предлагают отбрасывать 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
.
В чем проблема с кодом выше? Как я могу уловить нарушения уникального ключа?
Ответы
Ответ 1
С EF6 и API DbContext
(для 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
, я предполагаю, что вы используете API ObjectContext
, но он должен быть аналогичным.
Ответ 2
В моем случае я использую EF 6 и украсил одно из свойств в моей модели:
[Index(IsUnique = true)]
Чтобы поймать нарушение, я делаю следующее, используя С# 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
}
}
EDIT:
Вы также можете просто проверить компонент сообщения об исключении. Что-то вроде этого:
if (ex.Message.Contains("UniqueConstraint")) // do stuff
Ответ 4
Если вы хотите поймать уникальное ограничение
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
}
}
Ответ 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
}
}
Ответ 6
Я подумал, что было бы полезно показать некоторый код, который не только обрабатывает исключение дублирующихся строк, но также извлекает некоторую полезную информацию, которая может использоваться в программных целях. Например, составление собственного сообщения.
Этот подкласс 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
}