Una de mis tablas tiene una clave única y cuando intento insertar un registro duplicado, arroja una excepción como se esperaba. Pero necesito distinguir las excepciones de clave única de las demás, de modo que pueda personalizar el mensaje de error para violaciones de restricciones de clave única.
Todas las soluciones que he encontrado en línea sugieren lanzar ex.InnerException
a System.Data.SqlClient.SqlException
y verificar si la propiedad Number
es igual a 2601 o 2627 de la siguiente manera:
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."; } }
Pero el problema es que lanzar ex.InnerException
a System.Data.SqlClient.SqlException
provoca un error de conversión no válido ya que ex.InnerException
es en realidad un tipo de System.Data.Entity.Core.UpdateException
, no System.Data.SqlClient.SqlException
.
¿Cuál es el problema con el código anterior? ¿Cómo puedo detectar infracciones de restricciones de clave única?
Con EF6 y la API DbContext
(para SQL Server), actualmente estoy usando este código:
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... }
Como mencionó UpdateException
, supongo que está utilizando la API ObjectContext
, pero debería ser similar.
En mi caso, estoy usando EF 6 y decoré una de las propiedades de mi modelo con:
[Index(IsUnique = true)]
Para detectar la infracción, hago lo siguiente, usando C# 7, esto se vuelve mucho más fácil:
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(); }
Tenga en cuenta que esto solo detectará la violación de la restricción de índice único.
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 } }