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

时间:2022-05-20 00:13:19

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

我正在开发一个使用实体框架4.1(代码优先)的小型示例项目。我的课程是这样的:

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:

我将几个employeetype(“first”、“second”)保存到数据库中,并保存了一个拥有第一个类型的人。现在我想修改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();

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.

奇怪的是,这改变了FirstName和LastName,而不是EmployeeType。如果我得到一个新的上下文并请求这个人,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方法中已经有了更新后的person对象)

3 个解决方案

#1


19  

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. 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,但只有将DbContext转换为ObjectContext并使用ObjectStateManager和ChangeRelationshipState才能实现。

#2


2  

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:

在尝试了十几种不同的方法之后,我得出结论,没有一种合理的EF代码第一种方法可以完成我正在尝试的工作。所以我使用反射。我为继承自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需要在以后调用。

#3


1  

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

#1


19  

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. 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,但只有将DbContext转换为ObjectContext并使用ObjectStateManager和ChangeRelationshipState才能实现。

#2


2  

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:

在尝试了十几种不同的方法之后,我得出结论,没有一种合理的EF代码第一种方法可以完成我正在尝试的工作。所以我使用反射。我为继承自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需要在以后调用。

#3


1  

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