25

Una delle mie tabelle ha una chiave univoca e quando provo a inserire un record duplicato genera un'eccezione come previsto. Ma ho bisogno di distinguere le eccezioni chiave univoche da altre, in modo che possa personalizzare il messaggio di errore per le violazioni dei vincoli di chiave univoci.Come posso recuperare eccezioni UniqueKey Violation con EF6 e SQL Server?

Tutte le soluzioni che ho trovato on-line suggerisce di lanciare ex.InnerException-System.Data.SqlClient.SqlException e controllare la se Number proprietà è pari a 2601 o 2627 come segue:

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

Ma il problema è, gettando ex.InnerException a System.Data.SqlClient.SqlException cause errore di cast non valido poiché ex.InnerException è in realtà un tipo di System.Data.Entity.Core.UpdateException, non System.Data.SqlClient.SqlException.

Qual è il problema con il codice sopra? Come posso rilevare le violazioni del vincolo di chiave univoco?

risposta

38

Con EF6 e il DbContext API (per SQL Server), Attualmente sto usando questo pezzo di codice:

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

Come lei ha ricordato UpdateException, ti sto assumendo stai usando l'API ObjectContext, ma dovrebbe essere simile.

+0

Dopo aver controllato il codice che hai condiviso, ora posso vedere che il problema con il mio codice è così ovvio. Dovrei scrivere "ex.InnerException.InnerException as SqlException" anziché "ex.InnerException as SqlException". –

+1

C'è un modo per rilevare anche su quale violazione di colonna si è verificata? Potrebbero esserci più chiavi univoche in una tabella ... – Learner

+0

@Learner L'unico modo in cui posso pensare sarebbe quello di analizzare il messaggio di errore (che indica il nome del vincolo/colonna), ma non sarebbe un ottimo soluzione (i messaggi di errore potrebbero essere aggiornati in futuro, e più importante, sono tradotti in più lingue) – 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:

Si potrebbe anche solo ispezionare il componente del messaggio dell'eccezione. Qualcosa di simile a questo:

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

Sfortunatamente, catch (SqlException ex) non rileva l'eccezione di violazione della chiave univoca e genera questo errore: un'eccezione di tipo 'System.Data.Entity.Infrastructure.DbUpdateException' si è verificata in EntityFramework.dll ma non è stata gestita dall'utente codice –

+1

quale errore? e il secondo con se condizione? –

+0

L'ispezione di "UniqueConstraint" nel messaggio di errore dovrebbe funzionare, ma non sembra essere l'approccio migliore. –

2

Se si desidera catturare vincolo univoco

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

Nel mio caso, sto usando EF 6 e decorato una delle proprietà nel mio modello con:

[Index(IsUnique = true)] 

Per catturare la violazione Faccio ciò che segue, usando C# 7, questo diventa molto più facile:

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

Nota, che questo sarà solo gatto ch violazione del vincolo di indice univoco.