Подтвердить что ты не робот

Как я могу поймать исключения исключения 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.

В чем проблема с кодом выше? Как я могу уловить нарушения уникального ключа?

4b9b3361

Ответ 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
}