简体   繁体   English

实体框架代码优先 - 为什么我不能这样更新复杂属性?

[英]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). 我正在使用Entity Framework 4.1(代码优先)开展一个小型示例项目。 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. 我已经将几个EmployeeTypes(“first”,“second”)保存到数据库中,并且我保存了第一个类型的Person。 Now I want to modify the Person. 现在我要修改Person。 I know I can do this by loading the Person, changing properties, and then saving. 我知道我可以通过加载Person,更改属性,然后保存来完成此操作。 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. 奇怪的是,这会更改FirstName和LastName,但不会更改EmployeeType。 If I get a new Context and request this Person, the EmployeeType remains set to "first" as it was before this code ran. 如果我获得一个新的Context并请求此Person,那么EmployeeType将保持设置为“first”,就像在此代码运行之前一样。

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.) (这尤其令人费解,因为对于EmployeeType,实际需要更改的唯一内容是Person表中的外键,并且该键是标量属性。)

(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.) (顺便说一下,我知道我可以通过首先检索Person,然后逐个更改属性来做到这一点,但是因为我在ASP.NET MVC中使用模型绑定,所以看起来这样会更容易因为我我的POST方法中已经有了更新的人物对象。)

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: 其他方法是在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; }
}

This will change the type of relation between Person and EmployeeType from Independent association to Foreign key association. 这将把PersonEmployeeType之间的关系类型从Independent关联更改为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 . 我在MSDN上询问如何使用DbContext API,但只有将DbContextObjectContext并使用ObjectStateManagerChangeRelationshipState DbContext可能。

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. 在尝试了十几种不同的方式来做EF方式后,我得出结论,没有合理的EF Code First方法来做我想做的事情。 So I used reflection. 所以我用反射。 I created this method for my class that inherits from DbContext : 我为继承自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. 之后需要调用SaveChanges

This works for collections, although i feel like there's got to be a better way. 这适用于收藏品,虽然我觉得必须有更好的方式。


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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM