DbContext discards changes without recycling

I have a desktop client application that uses modal windows to set properties for hierarchical objects. Since this is a client application, and access to DbContext is not streaming, I use a long context in the main form, which is passed to the modal children.

These modal windows use the PropertyGrid property to display object properties and also have cancel buttons. If any data is changed and the cancel button is pressed, the changes are reflected in the parent form (where I cannot dispose of the DbContext object ).

Is there a way to undo any changes made if the DbContext.SaveChanges() method was NOT called?

UPDATE: Entity Version 4.4.

+43
c # undo entity-framework dbcontext
May 08 '13 at 9:27
source share
7 answers

How to wrap it in a transaction?

  using(var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })){ // Do something context.SaveChanges(); // Do something else context.SaveChanges(); scope.Complete(); } 
+9
May 8 '13 at 9:37
source share
 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; } } } 

Update:

Some users suggest adding .ToList() to exclude the "collection has been modified" exception. But I think there is a reason for this exception.

How do you get this exception? You are probably using the context in a non-thread safe manner.

+110
Feb 28 '14 at 2:35
source share

In the simple case of canceling changes made to the properties of one object, you can set the current values ​​to the original values.

 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; 

or alternatively reboot (but leads to db hit)

context.Entry(myEntity).Reload();

+16
May 08 '13 at 10:38
source share

You colud try to do it manually, something like this .. not sure if this works for your script, but you can try:

 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; } } 
+4
May 8 '13 at 10:21
source share

You can apply this:

 context.Entry(TEntity).Reload(); 

I try, and his work is good for me.

Note. This method ( Reload ) Reloads the object from the database, overwriting any property values ​​with values ​​from the database. After calling this method, the object will be in the Unchanged state.

+3
Nov 16 '13 at 1:55
source share

This is based on the answer of Sergei Shuvalov. It adds support for changing navigation properties.

 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); } 
+3
Sep 27 '16 at 15:07
source share

I came across an unpleasant surprise - call ChangeTracker.Entries () if you need to undo changes due to an exception in DbContext, for example

 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.' 

so I came up with a hacked version of manual rollback

  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; } 
0
Mar 04 '18 at 21:57
source share



All Articles