Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework Code First - Why can't I update complex properties this way?

I'm working on a small sample project using Entity Framework 4.1 (code first). My classes look like this:

public class Context : DbContext {     public IDbSet<Person> People { get; set; }     public IDbSet<EmployeeType> EmployeeTypes { get; set; } }  public class Person {     [Key]     public int Key { get; set; }     public string FirstName { get; set; }     public string LastName { get; set; }      virtual public EmployeeType EmployeeType { get; set; } }  public class EmployeeType {     [Key]     public int Key { get; set; }     public string Text { get; set; }      virtual public ICollection<Person> People { get; set; } } 

I've saved a couple EmployeeTypes ("first", "second") to the database, and I've saved a Person who has the first type. Now I want to modify the Person. I know I can do this by loading the Person, changing properties, and then saving. But what I want to do instead, which seems to me like it ought to work, is this:

var c = new Context(); var e = c.EmployeeTypes.Single(x => x.Text.Equals("second")); var p = new Person {              Key = originalKey,       // same key             FirstName = "NewFirst",  // new first name             LastName = "NewLast",    // new last name             EmployeeType = e };      // new employee type c.Entry(p).State = EntityState.Modified; c.SaveChanges(); 

Oddly, this changes FirstName and LastName but not EmployeeType. If I get a new Context and request this Person, the EmployeeType remains set to "first" as it was before this code ran.

What do I need to do to get the navigation properties to update, and not just the scalar properties? (This is especially puzzling because for EmployeeType, the only thing that actually needs to change is the foreign key in the Person table, and that key is a scalar property.)

(By the way, I know I can do this by retrieving the Person first, then changing properties one-by-one, but as I'm using model binding in ASP.NET MVC, it seems like this way would be easier because I'll have the updated person object already in my POST method.)

like image 950
Ryan Lundy Avatar asked Mar 31 '11 20:03

Ryan Lundy


People also ask

How do I update items in Entity Framework?

Update Objects in Entity Framework 4.0First retrieve an instance of the entity from the EntitySet<T> (in our case ObjectSet<Customer>), then edit the properties of the Entity and finally call SaveChanges() on the context.

What does Entitystate Modified do?

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.

How do you update data entity?

Updating the entity involves getting the entity from the database, make the necessary changes, and then call the SaveChanges to persist the changes in the database. There are two Scenario's that arise, when you update the data to the database.


2 Answers

You can try it different way:

var c = new Context(); var e = c.EmployeeTypes.Single(x => x.Text.Equals("second")); var p = new Person {              Key = originalKey,       // same key             FirstName = "NewFirst",  // new first name             LastName = "NewLast"};   // new last name c.People.Attach(p); // Attach person first so that changes are tracked  c.Entry(p).Reference(e => e.EmployeeType).Load();                p.EmployeeType = e; // Now context should know about the change c.Entry(p).State = EntityState.Modified; c.SaveChanges(); 

Other approach is exposing foreign key in your Person entity like:

public class Person {     [Key]     public int Key { get; set; }     public string FirstName { get; set; }     public string LastName { get; set; }     [ForeignKey("EmployeeType")]     public int EmployeeTypeKey { get; set; }     public virtual EmployeeType EmployeeType { get; set; } } 

This will change the type of relation between Person and EmployeeType from Independent association to Foreign key association. Instead of assigning the navigation property assign the foreign key property. This will allow you to modify relation by your current code.

Problem is that independent associations (those don't using foreign key property) are handled as separate object in state manager / change tracker. So your modification of the person didn't affect state of the existing relation neither set the new relation. I asked on MSDN how to do it with DbContext API but it is possible only if you cast DbContext to ObjectContext and use ObjectStateManager and ChangeRelationshipState.

like image 143
Ladislav Mrnka Avatar answered Oct 04 '22 09:10

Ladislav Mrnka


After trying a dozen different ways to do it the EF way, I concluded that there isn't a reasonable EF Code First way to do what I'm trying to do. So I used reflection. I created this method for my class that inherits from DbContext:

public void UpdateFrom<T>(T updatedItem) where T : KeyedItem {     var originalItem = Set<T>().Find(updatedItem.Key);     var props = updatedItem.GetType().GetProperties(         BindingFlags.Public | BindingFlags.Instance);     foreach (var prop in props)     {         var value = prop.GetValue(updatedItem, null);         prop.SetValue(originalItem, value, null);     } } 

All my objects inherit from an abstract class and have a primary key property in common, so this finds the existing object with the same key as the one passed in, and then updates the existing object's from the new one. SaveChanges needs to be called afterwards.

like image 41
Ryan Lundy Avatar answered Oct 04 '22 09:10

Ryan Lundy