29

我正在使用 Entity Framework 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; }
}

我已经将几个 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 方法中已有更新的人员对象。)

4

3 回答 3

20

你可以尝试不同的方式:

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

这将改变独立关联和外键关联之间的关系Person类型EmployeeType。而不是分配导航属性分配外键属性。这将允许您通过当前代码修改关系。

问题是独立关联(那些不使用外键属性的)在状态管理器/更改跟踪器中作为单独的对象处理。因此,您对人的修改不会影响现有关系的状态,也不会设置新关系。我在 MSDN 上询问了如何使用 DbContext API 来做到这一点,但只有当你DbContext转换为ObjectContext并使用ObjectStateManagerand时才有可能ChangeRelationshipState

于 2011-03-31T20:58:28.417 回答
2

在尝试了十几种不同的 EF 方式之后,我得出结论,没有一种合理的 EF Code First 方式来做我想做的事情。所以我使用了反射。我为我的类创建了这个方法,它继承自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);
    }
}

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

于 2011-04-11T14:27:23.680 回答
1

这适用于收藏,尽管我觉得必须有更好的方法。


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

于 2011-04-12T23:58:02.727 回答