Comportamiento por defecto
Por defecto, en EF Core, todos los cambios realizados en una simple llamada a SaveChanges son aplicados en una transacción (implicÃta). Si los cambios fallan, la transacción se revierte y no se aplican los cambios a la base de datos. Solo si todos los cambios son exitosamente persistidos en la base de datos, la llamada a SaveChanges pueden completarse.
Este comportamiento nos ahorra muchos dolores de cabeza. No tendremos que preocuparnos de dejar la base de datos en un estado inconsistente.
using var context = new TiendaContext(); context.Lineas.Add(new Linea { ProductId = productId, Quantity = quantity }); var stock = context.Stock.FirstOrDefault(s => s.ProductId == productId); stockQuantity -= quantity; context.SaveChanges();
Por ejemplo: si agregamos una lÃnea de una factura y en el mismo alcance reducimos el stock, la llamada a SaveChanges aplicará ambos cambios dentro de una sola transacción. De esa forma podemos garantizar que la base de datos queda en un estado consistente.
Creando Transacciones con EF Core
Pero en algún momento necesitaremos tener más control sobre las transacciones cuando trabajemos con EF Core. Pues, se pueden crear transacciones de forma manual accediendo a una instancia de DBContext y llamando a BeginTransaction.
Por ejemplo: si tenemos varias llamadas a SaveChanges. En el escenario por defecto, ambas llamadas tienen su propia transacción. Esto deja la posibilidad en la que la segunda llamada a SaveChanges falle y deje la base de datos en un estado inconsistente.
using var context = new TiendaContext(); using var transaction = context.Database.BeginTransaction(); try { context.Lineas.Add(new Linea { ProductId = productId, Quantity = quantity }); context.SaveChanges(); var stock = context.Stock.FirstOrDefault(s => s.ProductId == productId); stockQuantity -= quantity; context.SaveChanges(); //cuando se completen los cambios, serán aplicados a la base de datos //la transacción se revertirá automáticamente cuando sea eliminada //si algún comando falla transaction.Commit(); } catch(Exception) { transaction.Rollback(); }
Para evitar ese problema, llamamos a BeginTransaction para iniciar de forma manual una nueva transacción de base de datos. Esto creará una nueva transacción y la devolverá al programa, de modo que podamos completar la transacción (Commit) cuando la operación esté completa. A la vez podemos agregar un bloque try-catch rodeando el código, de modo que se pueda revertir la transacción (Rollback) si hay alguna excepción.
Usar Transacciones Existentes con EF Core
Crear una transacción utilizando DbContext de EF Core no es la única opción. Se puede crear una instancia de SqlTransaction y pasarla a EF Core, de modo que los cambios aplicados con EF Core pueden ser completados dentro de la misma transacción.
using var sqlConnection = new SqlTransaction(connectionString); sqlConnection.Open(); using var transaction = sqlConnection.BeginTransaction(); try { using var context = new TiendaContext(); context.UseTransaction(transaction); context.Lineas.Add(new Linea { ProductId = productId, Quantity = quantity }); context.SaveChanges(); var stock = context.Stock.FirstOrDefault(s => s.ProductId == productId); stockQuantity -= quantity; context.SaveChanges(); //cuando se completen los cambios, serán aplicados a la base de datos //la transacción se revertirá automáticamente cuando sea eliminada //si algún comando falla transaction.Commit(); } catch(Exception) { transaction.Rollback(); }
Resumen
EF Core soporta las transacciones de base de datos y es muy fácil de trabajarlo.
Tenemos tres opciones disponibles:
- Confiar en el comportamiento por defecto de las transacciones.
- Crear una nueva transacción.
- Utilizar una transacción existente.
En la mayorÃa de los casos podemos confiar en el comportamiento por defecto y no tenemos que pensar en ello. Tan pronto como necesitemos ejecutar diferentes llamadas a SaveChnages, podrÃas crear manualmente una transacción y controlarla por nosotros mismos.
0 Comentarios