c# .net entity-framework dbcontext undo

c# - DbContext descarta cambios sin eliminar



.net entity-framework (7)

Tengo una aplicación cliente de escritorio que usa ventanas modales para establecer propiedades para objetos jerárquicos. Como esta es una aplicación cliente y el acceso al DbContext no está enhebrado, utilizo un contexto de larga duración en el Formulario principal que se transfiere a niños modales.

Estas ventanas modales utilizan PropertyGrid para mostrar las propiedades de la entidad y también tienen botones para cancelar. Si se modifican datos y se presiona el botón cancelar, los cambios se reflejan en el formulario principal (donde no puedo eliminar el DbContext object ).

¿Hay alguna forma de descartar los cambios realizados si NO se ha llamado al método DbContext.SaveChanges() ?

ACTUALIZACIÓN: Entity Framework Version 4.4.


¿Qué tal envolverlo en una transacción?

using(var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })){ // Do something context.SaveChanges(); // Do something else context.SaveChanges(); scope.Complete(); }


En el caso simple de cancelar los cambios realizados a las propiedades de una sola entidad, puede establecer los valores actuales en los valores originales.

context.Entry(myEntity).CurrentValues.SetValues(context.Entry(myEntity).OriginalValues); //you may also need to set back to unmodified - //I''m unsure if EF will do this automatically context.Entry(myEntity).State = EntityState.UnModified;

o alternativamente recargar (pero da como resultado db hit)

context.Entry(myEntity).Reload();


Esto se basa en la respuesta de Surgeu Shuvalov. Agrega soporte para cambios en la propiedad de navegación.

public void RejectChanges() { RejectScalarChanges(); RejectNavigationChanges(); } private void RejectScalarChanges() { foreach (var entry in ChangeTracker.Entries()) { switch (entry.State) { case EntityState.Modified: case EntityState.Deleted: entry.State = EntityState.Modified; //Revert changes made to deleted entity. entry.State = EntityState.Unchanged; break; case EntityState.Added: entry.State = EntityState.Detached; break; } } } private void RejectNavigationChanges() { var objectContext = ((IObjectContextAdapter)this).ObjectContext; var deletedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).Where(e => e.IsRelationship && !this.RelationshipContainsKeyEntry(e)); var addedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Where(e => e.IsRelationship); foreach (var relationship in addedRelationships) relationship.Delete(); foreach (var relationship in deletedRelationships) relationship.ChangeState(EntityState.Unchanged); } private bool RelationshipContainsKeyEntry(System.Data.Entity.Core.Objects.ObjectStateEntry stateEntry) { //prevent exception: "Cannot change state of a relationship if one of the ends of the relationship is a KeyEntry" //I haven''t been able to find the conditions under which this happens, but it sometimes does. var objectContext = ((IObjectContextAdapter)this).ObjectContext; var keys = new[] { stateEntry.OriginalValues[0], stateEntry.OriginalValues[1] }; return keys.Any(key => objectContext.ObjectStateManager.GetObjectStateEntry(key).Entity == null); }


Intentas hacerlo manualmente, algo así ... no estoy seguro de que esto funcione para tu situación, pero puedes intentarlo:

public void UndoAll(DbContext context) { //detect all changes (probably not required if AutoDetectChanges is set to true) context.ChangeTracker.DetectChanges(); //get all entries that are changed var entries = context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged).ToList(); //somehow try to discard changes on every entry foreach (var dbEntityEntry in entries) { var entity = dbEntityEntry.Entity; if (entity == null) continue; if (dbEntityEntry.State == EntityState.Added) { //if entity is in Added state, remove it. (there will be problems with Set methods if entity is of proxy type, in that case you need entity base type var set = context.Set(entity.GeType()); set.Remove(entity); } else if (dbEntityEntry.State == EntityState.Modified) { //entity is modified... you can set it to Unchanged or Reload it form Db?? dbEntityEntry.Reload(); } else if (dbEntityEntry.State == EntityState.Deleted) //entity is deleted... not sure what would be the right thing to do with it... set it to Modifed or Unchanged dbEntityEntry.State = EntityState.Modified; } }


Me encontré con una desagradable sorpresa: llamar a ChangeTracker.Entries () se bloquea si necesita deshacer los cambios debido a la excepción en DbContext, por ejemplo

System.InvalidOperationException: ''The property ''Id'' on entity type ''TestEntity'' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke ''SaveChanges'' then associate the dependent with the new principal.''

así que se me ocurrió una versión pirateada de la reversión manual

public async Task RollbackChanges() { var oldBehavoir = ChangeTracker.QueryTrackingBehavior; var oldAutoDetect = ChangeTracker.AutoDetectChangesEnabled; // this is the key - disable change tracking logic so EF does not check that there were exception in on of tracked entities ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; ChangeTracker.AutoDetectChangesEnabled = false; var entries = ChangeTracker.Entries().ToList(); foreach (var entry in entries) { switch (entry.State) { case EntityState.Modified: await entry.ReloadAsync(); break; case EntityState.Deleted: entry.State = EntityState.Modified; //Revert changes made to deleted entity. entry.State = EntityState.Unchanged; break; case EntityState.Added: entry.State = EntityState.Detached; break; } } ChangeTracker.QueryTrackingBehavior = oldBehavoir; ChangeTracker.AutoDetectChangesEnabled = oldAutoDetect; }


Puedes aplicar esto:

context.Entry(TEntity).Reload();

Lo intento y funciona bien para mí.

Nota: Este método ( Recargar ) 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.


public void RejectChanges() { foreach (var entry in ChangeTracker.Entries()) { switch (entry.State) { case EntityState.Modified: case EntityState.Deleted: entry.State = EntityState.Modified; //Revert changes made to deleted entity. entry.State = EntityState.Unchanged; break; case EntityState.Added: entry.State = EntityState.Detached; break; } } }