Deshacer cambios en entidades de entity framework


Esto podría ser una pregunta trivial, pero: Desde ADO.NET entity framework realiza un seguimiento automático de los cambios (en las entidades generadas) y, por lo tanto, mantiene los valores originales, ¿cómo puedo revertir los cambios realizados en los objetos entity?

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

Ahora tengo dos botones "Accept" y "Revert": si se hace clic en "Accept", llamo a Context.SaveChanges() y los objetos cambiados se escriben de nuevo en la base de datos. Si "Revertir" es al hacer clic, me gustaría que todos los objetos obtuvieran sus valores de propiedad originales. ¿Cuál sería el código para eso?

Gracias

Author: MartinStettner, 2011-03-29

12 answers

No hay ninguna operación 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 revertirá los cambios en las propiedades / relaciones de navegación.

La forma común de "revertir cambios" es disponer el contexto y recargar entidades. Si quieres evitar recargar debes crear clones de entidades y modificar esos clones en new contexto del objeto. Si el usuario cancela los cambios, seguirá teniendo entidades originales.

 61
Author: Ladislav Mrnka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-29 06:09:31

Consulta ChangeTracker de DbContext para elementos sucios. Establezca el estado de los elementos eliminados en sin cambios y los elementos agregados en separados. Para los elementos modificados, utilice los valores originales y establezca los valores actuales de la entrada. Finalmente establezca el estado de la entrada modificada en 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;
        }
    }
 }
 122
Author: Taher Rahgooy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-23 04:08:05
dbContext.Entry(entity).Reload();

Según 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 Sin cambios estado 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 de DB
  • el aumento del tiempo de respuesta de la aplicación
 25
Author: Lu55,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-22 23:00:17

Esto funcionó para mí:

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

Donde item es la entidad cliente a revertir.

 17
Author: Matyas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-09 00:53:55

Manera fácil sin rastrear 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);
}
 11
Author: Guish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-31 20:56:45
// 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; 
    } 
} 

Funcionó para mí. Sin embargo, debe recargar sus datos desde el contexto para traer los datos antiguos. Fuente aquí

 6
Author: Alejandro del Río,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-25 08:31:07

" Esto funcionó para mí:

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

Donde item es la entidad cliente a revertir."


He hecho pruebas con ObjectContext.Actualizar en SQL Azure, y el " Modo de actualización.StoreWins " dispara una consulta contra la base de datos para cada entidad y causa una fuga de rendimiento. Basado en la documentación de microsoft ():

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

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

ClientWins tampoco es una buena idea, porque disparar .SaveChanges confirmará los cambios "descartados" en la fuente de datos.

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

Saludos,

Henrique Clausing

 3
Author: Henrique Clausing Cunha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-04 21:17:03

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

 2
Author: Nazar Gargol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-07 08:31:56

Encontré que esto funciona bien en mi contexto:

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

 2
Author: Alex Pop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-08 15:23:45

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 reflection para establecer valores de una manera genérica. (Esto funciona a partir de EntityFramework 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]); 
        }
    }
}
 2
Author: BizarroDavid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-23 23:30:57

Estamos usando EF 4, con el contexto de Objeto Heredado. Ninguna de las soluciones anteriores respondió directamente a esto para mí although aunque lo hizo responder en el largo plazo empujándome en la dirección correcta.

No podemos simplemente disponer y reconstruir el contexto porque algunos de los objetos que tenemos colgando alrededor en la memoria (maldita sea que la carga perezosa!!) 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 martilleo de la base de datos y sin dejar caer 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.

 1
Author: Jerry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-26 22:19:43

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

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

Se utilizará como:

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

De esta manera pude clonar mi lista de entidades de productos, aplicar un descuento a cada artículo y no tener que preocuparme por revertir ningún cambio en la entidad original. No es necesario hablar con el DbContext y pedir una actualización o trabajar con el ChangeTracker. Se podría decir que no estoy haciendo pleno uso 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 éxito de rendimiento.

 0
Author: IbrarMumtaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:46