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
Thank you for this explanatory post
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.
@Joal When an entity is deleted its relationships to other entities are severed. This includes setting FKs to null for nullable FKs or marking the FKs as conceptually null (don’t ask!) if the FK property is not nullable. You’ll need to reset the FK property values to the values that they had previously in order to re-form the relationships. This may include FK properties in other entities for relationships where the deleted entity is the principal of the relationship–e.g. has the PK rather than the FK. I know this is a pain–it would be great if it could be made easier in the future, but for now it is what it is.
Thanks,
Arthur
What about DbEntityEntry.Reload Method?
http://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.dbentityentry.reload(v=vs.103).aspx
@Robert What specifically are you asking about the Reload method?
Thanks Arthur. this is very useful idea and I gotta use it in my work.
Thanks a million for this Arthur!
GOD bless you man!
Thanks Arthur for this post. I had a flaw in my POCO and your post exposed it.
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
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?
Randall: see the reply to Robert below.
Thanks,
Arthur
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.
I use Context.Refresh(RefreshMode.StoreWins, entity); for the first part, is that a good approach? This also sets the entity back to unchanged.
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.