Udostępnij za pośrednictwem


Using DbContext in EF 4.1 Part 4: Add/Attach and Entity States

 


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.

For Add/Attach and Entity States see https://msdn.com/data/jj592676


 

Introduction

Version 4.1 of the Entity Framework contains both the Code First approach and the new DbContext API. This API provides a more productive surface for working with the Entity Framework and can be used with the Code First, Database First, and Model First approaches. This is the fourth post of a twelve part series containing collections of patterns and code fragments showing how features of the new API can be used.

The posts in this series do not contain complete walkthroughs. If you haven’t used EF 4.1 before then you should read Part 1 of this series and also Code First Walkthrough or Model and Database First with DbContext before tackling this post.

Entity states and SaveChanges

An entity can be in one of five states as defined by the EntityState enumeration. These states are:

  • Added: the entity is being tracked by the context but does not yet exist in the database
  • Unchanged: the entity is being tracked by the context and exists in the database, and its property values have not changed from the values in the database
  • Modified: the entity is being tracked by the context and exists in the database, and some or all of its property values have been modified
  • Deleted: the entity is being tracked by the context and exists in the database, but has been marked for deletion from the database the next time SaveChanges is called
  • Detached: the entity is not being tracked by the context

SaveChanges does different things for entities in different states:

  • Unchanged entities are not touched by SaveChanges. Updates are not sent to the database for entities in the Unchanged state.
  • Added entities are inserted into the database and then become Unchanged when SaveChanges returns.
  • Modified entities are updated in the database and then become Unchanged when SaveChanges returns.
  • Deleted entities are deleted from the database and are then detached from the context.

The following examples show ways in which the state of an entity or an entity graph can be changed.

Adding a new entity to the context

A new entity can be added to the context by calling the Add method on DbSet. This puts the entity into the Added state, meaning that it will be inserted into the database the next time that SaveChanges is called. For example:

 using (var context = new UnicornsContext())
{
    var unicorn = new Unicorn { Name = "Franky", PrincessId = 1};
    context.Unicorns.Add(unicorn);
    context.SaveChanges();
}

Another way to add a new entity to the context is to change its state to Added. For example:

 using (var context = new UnicornsContext())
{
    var unicorn = new Unicorn { Name = "Franky", PrincessId = 1};
    context.Entry(unicorn).State = EntityState.Added;
    context.SaveChanges();
}

Finally, you can add a new entity to the context by hooking it up to another entity that is already being tracked. This could be by adding the new entity to the collection navigation property of another entity or by setting a reference navigation property of another entity to point to the new entity. For example:

 using (var context = new UnicornsContext())
{
    // Add a new princess by setting a reference from a tracked unicorn
    var unicorn = context.Unicorns.Find(1);
    unicorn.Princess = new Princess { Name = "Belle" };

    // Add a new unicorn by adding to the collection of a tracked princess
    var princess = context.Princesses.Find(2);
    princess.Unicorns.Add(new Unicorn { Name = "Franky" });

    context.SaveChanges();
}

Note that for all of these examples if the entity being added has references to other entities that are not yet tracked then these new entities will also added to the context and will be inserted into the database the next time that SaveChanges is called.

Attaching an existing entity to the context

If you have an entity that you know already exists in the database but which is not currently being tracked by the context then you can tell the context to track the entity using the Attach method on DbSet. The entity will be in the Unchanged state in the context. For example:

 var existingUnicorn = GetMyExistingUnicorn();

using (var context = new UnicornsContext())
{
    context.Unicorns.Attach(existingUnicorn);

    // Do some more work... 

    context.SaveChanges();
}

In the example above the GetMyExistingUnicorn returns a unicorn that is known to exist in the database. For example, this object might have come from the client in an n-tier application.

Note that no changes will be made to the database if SaveChanges is called without doing any other manipulation of the attached entity. This is because the entity is in the Unchanged state.

Another way to attach an existing entity to the context is to change its state to Unchanged. For example:

 var existingUnicorn = GetMyExistingUnicorn();

using (var context = new UnicornsContext())
{
    context.Entry(existingUnicorn).State = EntityState.Unchanged;

    // Do some more work... 

    context.SaveChanges();
}

Note that for both of these examples if the entity being attached has references to other entities that are not yet tracked then these new entities will also attached to the context in the Unchanged state.

Attaching an existing but modified entity to the context

If you have an entity that you know already exists in the database but to which changes may have been made then you can tell the context to attach the entity and set its state to Modified. For example:

 var existingUnicorn = GetMyExistingUnicorn();

using (var context = new UnicornsContext())
{
    context.Entry(existingUnicorn).State = EntityState.Modified;

    context.SaveChanges();
}

When you change the state to Modified all the properties of the entity will be marked as modified and all the property values will be sent to the database when SaveChanges is called. More about working with property values is covered in Part 5 of this series.

Note that if the entity being attached has references to other entities that are not yet tracked, then these new entities will attached to the context in the Unchanged state—they will not automatically be made Modified. If you have multiple entities that need to be marked Modified you should set the state for each of these entities individually.

Changing the state of a tracked entity

You can change the state of an entity that is already being tracked by setting the State property on its entry. For example:

 var existingUnicorn = GetMyExistingUnicorn();

using (var context = new UnicornsContext())
{
    context.Unicorns.Attach(existingUnicorn); // Entity is in Unchanged state
    context.Entry(existingUnicorn).State = EntityState.Modified;

    context.SaveChanges();
}

Note that calling Add or Attach for an entity that is already tracked can also be used to change the entity state. For example, calling Attach for an entity that is currently in the Added state will change its state to Unchanged.

Insert or update pattern

A common pattern for some applications is to either Add an entity as new (resulting in a database insert) or Attach an entity as existing and mark it as modified (resulting in a database update) depending on the value of the primary key. For example, when using database generated integer primary keys it is common to treat an entity with a zero key as new and an entity with a non-zero key as existing. This pattern can be achieved by setting the entity state based on a check of the primary key value. For example:

 public void InsertOrUpdate(DbContext context, Unicorn unicorn)
{
    context.Entry(unicorn).State = unicorn.Id == 0 ?
                                   EntityState.Added :
                                   EntityState.Modified;
    context.SaveChanges();
}

Note that when you change the state to Modified all the properties of the entity will be marked as modified and all the property values will be sent to the database when SaveChanges is called. More about working with property values is covered in Part 5 of this series.

Summary

In this part of the series we talked about entity states and showed how to add and attach entities and change the state of an existing entity.

As always we would love to hear any feedback you have by commenting on this blog post.

For support please use the Entity Framework Forum.

Arthur Vickers

Developer

ADO.NET Entity Framework

Comments

  • Anonymous
    February 05, 2011
    Hi, the first snippet: using (var context = new UnicornsContext()) {    var unicorn = new Unicorn { Name = "Franky" };    context.Unicorns.Add(unicorn);    context.SaveChanges(); } generates an exception related to violation of a foreign key constraint on the Unicorn table. I guess you must provide a reference to an existing princess object before adding a new unicorn. regards, Przemek S.

  • Anonymous
    February 07, 2011
    @Przemek: Thanks for pointing that out. I have updated the fragments to include the FK of a princess so they should work now.

  • Anonymous
    February 25, 2011
    What about deleting?  Do I have to first query for or Find the object before I can delete it?  An example of this would be helpful.

  • Anonymous
    March 13, 2011
    Great post! You guys saved my weekend... :o) Just a quick note: I have a strong Java background and just jumped into the .Net C# world. I'm enjoying a lot. Well done!

  • Anonymous
    April 14, 2011
    According to the documentation msdn.microsoft.com/.../system.data.entity.dbset.add%28v=vs.103%29.aspx The DbSet.Add() method returns the object you just added.  Why would you need the object back if you already just instantiated the object?

  • Anonymous
    April 18, 2011
    Nice post. In the "Attaching an existing entity to the context" part, this statement can be read: "if the entity being attached has references to other entities that are not yet tracked then these new entities will also attached to the context in the Unchanged state"... Does this mean that if we attach an entity that has hundreds of thousands of references, all of these references will be loaded to the memory when attached? e.g. A service offer having hundreds of thousands of subscribers. Reply is appreciated. Thanks.

  • Anonymous
    May 04, 2011
    Attaching an existing but modified entity to the context using context.Entry(existingEntity).State = EntityState.Modified; does not update changes to collection of child entity I have a member entity which has collection on investment in it. a member can hv zero or many investments. When I try to update changes to member and one of their investments, the member info gets update, but the investment in not changed. Could someone please suggest why? Thanks

  • Anonymous
    May 04, 2011
    JobLot: I don't have any hands-on experience with your particular scenario, but I believe that although the child entities get attached alongside the parent entity, the change state for all of the children is left at Unchanged. I think you would need to loop through all of the children and set the state to Modified. <a href="http://omniscientist.net">http://omniscientist.net</a>

  • Anonymous
    May 26, 2011
    On the header "Attaching an existing but modified entity to the context": should we add the code " context.Unicorns.Attach(existingUnicorn);" before "context.Entry(existingUnicorn).State = EntityState.Modified;"

  • Anonymous
    July 13, 2011
    JohnnyFee:  I think you are correct.  I was reading and it seems like the code for those two sections are in the wrong places.  They need to be switched, so the code sample matches the section description.

  • Anonymous
    August 11, 2011
    Hi! I have an issue with the Add() method. I create a new Object (in this expample it would be a unicorn) and then I get another one from the database (let's say it's a princess). Then I give the just created Unicorn the "old" existing princess. When I now add the unicorn to the context the princess is marked as added, too, which causes EF to try to insert it into the database again. Is there a way to prevent this without iterating over all related entities and manipulate the state manually?

  • Anonymous
    January 22, 2012
    Please help me with this issue: http://goo.gl/oDHVp I can't attach values when using inherited tables, I see the following error: A value shared across entities or associations is generated in more than one location. Check that mapping does not split an EntityKey to multiple store-generated columns.

  • Anonymous
    January 24, 2012
    @Kyralessa: To delete you can either query/Find and then delete or Attach a stub containing necessary information and then delete. Necessary information is always the primary key and then also any concurrency tokens if you have them. @Anon: This post describes why getting the object back from Add is useful: blog.oneunicorn.com/.../ef-4-1-quick-tip-addattach-returns-your-entity @tensai: I’m not sure what you mean by “loaded to the memory”. It means that Attach will essentially always attach the full graph of entities that you currently have loaded into memory and are reachable from the existing object. It will not load new objects into memory that are not already in memory. @JobLot You will need to use the change tracker API—context.ChangeTracker.Entries—to process the child entities and mark them as Modified if they need to be saved to the database. Note though that if the objects are already being tracked but have not changed then it is usually not required to save them to the database since doing so would just be writing the same data that is already there. If the objects have changed then DetectChanges (or equivalent) should detect this and mark the entities as Modified automatically. @JohnnyFee @Person: There is no need to Attach before setting the state to Modified. Setting the state to Modified will do the Attach first, if necessary. The example that shows Attach first is perhaps not clear. The idea is that if the entity is already tracked, which calling Attach sets up, then changing the state to Modified will change its state to Modified. Maybe this is too simple to even need an example.  @Ralf: This shouldn’t happen if the entity that you got from the database is being tracked by the context. If you’re getting the entity in some way where it isn’t being tracked then you should Attach it first before making the relationship to the new entity. @Shimmy: I can see your post. If you still have a question, then please comment again. Thanks, Arthur

  • Anonymous
    November 11, 2012
    Thanks for reminding me what Context.Entry did I've been perplexed wondering why my entities weren't being saved!

  • Anonymous
    March 05, 2013
    That was a helpful post! Thank you.

  • Anonymous
    June 23, 2013
    thank you it worked fine for me