保存EF4 POCO对象的更改时更新关系

时间:2010-09-03 10:59:19

标签: asp.net-mvc entity-framework-4 poco

实体框架4,POCO对象和ASP.Net MVC2。我有很多关系,比如BlogPost和Tag实体之间的关系。这意味着在我的T4生成的POCO BlogPost类中我有:

public virtual ICollection<Tag> Tags {
    // getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;

我要求一个BlogPost和来自ObjectContext实例的相关标签,并将其发送到另一层(MVC应用程序中的View)。稍后我回到更新的BlogPost,更改了属性并更改了关系。例如,它具有标签“A”“B”和“C”,并且新标签是“C”和“D”。在我的特定示例中,没有新的标签,并且标签的属性永远不会改变,因此唯一应该保存的是改变的关系。现在我需要将它保存在另一个ObjectContext中。 (更新:现在我尝试在同一个上下文实例中做,但也失败了。)

问题:我无法正确保存关系。我尝试了所有我发现的东西:

  • Controller.UpdateModel和Controller.TryUpdateModel不起作用。
  • 从上下文中获取旧的BlogPost然后修改集合不起作用。 (使用下一点的不同方法)
  • This可能会有效,但我希望这只是一种解决方法,而不是解决方案:(。
  • 尝试在每种可能的组合中为BlogPost和/或标签添加/添加/更改ObjectObjectState函数。失败。
  • This看起来像我需要的,但它不起作用(我试图解决它,但不能解决我的问题)。
  • 尝试ChangeState / Add / Attach / ...上下文的关系对象。失败。

“不起作用”意味着在大多数情况下我使用给定的“解决方案”,直到它不产生任何错误并至少保存BlogPost的属性。关系会发生什么变化:通常使用新的PK将标签添加到Tag表中,并且保存的BlogPost引用那些而不是原始的。当然返回的标签有PK,在保存/更新方法之前我检查PK并且它们等于数据库中的那些,所以EF可能认为它们是新对象而那些PK是临时的。

我知道的一个问题并且可能使得无法找到自动化的简单解决方案:当POCO对象的集合发生变化时,应该通过上面提到的虚拟集合属性发生,因为那时FixupCollection技巧将更新反向引用多对多关系的另一端。但是,当View“返回”更新的BlogPost对象时,这种情况并未发生。这意味着对我的问题可能没有简单的解决方案,但这会让我非常伤心,我会讨厌EF4-POCO-MVC的胜利:(。这也意味着EF无法在MVC环境中做到这一点,无论哪个使用EF4对象类型:(。我认为基于快照的更改跟踪应该发现更改的BlogPost与具有现有PK的标签有关系。

顺便说一句:我认为同一个问题发生在一对多的关系上(谷歌和我的同事这么说)。我会在家里尝试一下,但即使这样做对我的应用程序中的六个多对多关系没有帮助:(。

5 个答案:

答案 0 :(得分:143)

答案 1 :(得分:19)

我找到了Ladislav上面描述的问题的解决方案。我已经为DbContext创建了一个扩展方法,它将根据提供的图形和持久图形的差异自动执行添加/更新/删除。

  

目前使用实体框架,您需要手动执行联系人更新,检查每个联系人是否是新的并添加,检查是否更新和编辑,检查是否已删除然后从数据库中删除它。一旦你必须在大型系统中为几个不同的聚合做到这一点,你就会开始意识到必须有一种更好,更通用的方式。

请查看它是否有用http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/

您可以直接查看此处的代码https://github.com/refactorthis/GraphDiff

答案 2 :(得分:9)

我知道OP已经晚了但是因为这是一个非常常见的问题,所以我发布了这个以防万一。 我一直在努力解决这个问题,我想我有一个相当简单的解决方案, 我所做的是:

  1. 通过将其状态设置为“已修改”来保存主对象(例如“博客”)。
  2. 在数据库中查询更新的对象,包括我需要更新的集合。
  3. 查询并转换.ToList()我希望我的集合包含的实体。
  4. 将主对象的集合更新为我从步骤3获得的列表。
  5. 的SaveChanges();
  6. 在下面的示例中,“dataobj”和“_categories”是我的控制器接收的参数“dataobj”是我的主要对象,“_ category”是包含用户在视图中选择的类别的ID的IEnumerable。 / p>

        db.Entry(dataobj).State = EntityState.Modified;
        db.SaveChanges();
        dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id);
        var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null;
        dataobj.Categories = it;
        db.SaveChanges();
    

    它甚至适用于多种关系

答案 3 :(得分:7)

实体框架团队意识到这是一个可用性问题,并计划在EF6之后解决这个问题。

来自实体框架团队:

  

这是我们意识到的可用性问题,也是我们一直在思考的问题,并计划在后EF6上做更多的工作。我已创建此工作项来跟踪问题:http://entityframework.codeplex.com/workitem/864工作项还包含指向用户语音项的链接 - 如果您尚未这样做,我建议您投票。

如果这会影响您,请在

投票选择该功能

http://entityframework.codeplex.com/workitem/864

答案 4 :(得分:1)

所有答案都很好地解释了这个问题,但是没有一个能真正为我解决问题。

我发现如果我没有在父实体中使用该关系但只是添加并删除了子实体,那么一切工作都很好。

对不起VB,但这就是我正在处理的项目。

父实体“Report”与“ReportRole”具有一对多的关系,并具有属性“ReportRoles”。新角色由来自Ajax调用的逗号分隔字符串传入。

第一行将删除所有子实体,如果我使用“report.ReportRoles.Remove(f)”而不是“db.ReportRoles.Remove(f)”,我会收到错误。

report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f))
Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(","))
newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))