entity framework代码优先 – 为什么我不能以这种方式更新复杂属性?

我正在使用Entity Framework 4.1(代码优先)开展一个小型示例项目。 我的课程看起来像这样:

public class Context : DbContext { public IDbSet People { get; set; } public IDbSet 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 People { get; set; } } 

我已经将几个EmployeeTypes(“first”,“second”)保存到数据库中,并且我保存了第一个类型的Person。 现在我想修改Person。 我知道我可以通过加载Person,更改属性,然后保存来完成此操作。 但我想要做的事情,在我看来应该像它应该工作,是这样的:

 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(); 

奇怪的是,这会更改FirstName和LastName,但不会更改EmployeeType。 如果我获得一个新的Context并请求此Person,那么EmployeeType将保持设置为此代码运行之前的“first”。

我需要做什么才能更新导航属性,而不仅仅是标量属性? (这尤其令人费解,因为对于EmployeeType,实际需要更改的唯一内容是Person表中的外键,并且该键是标量属性。)

(顺便说一句,我知道我可以通过首先检索Person,然后逐个更改属性来实现这一点,但是当我在ASP.NET MVC中使用模型绑定时,似乎这样会更容易因为我我的POST方法中已经有了更新的人物对象。)

你可以尝试不同的方式:

 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(); 

其他方法是在Person实体中公开外键,如:

 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; } } 

这将把PersonEmployeeType之间的关系类型从Independent关联更改为Foreign key association。 而不是分配导航属性分配外键属性。 这将允许您通过当前代码修改关系。

问题是独立关联(那些不使用外键属性)在状态管理器/更改跟踪器中作为单独的对象处理。 所以你对这个人的修改并没有影响现有关系的状态,也没有设置新的关系。 我在MSDN上询问如何使用DbContext API,但只有将DbContextObjectContext并使用ObjectStateManagerChangeRelationshipState DbContext可能。

在尝试了十几种不同的方式来做EF方式后,我得出结论,没有合理的EF Code First方法来做我想做的事情。 所以我用reflection。 我为inheritance自DbContext类创建了此方法:

 public void UpdateFrom(T updatedItem) where T : KeyedItem { var originalItem = Set().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); } } 

我的所有对象都从一个抽象类inheritance并具有一个共同的主键属性,因此这将找到与传入的键具有相同键的现有对象,然后从新对象更新现有对象。 之后需要调用SaveChanges

这适用于collections品,虽然我觉得必须有更好的方法。

 var properties = typeof(TEntity).GetProperties(); foreach (var property in properties) { if (property.GetCustomAttributes(typeof(OneToManyAttribute), false).Length > 0) { dynamic collection = db.Entry(e).Collection(property.Name).CurrentValue; foreach (var item in collection) { if(item.GetType().IsSubclassOf(typeof(Entity))) { if (item.Id == 0) { db.Entry(item).State = EntityState.Added; } else { db.Entry(item).State = EntityState.Modified; } } } } } db.Entry(e).State = EntityState.Modified; db.SaveChanges();