【问题标题】:Updating disconnected entities with many-to-many relationships更新具有多对多关系的断开连接的实体
【发布时间】:2012-11-15 20:36:50
【问题描述】:

假设我在实体框架代码优先设置中有以下模型类:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Team> Teams { get; set; }
}

public class Team
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Person> People { get; set; }
}

由此代码创建的数据库包含一个 TeamPersons 表,表示人员与团队之间的多对多关系。

现在假设我有一个断开连接的 Person 对象(不是代理,并且尚未附加到上下文),它的 Teams 集合包含一个或多个断开连接的 Team 对象,所有这些对象都表示数据库中已经存在的 Teams。例如,如果数据库中已经存在一个 ID 为 1 的人员和一个 ID 为 3 的团队,则会创建如下对象:

var person = new Person
{
    Id = 1,
    Name = "Bob",
    Teams = new HashSet<Team>
    {
        new Team { Id = 3, Name = "C Team"}
    }
};

更新此对象的最佳方法是什么,以便在更新后 TeamPersons 表包含 Bob 的单行,将他链接到 C Team ?我已经尝试了明显的:

using (var context = new TestContext())
{
    context.Entry(person).State = EntityState.Modified;
    context.SaveChanges();
}

但是 Teams 集合被忽略了。我也尝试过其他各种事情,但似乎没有什么能完全符合我的要求。谢谢你的帮助。

编辑:

所以我知道我可以从数据库中获取 Person 和 Team[s],更新它们,然后提交更改:

using (var context = new TestContext())
{
    var dbPerson = context.People.Find(person.Id);
    dbPerson.Name = person.Name;
    dbPerson.Teams.Clear();
    foreach (var id in person.Teams.Select(x => x.Id))
    {
        var team = context.Teams.Find(id);
        dbPerson.Teams.Add(team);
    }

    context.SaveChanges();
}

但是,如果 Person 是一个复杂的实体,这会很痛苦。我知道我可以使用 Automapper 或其他东西来让事情变得更容易一些,但如果没有办法保存原始人对象,而不是必须得到一个新对象并将所有属性复制过来,这仍然是一种耻辱……

【问题讨论】:

    标签: c# entity-framework ef-code-first many-to-many dbcontext


    【解决方案1】:

    一般的方法是从数据库中获取Team,然后将Add 获取到PersonTeams 集合中。设置EntityState.Modified 只会影响标量属性,不会影响导航属性。

    【讨论】:

    • 购买自己,这似乎不起作用:将从数据库中获取的团队添加到不是代理的人员仍然不会更新 TeamPersons 表。起作用的是从数据库中获取团队和人员(因此人员是代理),但是您必须更新这个获取的人员以匹配正在保存的人员...
    • 啊,你是对的(应该读得更好)。编辑后:您可以使用DbContext.Entry(dbPerson).CurrentValues.SetValues(person)
    • 谢谢,这很有用 - 我不知道 CurrentValues.SetValues
    【解决方案2】:

    尝试先选择现有实体,然后将团队附加到人员对象的团队集合中。

    类似这样的:(语法可能不完全正确)

    using (var context = new TestContext())
    {
        var person = context.Persons.Where(f => f.Id == 1).FirstOrDefault();
        var team = context.Teams.Where(f => f.Id == 3).FirstOrDefault();
    
        person.Teams.Add(team);
    
        context.Entry(person).State = EntityState.Modified;
        context.SaveChanges();
    }
    

    【讨论】:

    • 谢谢,但是我必须从我想要保存的原始人中更新这个新人(请参阅我的编辑) - 我希望有另一种方式。
    • 好的,你是说断开连接的person对象有很多变化?
    • 可能,是的:当不知道该对象的哪些属性已更改时,我想知道从断开连接的 Person 对象更新数据库的最佳方法。
    【解决方案3】:

    这就是EF s**ks的地方。对于断开连接的场景非常低效。加载更新/删除的数据以及重新附加更新的每个数据,不能只是将更新的实体附加到上下文,因为具有相同键的实体可能已经存在于上下文中,在这种情况下,EF 只会抛出。需要做的是检查具有相同键的实体是否已经在上下文中并相应地附加或更新。使用多对多关系子更新实体更糟糕。删除删除的孩子是从孩子的实体集中而不是参考属性,这很混乱。

    【讨论】:

      【解决方案4】:

      您可以使用附加方法。试试这个:

      using (var context = new TestContext())
      {
          context.People.Attach(person);
      
          //i'm not sure if this foreach is necessary, you can try without it to see if it works
          foreach (var team in person.Teams)
          {
              context.Teams.Attach(team);
          }
      
          context.Entry(person).State = EntityState.Modified;
      
          context.SaveChanges();
      }
      

      我没有测试这段代码,如果你有任何问题,请告诉我

      【讨论】:

        猜你喜欢
        • 2015-08-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多