Tengo una lista enorme de objetos "aplanados" en formato json y un esquema de base de datos relacional algo complicado (con unas 20 tablas correspondientes a un objeto aplanado). Estoy tratando de automatizar las inserciones de esos objetos aplanados en mi nueva base de datos relacional:
foreach (var flattenedObject in flattenedObjects) { _repository.Insert(flattenedObject).Wait(); //some time logging, etc } El método Insert() llama a AddRangeAsync() y AddAsync() para una cantidad de objetos relacionados en diferentes tablas.
Dado que los objetos aplanados son heredados, diría que alrededor del 0,001 % de ellos tienen un formato incorrecto y violarán las restricciones de la base de datos; por ejemplo, al intentar insertar una clave principal compuesta duplicada en una de las tablas.
Espero estos errores raros, por lo tanto, mi idea es: envolver toda la operación Insert() en una transacción; si alguna parte de la operación no es válida, simplemente no inserte nada y registre el error, para que pueda modificar el objeto aplanado manualmente antes de volver a intentarlo. Por lo tanto, mi código se ve algo similar a esto:
public async Task Insert(FlattenedObject fo) { using (var transaction = _context.Database.BeginTransaction()) { try { //magical code that calls AddAsync for multiple tables } catch (Exception ex) { transaction.Rollback() //logging } } }Sin embargo, si ocurre un error en algún lugar de mi bloque de prueba (intento insertar un objeto que viola una clave primaria compuesta), todo mi objeto de contexto se corrompe.
El objeto que causó la excepción aún permanece en mi DbContext y cualquier llamada posterior a AddAsync() en una transacción diferente desencadena una nueva excepción.
Intenté recrear mi DbContext y el repositorio para cada objeto nuevo en el bucle foreach anterior, pero incluso si consulto:
_context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged);Veo que mi antiguo objeto todavía está en la nueva instancia de dbContext.
¿Hay alguna forma (elegante) de indicarle a mi contexto que restablezca todos los cambios pendientes, de modo que pueda colocarlo en el bloque catch cada vez que ocurra un error? Quiero que todo lo que suceda dentro de mi transacción fallida permanezca allí y no se filtre.
El siguiente código funcionó para mí. Sin embargo, si alguien publica una solución más limpia (todavía espero que haya algo listo para usar con EF), lo aceptaré.
private void ResetContextState() => _context.ChangeTracker.Entries() .Where(e => e.Entity != null).ToList() .ForEach(e => e.State = EntityState.Detached);Después de años, tal vez algunos de ustedes todavía estén buscando y, por milagro, estén usando EF Core 5.0. Hay una nueva función para borrar el ChangeTracker:
dbContext.ChangeTracker.Clear(); Simplemente llámelo cada vez que falle una actualización.
Pero no olvide que esto no es en absoluto la mejor práctica. Microsoft recomienda crear un nuevo dbContext para cada solicitud, ya que está diseñado para tener una vida útil corta.
Más información aquí: Microsoft EF Core 5.0: Método ChangeTracker.Clear
Pero un poco más complejo, es usar DbContextFactory. Esto puede ser útil cuando el código de la aplicación necesita crear y desechar instancias de contexto manualmente.
Más información aquí: Microsoft EF Core 5.0: DbContextFactory
Disfrutar