Deshacer cambios en las entidades del marco de la entidad

esto podría ser una pregunta trivial pero: dado que el marco de entidad ADO.NET sigue automáticamente los cambios (en las entidades generadas) y, por lo tanto, conserva los valores originales, ¿cómo puedo deshacer los cambios realizados en los objetos de la entidad?

Tengo un formulario que permite al usuario editar un conjunto de entidades “Cliente” en una vista de cuadrícula.

Ahora tengo dos botones “Aceptar” y “Revertir”: si se hace clic en “Aceptar”, llamo Context.SaveChanges() y los objetos modificados se vuelven a escribir en la base de datos. Si se hace clic en “Revertir”, me gustaría que todos los objetos obtengan sus valores de propiedad originales. ¿Cuál sería el código para eso?

Gracias

No hay operaciones de reversión o cancelación de cambios en EF. Cada entidad tiene ObjectStateEntry en ObjectStateManager . La entrada de estado contiene valores originales y reales, por lo que puede usar valores originales para sobrescribir valores actuales, pero debe hacerlo manualmente para cada entidad. No reverenciará los cambios en las propiedades / relaciones de navegación.

La forma común de “revertir cambios” es eliminar el contexto y volver a cargar las entidades. Si desea evitar la recarga, debe crear clones de entidades y modificar esos clones en el nuevo contexto del objeto. Si el usuario cancela los cambios, todavía tendrá las entidades originales.

Consulta ChangeTracker de DbContext para elementos sucios. Establezca el estado de los elementos eliminados en los elementos sin cambios y agregados a los desconectados. Para elementos modificados, use valores originales y establezca los valores actuales de la entrada. Finalmente establece el estado de la entrada modificada a sin cambios:

 public void RollBack() { var context = DataContextFactory.GetDataContext(); var changedEntries = context.ChangeTracker.Entries() .Where(x => x.State != EntityState.Unchanged).ToList(); foreach (var entry in changedEntries) { switch(entry.State) { case EntityState.Modified: entry.CurrentValues.SetValues(entry.OriginalValues); entry.State = EntityState.Unchanged; break; case EntityState.Added: entry.State = EntityState.Detached; break; case EntityState.Deleted: entry.State = EntityState.Unchanged; break; } } } 
 dbContext.Entry(entity).Reload(); 

Accroding to MSDN :

Recarga la entidad de la base de datos sobrescribiendo cualquier valor de propiedad con valores de la base de datos. La entidad estará en el estado Sin cambios después de llamar a este método.

Tenga en cuenta que revertir a través de la solicitud a la base de datos tiene algunos inconvenientes:

  • tráfico de red
  • Sobrecarga DB
  • el aumento del tiempo de respuesta de la aplicación

Esto funcionó para mí:

 dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item); 

Donde el item es la entidad del cliente que se revertirá.

Manera fácil sin seguir ningún cambio. Debería ser más rápido que mirar a todas las entidades.

 public void Rollback() { dataContext.Dispose(); dataContext= new MyEntities(yourConnection); } 
 // Undo the changes of all entries. foreach (DbEntityEntry entry in context.ChangeTracker.Entries()) { switch (entry.State) { // Under the covers, changing the state of an entity from // Modified to Unchanged first sets the values of all // properties to the original values that were read from // the database when it was queried, and then marks the // entity as Unchanged. This will also reject changes to // FK relationships since the original value of the FK // will be restred. case EntityState.Modified: entry.State = EntityState.Unchanged; break; case EntityState.Added: entry.State = EntityState.Detached; break; // If the EntityState is the Deleted, reload the date from the database. case EntityState.Deleted: entry.Reload(); break; default: break; } } 

Funcionó para mí Sin embargo, debe volver a cargar sus datos del contexto para traer los datos anteriores. Fuente aquí

“Esto funcionó para mí:

 dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item); 

Donde el item es la entidad del cliente que se revertirá “.


Realicé pruebas con ObjectContext.Refresh en SQL Azure y el “RefreshMode.StoreWins” desencadena una consulta en la base de datos para cada entidad y provoca una pérdida de rendimiento. Basado en la documentación de Microsoft ():

ClientWins: los cambios de propiedad realizados a los objetos en el contexto del objeto no se reemplazan con valores del origen de datos. En la siguiente llamada a SaveChanges, estos cambios se envían a la fuente de datos.

StoreWins: los cambios de propiedad realizados a los objetos en el contexto del objeto se reemplazan por valores del origen de datos.

ClientWins tampoco es una buena idea, porque al disparar .SaveChanges se realizarán cambios “descartados” en la fuente de datos.

No sé cuál es la mejor manera, porque eliminar el contexto y crear uno nuevo es una excepción con el mensaje: “El proveedor subyacente falló al abrir” cuando trato de ejecutar cualquier consulta en un nuevo contexto creado.

Saludos,

Henrique Clausing

En cuanto a mí, un mejor método para hacerlo es establecer EntityState.Unchanged en cada entidad sobre la que desee deshacer los cambios. Esto asegura que los cambios se revierten en FK y tiene una syntax más clara.

Encontré que funcionaba bien en mi contexto:

Context.ObjectStateManager.ChangeObjectState(customer, EntityState.Unchanged);

Este es un ejemplo de lo que Mrnka está hablando. El siguiente método sobrescribe los valores actuales de una entidad con los valores originales y no llama a la base de datos. Hacemos esto haciendo uso de la propiedad OriginalValues ​​de DbEntityEntry, y hacemos uso de la reflexión para establecer valores de una manera genérica. (Esto funciona a partir de EntityFramework 5.0)

 ///  /// Undoes any pending updates ///  public void UndoUpdates( DbContext dbContext ) { //Get list of entities that are marked as modified List modifiedEntityList = dbContext.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified).ToList(); foreach( DbEntityEntry entity in modifiedEntityList ) { DbPropertyValues propertyValues = entity.OriginalValues; foreach (String propertyName in propertyValues.PropertyNames) { //Replace current values with original values PropertyInfo property = entity.Entity.GetType().GetProperty(propertyName); property.SetValue(entity.Entity, propertyValues[propertyName]); } } } 

Estamos utilizando EF 4, con el contexto del objeto heredado. Ninguna de las soluciones anteriores respondió directamente esto por mí, aunque a la larga me RESPONDIÓ empujándome en la dirección correcta.

No podemos simplemente disponer y reconstruir el contexto porque algunos de los objetos que tenemos almacenados en la memoria (¡maldita sea que la carga es vaga!) Todavía están unidos al contexto pero tienen hijos que aún no se han cargado. Para estos casos, necesitamos devolver todo a los valores originales sin golpear la base de datos y sin soltar la conexión existente.

A continuación está nuestra solución a este mismo problema:

  public static void UndoAllChanges(OurEntities ctx) { foreach (ObjectStateEntry entry in ctx.ObjectStateManager.GetObjectStateEntries(~EntityState.Detached)) { if (entry.State != EntityState.Unchanged) { ctx.Refresh(RefreshMode.StoreWins, entry.Entity); } } } 

Espero que esto ayude a otros.

Algunas buenas ideas anteriores, elegí implementar ICloneable y luego un método de extensión simple.

Encontrado aquí: ¿Cómo puedo clonar una lista genérica en C #?

Para ser utilizado como:

 ReceiptHandler.ApplyDiscountToAllItemsOnReciept(LocalProductsOnReciept.Clone(), selectedDisc); 

De esta forma, pude clonar la lista de entidades de mi producto, aplicar un descuento a cada elemento y no tener que preocuparme de revertir ningún cambio en la entidad original. No es necesario que hable con DBContext y solicite una actualización o trabaje con ChangeTracker. Podría decir que no estoy haciendo un uso completo de EF6, pero esta es una implementación muy agradable y simple y evita un golpe de DB. No puedo decir si esto tiene o no un golpe de rendimiento.