Rejecting changes to entities in EF 4.1

Imagine your app has an entity to which changes have been made and you want to reject those changes such that they won’t be saved to the database when SaveChanges is called. Using Entity Framework 4.1 you can do this by setting the state of the entity to Unchanged. For example:

context.Entry(myEntity).State = EntityState.Unchanged;

 

Here’s a simple test that demonstrates this behavior:

[TestMethod]
public void Setting_state_of_Modified_entity_to_Unchanged_rejects_changes()
{
    using (var context = new BlogContext())
    {
        // Find an entity and modify some property.
        var post = context.Posts.Where(p => p.Title == "Lazy Unicorns").Single();
        post.Title = "Bouncy Unicorns";
        Assert.AreEqual(EntityState.Modified, context.Entry(post).State);

        // Reject the change
        context.Entry(post).State = EntityState.Unchanged;

        Assert.AreEqual(EntityState.Unchanged, context.Entry(post).State);
        Assert.AreEqual("Lazy Unicorns", post.Title);

        // Nothing will be saved.
        Assert.AreEqual(0, context.SaveChanges());
    }
}

There isn’t a single method to reject changes to all entities, but you can easily find all Modified entities in the context and set the state of each to Unchanged. For example:

foreach (var entry in context.ChangeTracker.Entries()
                        .Where(e => e.State == EntityState.Modified))
{
    entry.State = EntityState.Unchanged;
}

If you may also have Added entities then you might want to detach these entities at the same time. For example:

foreach (var entry in context.ChangeTracker.Entries())
{
    if (entry.State == EntityState.Modified)
    {
        entry.State = EntityState.Unchanged;
    }
    else if (entry.State == EntityState.Added)
    {
        entry.State = EntityState.Detached;
    }
}

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.

One caveat to this is that if you are using independent associations in your model and want to reject changes to the relationships between entities then you need to write a lot more nasty code dealing with ObjectStateEntry and RelatedEnd objects for relationships. This is another reason to map FKs in your model if you can and not use independent associations. (At some point we hope to allow you to map FKs to the conceptual model but still not expose them in your object model—this is sometimes known as shadow-state. This would allow simple code like that above to work without having FK properties on your model.)

Thanks for reading!

Arthur

About Arthur Vickers

Developer on the Entity Framework team at Microsoft.
This entry was posted in Change Tracking, Code First, DbContext API, Entity Framework and tagged , , , . Bookmark the permalink.

16 Responses to Rejecting changes to entities in EF 4.1

  1. Thank you for this explanatory post

  2. Joel Gordon says:

    Hi Arthur,

    Thanks very much for the post – works well for rejecting changes to Modified and Added entities.

    How do I reject changes for entities that are in the Deleted state ?

    I have tried setting the State to Unchanged for Deleted entities by adding a case your code as follows:

    foreach (var entry in context.ChangeTracker.Entries())
    {
    if (entry.State == EntityState.Modified)
    {
    entry.State = EntityState.Unchanged;
    }
    else if (entry.State == EntityState.Added)
    {
    entry.State = EntityState.Detached;
    }
    // Handle deleted entities:
    else if ( entry.State == EntityState.Deleted )
    {
    entry.State = EntityState.Unchanged;
    }
    }

    But this does not seem to reject changes to FK relationships for deleted entities, i.e. the entity does not appear to be put back into the collection which it was a member of when it was deleted.

    Could it be that the order in which changes to entities are rejected is important?

    I’m happy to give you a small but complete example if this would help.

    Thanks very much for your time,
    Joel Gordon.

  3. Thanks Arthur. this is very useful idea and I gotta use it in my work.

  4. Edson Ferreira says:

    Thanks a million for this Arthur!
    GOD bless you man!

  5. Matt Gehres says:

    Thanks Arthur for this post. I had a flaw in my POCO and your post exposed it.

  6. Robert says:

    I do not see the ChangeTracker as a property. Am I missing a declaration/using?

    • Robert: Make sure that you are using DbContext and not ObjectContext. The ChangeTracker property hasn’t changed since DbContext was released so it should be there.

      Thanks,
      Arthur

  7. Randall Doser says:

    Sorry for the dumb question, but I am relatively new to this game. My context has no ChangeTracker property. I have EntityFramework 4.3.1 in my project references and I am targeting .NET Framework 4 Client Profile. What might I be doing wrong?

  8. Marcel says:

    Arthur is it possible to use this feature with the ObjectContext class without changing to the DbContex class? Using Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Unchanged), or does it only work with the DbContext class?

    • You can do the same thing with ObjectContext but you have to do some work yourself. From the post: “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.” Using ChangeObjectState does the second part of this but not the first so you won’t really be rejecting changes unless you manually set all property values to their original values before using ChangeObjectState.

      • Marcel says:

        I use Context.Refresh(RefreshMode.StoreWins, entity); for the first part, is that a good approach? This also sets the entity back to unchanged.

  9. Calling Refresh does a very similar thing the main difference being that it queries the database for the values to use whereas rejecting changes as described in this post does not hit the database. Assuming that the database hasn’t changed between the original query and the refresh then the results will be the same.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s