2011-03-29 14 views
87

esto podría ser una pregunta trivial pero: dado que ADO.NET Entity Framework sigue automáticamente los cambios (en las entidades generadas) y guarda los valores originales, ¿cómo puedo deshacer los cambios realizados en los objetos de entidad?Deshacer cambios en las entidades del marco de entidades

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 al 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

Respuesta

57

No hay deshacer o cancelar los cambios en el funcionamiento de 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 deshacerse del 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.

+2

Gracias. Esto me parece ser un candidato para una solución genérica, aunque ... – MartinStettner

+4

@LadislavMrnka Seguramente 'Context.Refresh()' es un contraejemplo a su afirmación de que no hay operación de reversión? El uso de 'Refresh()' parece ser un mejor enfoque (es decir, más orientado a entidades específicas) que eliminar el contexto y perder todos los cambios rastreados. – Rob

+14

@robjb: No. Refresh puede actualizar solo una sola entidad o conjunto de entidades que defina manualmente, pero la funcionalidad de actualización solo afecta a las propiedades simples (no a las relaciones). Tampoco resuelve el problema con entidades añadidas o eliminadas. –

2

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

+4

Nota: Los cambios volverán si la entidad se cambia nuevamente. –

16

Esto funcionó para mí:

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

Dónde item es la entidad de cliente va a ser revertida.

3

"Esto funcionó para mí:

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

Dónde item es la entidad de cliente va a ser revertida."


He hecho pruebas con ObjectContext.Refresh en SQL Azure, y los "RefreshMode.StoreWins" dispara una consulta en la base de datos para cada entidad y produce una pérdida de rendimiento. Según la documentación de Microsoft():

ClientWins: los cambios de propiedad realizados a los objetos en el contexto del objeto no se reemplazan con los 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 con los valores de la fuente de datos.

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

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

cordiales,

Henrique Clausing

2

Me pareció que para ser trabajando bien en mi contexto:

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

+1

Creo que esto evitará que los cambios en la entidad persistan al llamar 'DbContext.SaveChanges()', pero no devolverá los valores de la entidad a los valores originales. Y si el estado de la entidad se modifica a partir de un cambio posterior, posiblemente todas las modificaciones anteriores se mantendrán al momento de guardarse? –

+1

Revise este enlace http://code.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4 Dice que establecer una entidad en estado Unchaged restaura los valores originales "debajo de las cubiertas". – Hannish

25
dbContext.Entry(entity).Reload(); 

Accroding a MSDN:

Vuelve a cargar la entidad de la base de datos sobrescribiendo los valores de propiedad con los 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 de base de datos tiene algunos inconvenientes:

  • tráfico de red Tiempo de sobrecarga
  • DB
  • el aumento de la respuesta de las aplicaciones
109

de consulta ChangeTracker de DbContext para artículos 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. Por último, establezca 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; 
     } 
    } 
} 
+1

Gracias - ¡esto realmente me ayudó! – Matt

+0

¡Eres el mejor! –

+4

Probablemente también deba establecer los valores originales en las entradas eliminadas. Es posible que primero haya cambiado un artículo y lo haya eliminado después de eso. –

11

Manera fácil sin rastrear los cambios. Debería ser más rápido que mirar a todas las entidades.

public void Rollback() 
{ 
    dataContext.Dispose(); 
    dataContext= new MyEntities(yourConnection); 
} 
+0

Simple de hecho, pero el rendimiento? – majkinetor

+0

Hora de crear un objeto de entidad único ... que es un par de ms (50 ms). Looping a través de la colección puede ser más rápido o más largo dependiendo de su tamaño. En cuanto al rendimiento, O (1) rara vez es un problema en comparación con O (n). [Notación de Big O] (http://en.wikipedia.org/wiki/Big_O_notation) – Guish

+0

No le sigue: rendimiento de la eliminación y recreación de la conexión. Lo probé en un proyecto existente y terminó un poco más rápido que el anterior procedimiento 'Rollback', lo que hace que sea una opción mucho mejor si se quiere revertir el estado completo de la base de datos. Rollback podría recoger cereza tho. – majkinetor

2

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 como de ADO.NET Entity Framework 5,0)

/// <summary> 
/// Undoes any pending updates 
/// </summary> 
public void UndoUpdates(DbContext dbContext) 
{ 
    //Get list of entities that are marked as modified 
    List<DbEntityEntry> 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]); 
     } 
    } 
} 
6
// 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 restored. 
     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; 
    } 
} 

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

0

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

encontrar aquí: How do I clone a generic list in C#?

Para ser utilizado como:

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

De esta manera pude clonar mi lista de entidades de productos, aplicar un descuento a cada elemento y no tener que preocuparse de revertir cualquier cambios 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.

1

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 deshacernos 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 están por ser- cargado. Para estos casos, necesitamos devolver todo a los valores originales sin golpear la base de datos y sin soltar la conexión existente.

continuación se muestra la 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.

Cuestiones relacionadas