Tener dos clases:
public class Parent
{
public int Id { get; set; }
public int ChildId { get; set; }
}
public class Child { ... }
Al asignar ChildId
a Parent
debería comprobar primero si existe en la base de datos o esperar a que la base de datos a una excepción?
Por ejemplo (usando Entity Framework Core):
TENGA EN CUENTA que este tipo de comprobaciones ESTÁN EN TODA LA INTERNET incluso en los documentos oficiales de Microsoft: https://docs.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using- mvc / handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application # modify-the-department-controller pero hay un manejo adicional de excepciones paraSaveChanges
Además, tenga en cuenta que la intención principal de esta comprobación fue devolver un mensaje amigable y un estado HTTP conocido al usuario de la API y no ignorar por completo las excepciones de la base de datos. Y el único lugar en el que se lanzará una excepción es dentro SaveChanges
o SaveChangesAsync
llamando ... para que no haya ninguna excepción cuando llame FindAsync
o Any
. Por lo tanto, si existe un elemento secundario pero se eliminó antes, SaveChangesAsync
se generará una excepción de concurrencia.
Hice esto debido a que la foreign key violation
excepción será mucho más difícil de formatear para mostrar "No se pudo encontrar el niño con ID {parent.ChildId}".
public async Task<ActionResult<Parent>> CreateParent(Parent parent)
{
// is this code redundant?
// NOTE: its probably better to use Any isntead of FindAsync because FindAsync selects *, and Any selects 1
var child = await _db.Children.FindAsync(parent.ChildId);
if (child == null)
return NotFound($"Child with id {parent.ChildId} could not be found.");
_db.Parents.Add(parent);
await _db.SaveChangesAsync();
return parent;
}
versus:
public async Task<ActionResult<Parent>> CreateParent(Parent parent)
{
_db.Parents.Add(parent);
await _db.SaveChangesAsync(); // handle exception somewhere globally when child with the specified id doesn't exist...
return parent;
}
El segundo ejemplo en Postgres arrojará un 23503 foreign_key_violation
error: https://www.postgresql.org/docs/9.4/static/errcodes-appendix.html
La desventaja de manejar excepciones de esta manera en ORM como EF es que solo funcionará con un backend de base de datos específico. Si alguna vez quisiste cambiar al servidor SQL o algo más, esto ya no funcionará porque el código de error cambiará.
No formatear la excepción correctamente para el usuario final podría exponer algunas cosas que no desea que nadie más que los desarrolladores vean.
Relacionado:
https://stackoverflow.com/questions/308905/should-there-be-a-transaction-for-read-queries
fuente
Child with id {parent.ChildId} could not be found.
. Y formatear "Violación de clave externa" creo que es peor en este caso.Respuestas:
Es una pregunta confusa, pero SÍ , debe verificar primero y no solo manejar una excepción de base de datos.
En primer lugar, en su ejemplo, está en la capa de datos, utilizando EF directamente en la base de datos para ejecutar SQL. Tu código es equivalente a correr
La alternativa que sugiere es:
Usar la excepción para ejecutar la lógica condicional es lento y universalmente mal visto.
Tienes una condición de carrera y debes usar una transacción. Pero esto se puede hacer completamente en código.
La clave es preguntarse:
Si no, entonces seguro, inserte y arroje una excepción. Pero solo maneje la excepción como cualquier otro error que pueda ocurrir.
Si espera que ocurra, NO es excepcional y debe verificar si el niño existe primero, respondiendo con el mensaje amistoso apropiado si no es así.
Editar : parece haber mucha controversia sobre esto. Antes de votar negativamente, considere:
A. ¿Qué pasaría si hubiera dos restricciones FK? ¿Recomendaría analizar el mensaje de excepción para determinar qué objeto faltaba?
B. Si tiene una falla, solo se ejecuta una instrucción SQL. Solo los hits generan el gasto adicional de una segunda consulta.
C. Por lo general, Id sería una clave sustituta. Es difícil imaginar una situación en la que conozca una y no esté seguro de que esté en la base de datos. La comprobación sería extraña. Pero, ¿qué pasa si es una clave natural que el usuario ha ingresado? Eso podría tener una alta probabilidad de no estar presente
fuente
Comprobar la unicidad y luego configurar es un antipatrón; siempre puede suceder que la ID se inserte simultáneamente entre el tiempo de verificación y el tiempo de escritura. Las bases de datos están equipadas para tratar este problema a través de mecanismos como restricciones y transacciones; la mayoría de los lenguajes de programación no lo son. Por lo tanto, si valora la consistencia de los datos, déjelo al experto (la base de datos), es decir, inserte y capture una excepción si ocurre.
fuente
Creo que lo que llamas "falla rápido" y lo que yo llamo no es lo mismo.
Decirle a la base de datos que haga un cambio y manejar la falla, eso es rápido. Su camino es complicado, lento y no particularmente confiable.
Esa técnica suya no es fallar rápidamente, es "verificación previa". A veces hay buenas razones, pero no cuando usa una base de datos.
fuente
Esto comenzó como un comentario pero creció demasiado.
No, como han dicho las otras respuestas, este patrón no debe usarse. *
Cuando se trata de sistemas que usan componentes asincrónicos, siempre habrá una condición de carrera en la que la base de datos (o sistema de archivos u otro sistema asíncrono) puede cambiar entre la verificación y el cambio. Una verificación de este tipo simplemente no es una forma confiable de evitar el tipo de error que no desea manejar.
Peor que no ser suficiente, de un vistazo da la impresión de que debería evitar el error de registro duplicado dando una falsa sensación de seguridad.
Necesita el manejo de errores de todos modos.
En los comentarios, ha preguntado si necesita datos de múltiples fuentes.
Aún no.
La cuestión fundamental no desaparece si lo que desea verificar se vuelve más complejo.
Todavía necesita el manejo de errores de todos modos.
Incluso si esta verificación fuera una forma confiable de prevenir el error particular del que está tratando de protegerse, aún pueden ocurrir otros errores. ¿Qué sucede si pierde la conexión a la base de datos, o se queda sin espacio, o?
Es muy probable que aún necesite otro manejo de errores relacionado con la base de datos de todos modos. El manejo de este error en particular probablemente debería ser una pequeña parte de él.
Si necesita datos para determinar qué cambiar, obviamente tendrá que recopilarlos de alguna parte. (dependiendo de las herramientas que esté utilizando, probablemente haya mejores formas que las consultas separadas para recopilarlas) Si, al examinar los datos que recopiló, determina que no necesita realizar el cambio, después de todo, excelente, no realice cambio. Esta determinación está completamente separada de las preocupaciones de manejo de errores.
Todavía necesita el manejo de errores de todos modos.
Sé que estoy siendo repetitivo, pero creo que es importante aclarar esto. Ya he limpiado este desastre antes.
Fallará eventualmente. Cuando falla, será difícil y lento llegar al fondo. Resolver problemas que surgen de las condiciones de carrera es difícil. No suceden constantemente, por lo que será difícil o incluso imposible reproducirse de forma aislada. Para empezar, no introdujo el manejo de errores adecuado, por lo que es probable que no tenga mucho para continuar: tal vez el informe de un usuario final de algún texto críptico (que oye, estaba tratando de evitar que se vea en primer lugar). Tal vez una traza de pila que señala de nuevo a esa función que cuando la miras descaradamente niega que el error sea incluso posible.
* Puede haber razones comerciales válidas para realizar estas verificaciones existentes, como evitar que la aplicación duplique el trabajo costoso, pero no es un reemplazo adecuado para el manejo adecuado de errores.
fuente
Creo que una cosa secundaria a tener en cuenta aquí: una de las razones por las que desea esto es para que pueda formatear un mensaje de error para que el usuario lo vea.
Recomiendo encarecidamente que:
a) muestre al usuario final el mismo mensaje de error genérico para cada error que ocurra.
b) registre la excepción real en algún lugar al que solo los desarrolladores puedan acceder (si está en un servidor) o en algún lugar que pueda enviarle mediante herramientas de informe de errores (si el cliente está implementado)
c) no intente formatear los detalles de excepción de error que registra a menos que pueda agregar más información útil. No querrás haber "formateado" accidentalmente la única información útil que hubieras podido usar para rastrear un problema.
En resumen, las excepciones están llenas de información técnica muy útil. Nada de esto debe ser para el usuario final y usted pierde esta información bajo su propio riesgo.
fuente