25

Jedna z moich tabel ma unikalny klucz, a gdy próbuję wstawić duplikat, zgłasza wyjątek zgodnie z oczekiwaniami. Ale muszę odróżnić wyjątkowe wyjątki klucza od innych, aby móc dostosować komunikat o błędzie dla unikalnych naruszeń ograniczeń klucza.Jak mogę złapać wyjątki wyjątków UniqueKey za pomocą EF6 i SQL Server?

Wszystkie rozwiązania znalazłem internetowy proponuje rzucić ex.InnerException do System.Data.SqlClient.SqlException i sprawdzić czy Number nieruchomość wynosi 2601 lub 2627 w sposób następujący:

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."; 
    } 
} 

Ale problem jest, rzucając ex.InnerException do System.Data.SqlClient.SqlException przyczyny nieprawidłowy błąd rzutowania, ponieważ ex.InnerException jest faktycznie typu System.Data.Entity.Core.UpdateException, a nie System.Data.SqlClient.SqlException.

Na czym polega problem z powyższym kodem? Jak mogę złapać naruszenie ograniczeń związanych z Unikalnym kluczem?

Odpowiedz

38

Z EF6 i DbContext API (SQL Server), obecnie używam ten kawałek kodu:

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

Jak wspomniano UpdateException, jestem przy założeniu, że używa interfejsu API ObjectContext, ale powinno być podobne.

+0

Po sprawdzeniu udostępnionego kodu widzę, że problem z moim kodem jest tak oczywisty. Powinienem napisać "ex.InnerException.InnerException jako SqlException" zamiast "ex.InnerException jako SqlException". –

+1

Czy istnieje sposób wykrycia również, w którym przypadku doszło do naruszenia kolumny? W jednej tabeli może znajdować się wiele unikalnych kluczy ... – Learner

+0

@Learner Jedynym sposobem, jaki mogę wymyślić, to przeanalizowanie komunikatu o błędzie (który określa nazwę ograniczenia/kolumny), ale nie byłoby to zbyt dobre rozwiązanie (komunikaty o błędach mogą być aktualizowane w przyszłości, a co ważniejsze, są tłumaczone na wiele języków) – 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 
    } 
} 

EDIT:

Można też po prostu sprawdzić komponent wiadomość wyjątku. Coś takiego:

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

Niestety, catch (SqlException ex) nie przechwytuje wyjątku naruszenia klucza unikalnego i zgłasza ten błąd: wyjątek typu "System.Data.Entity.Infrastructure.DbUpdateException" wystąpił w EntityFramework.dll, ale nie był obsługiwany przez użytkownika kod –

+1

jaki błąd? a drugi z warunkiem? –

+0

Sprawdzanie "UniqueConstraint" w komunikacie o błędzie powinno działać, ale nie wydaje się być najlepszym podejściem. –

2

Jeśli chcesz złapać unique

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

W moim przypadku Używam EF 6 i urządzone jedną z właściwości w moim modelu:

[Index(IsUnique = true)] 

złapać naruszenie Wykonuję następujące czynności, używając C# 7, staje się to znacznie łatwiejsze:

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

Uwaga, to będzie tylko cat ch unikalne naruszenie ograniczenia indeksu.

Powiązane problemy