9

I was working with RIA services where ObjectContext has RejectChanges() method. However, I am now working with EF 4.4 in a desktop application and I cannot find that method. So, my question is: in a scenrario where I allow user to do batch CrUD operations on collection, how would I revert all the changes? I could go with recreating the Context and fetching the data again, but that sound highly unnecessary if I need to revert changes back to 1-2 entities.

So, what is the best way to reject changes? And also, how do we know if the context is doing something (IsBusy)?

Tilak
  • 30,108
  • 19
  • 83
  • 131
Goran
  • 6,328
  • 6
  • 41
  • 86

5 Answers5

12

EF doesn't have any direct "reject changes" operation. You can go through entity entries in ChangeTracker / ObjectStateManager and overwrite current values with original values for modified entities. You can also detach added entities and change deleted entities back to unchanged but that all will mostly work only if you (or EF internally) didn't change the state of any independent association (relation). If you work with relations as well the whole thing can become much more complicated because you must revert relations as well - in such case it is simpler to reload data.

For reverting changes in DbContext API you can try this:

foreach (var entry in context.ChangeTracker
                             .Entries<YourEntityType>()
                             .Where(e => e.State == EntityState.Modified))
{
    entry.CurrentValues.SetValues(entry.OriginalValues);
}

In this case I think the main problem is the way how you work with entities - you allow changes on live data and EF does its logic on behind to keep data consistent when changes are performed but later on you decide that those changes will not be saved. In this case you should do one of following:

  • Discard changed data and reload whole data set (by recreating the context)
  • Separate this logic to not work on live data and push data modification to EF context only when the modification is really confirmed

Context is doing something if you say it to do something. It never becomes busy itself.

Lucas B
  • 11,793
  • 5
  • 37
  • 50
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Recreating the Context would be a fine solution. However, I was having problems with synchronizing related data. An example would be that I had CRUD on CollectionA on ViewA, and CRUD on COllectionB on ViewB. CollectionB also holds reference to CollectionA. So, if I have 2 Context objects, both for CollectionA and CollectionB, how would I sync ViewB, if I change CollectionA on ViewA? When having one Context everything is sync, since we are actually working on only one instance collectionA, not two. Recreating "global" Context would create a mess. :) Did I made mistake by using "global" context? – Goran May 10 '12 at 16:11
  • As for the "Busy", I was concerned what will happen if I issue a Load in async maner, and then I try to issue another Load, while previously didn't finish? This way I could check if it is "Busy", so I could queue a new Load. – Goran May 10 '12 at 16:12
  • +1 for noticing what most people seem to miss re. the relations: it's possible to have tracked changes that won't be picked up simply by looking through the Entries collection for State != Unchanged. (EF v6.) – mwardm Aug 14 '15 at 14:52
11
public void RejectChanges()
        {
            foreach (var entry in ChangeTracker.Entries())
            {
                switch (entry.State)
                {
                    case EntityState.Modified:
                        {
                            entry.CurrentValues.SetValues(entry.OriginalValues);
                            entry.State = EntityState.Unchanged;
                            break;
                        }
                    case EntityState.Deleted:
                        {
                            entry.State = EntityState.Unchanged;
                            break;
                        }
                    case EntityState.Added:
                        {
                            entry.State = EntityState.Detached;
                            break;
                        }
                }
            }
        }
Sergey Shuvalov
  • 2,098
  • 2
  • 17
  • 21
  • This seems to be the only answer that handles all states without hitting the database. +1 – Dan Bechard Sep 19 '14 at 15:27
  • What if I only changed references? Especially pure m:n when there is not any foreign key property affected? – springy76 Oct 09 '15 at 11:16
  • I think that `entry.CurrentValues.SetValues(entry.OriginalValues)` is not required here, as setting the state to `entry.State = EntityState.Unchanged` will revert to the original values automatically. – Ahmed Suror Sep 24 '22 at 00:25
2

I know this is an old question. However, none of the answers fit my situation. I needed to reject the changes on only 1 entity in a collection. This is what worked for me:

    var objectContext = (myDbContext as IObjectContextAdapter).ObjectContext;
    objectContext.Refresh(RefreshMode.StoreWins, partMaster);
Tarzan
  • 4,270
  • 8
  • 50
  • 70
  • Although the question was about collection changes, not single entity, I do not see how none of the answers helped you? The answer right above yours (TheSoul75) does exactly the same as you are suggestion, only it does it for all items in collection. – Goran Jan 21 '14 at 23:29
  • Goran, if all of the changes in the collection were rejected then my data would be invalid. Only one of the items in the collection needs to revert to the original values. I need a more granular approach. I know sometimes others have need for granular control also. I posted the answer to help others that have similar requirements. – Tarzan Jan 22 '14 at 17:01
  • 1
    Well, you do not use foreach, but you are essentially using the same techicque (Refresh with StoreWins), so I do not see the added value in your answer, since you have just removed the loop. – Goran Jan 23 '14 at 17:35
1

This works for me:

public void RejectChanges() {
    var context = ((IObjectContextAdapter)this).ObjectContext;
    foreach (var change in this.ChangeTracker.Entries()) {
        if (change.State == EntityState.Modified) {
             context.Refresh(RefreshMode.StoreWins, change.Entity);
        }
        if (change.State == EntityState.Added) {
             context.Detach(change.Entity);
        }
     }
}

this=DbContext in this case

TheSoul75
  • 11
  • 1
  • 1
    What about the deleted entities? Also, how would you get new data for entities that are changed by other users, not you? – Goran Jul 13 '12 at 17:47
0

This may be an old answer but useful to any new visitors.... The Reload function will reload the object from the data source and overwrite any existing changes and the newly loaded entity will have a unchanged status.

public static void UndoEntityChanges(object Entity)
{
    <EFModelContainer>.Entry(Entity).Reload();
}
Kevbo
  • 947
  • 9
  • 12