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

entity framework4,POCO对象和ASP.Net MVC2。 我有一个多对多的关系,可以说BlogPost和Tag实体之间。 这意味着在我生成的POCO BlogPost类中,我有:

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

我从ObjectContext的一个实例请求一个BlogPost和相关的标签,并将其发送到另一个层(在MVC应用程序中查看)。 后来我找回更新的BlogPost,更改了属性并更改了关系。 例如它有标签“A”“B”和“C”,新的标签是“C”和“D”。 在我的例子中,没有新的标签,标签的属性从不改变,所以唯一应该保存的是改变的关系。 现在我需要保存在另一个ObjectContext中。 (更新:现在我试图做在相同的上下文实例,也失败了。)

问题是:我无法正确保存关系。 我尝试了我发现的一切:

  • Controller.UpdateModel和Controller.TryUpdateModel不起作用。
  • 从上下文获取旧的BlogPost,然后修改集合不起作用。 (用下一个不同的方法)
  • 这可能会工作,但我希望这只是一个解决方法,而不是解决scheme:(。
  • 尝试每个可能的组合中的BlogPost和/或标签的附加/添加/ ChangeObjectStatefunction。 失败。
  • 这看起来像我所需要的,但它不工作(我试图解决它,但不能解决我的问题)。
  • 尝试了ChangeState / Add / Attach / …上下文的关系对象。 失败。

“不工作”在大多数情况下意味着我在给定的“解决scheme”上工作,直到它不产生错误并至less保存BlogPost的属性。 这些关系会发生什么变化:通常标签再次被添加到标签表中,新的PK和保存的BlogPost引用这些,而不是原来的。 当然,返回的标签有PK,在保存/更新方法之前,我检查PK,它们与数据库中的相同,所以EF可能认为它们是新对象,而那些PK是临时的。

我知道的一个问题,可能使它无法find一个自动化的简单解决scheme:当一个POCO对象的集合被改变,这应该发生在上面提到的虚拟集合属性,因为然后FixupCollection技巧将更新另一端的反向引用多对多的关系。 然而,当一个View“返回”一个更新的BlogPost对象时,并没有发生。 这意味着也许对我的问题没有简单的解决scheme,但这会让我非常难过,而且我会讨厌EF4-POCO-MVC的胜利:(也就是说EF不能在MVC环境中做到这一点,使用EF4对象types:(我认为基于快照的更改跟踪应该发现更改后的BlogPost与具有现有PK的标记具有关系。

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

我们来试试这个方法:

  • 将BlogPost附加到上下文。 将对象附加到上下文后,对象的状态,所有相关对象和所有关系都设置为“未更改”。
  • 使用context.ObjectStateManager.ChangeObjectState将您的BlogPost设置为Modified
  • 通过标签集合迭代
  • 使用context.ObjectStateManager.ChangeRelationshipState设置当前Tag和BlogPost之间的关系状态。
  • 保存更改

编辑:

我想我的一个评论给了你一个虚假的希望,即EF会为你合并。 我在这个问题上玩了很多,我的结论是EF不会为你做这个事情。 我想你也在MSDN上发现了我的问题。 实际上互联网上有很多这样的问题。 问题是没有清楚说明如何处理这种情况。 所以让我们看看这个问题:

问题的背景

EF需要跟踪对实体的更改,以便持久性知道哪些logging必须更新,插入或删除。 问题是跟踪更改是ObjectContext的责任。 ObjectContext能够仅追踪附加实体的更改。 在ObjectContext之外创build的实体根本不被跟踪。

问题描述

基于以上描述,我们可以清楚地表明,EF更适合连接场景,其中实体总是附加到上下文 – 典型的WinForm应用程序。 Web应用程序需要断开连接的场景,在请求处理和实体内容作为HTTP响应传递给客户端后,上下文closures。 下一个HTTP请求提供了必须重新创build的实体的修改内容,附加到新的上下文并保持。 休闲活动通常在上下文范围之外发生(分层build筑,坚持无知)。

那么如何处理这种断开的情况呢? 当使用POCO类时,我们有3种方法来处理变更跟踪:

  • 快照 – 需要相同的上下文=断开情况下无用
  • dynamic跟踪代理 – 需要相同的上下文=断开连接的场景无用
  • 手动同步。

手动同步单个实体是一件容易的事情。 您只需附加实体并调用AddObject进行插入,DeleteObject用于删除或将ObjectStateManager中的状态设置为Modified进行更新。 当你必须处理对象图而不是单个实体时,真正的痛苦来临了。 当你必须处理独立的关联(那些不使用外键属性的关联)和多对多的关系时,这种痛苦更加严重。 在这种情况下,您必须手动同步对象图中的每个实体,而且还要对对象图中的每个关系进行同步。

MSDN文档提出了手动同步的解决scheme: 附加和分离对象表示:

对象以Unchanged状态附加到对象上下文。 如果您需要更改对象或关系的状态,因为您知道对象已被修改为分离状态,请使用以下方法之一。

提到的方法是ChangeObjectState和ObjectStateManager的ChangeRelationshipState =手动更改跟踪。 类似的build议是在其他MSDN文档文章: 定义和pipe理关系说:

如果您正在使用断开连接的对象,则必须手动pipe理同步。

此外还有与EF v1有关的博客文章 ,正是批评EF的这种行为。

解决的理由

EF有许多“有用”的操作和设置,如刷新 , 加载 , ApplyCurrentValues , ApplyOriginalValues , MergeOption等。但通过我的调查,所有这些function只适用于单个实体,只影响标量属性(=不导航属性和关系)。 我宁愿不用嵌套在实体中的复杂types来testing这个方法。

其他解决scheme

EF团队不提供真正的合并function,而是提供一些称为自我跟踪实体 (STE)的东西,这些东西不能解决问题。 首先,只有在同一实例用于整个处理的情况下,STE才起作用。 在web应用程序中,除非将实例存储在视图状态或会话中,否则不是这种情况。 由于我非常不满意使用EF和我要检查NHibernate的function。 首先观察说NHibernate也许有这样的function 。

结论

我将结束这个假设与MSDN论坛上的另一个相关问题的单一链接。 检查Zeeshan Hirani的答案。 他是Entity Framework 4.0 Recipes的作者 。 如果他说自动合并对象图不被支持,我相信他。

但是,仍然有可能是我完全错误,EF中存在一些自动合并function。

编辑2:

正如你所看到的,这已经在2007年作为build议添加到了MS Connect中.MS已经把它作为下一个版本来完成,但是除了STE之外,没有任何东西能够改善这个差距。

我有一个解决Ladislav上面描述的问题的办法。 我为DbContext创build了一个扩展方法,它会根据提供的graphics和持久graphics的差异自动执行添加/更新/删除操作。

目前使用的entity framework,您将需要手动执行联系人的更新,检查每个联系人是否新增和添加,检查是否更新和编辑,检查是否删除,然后从数据库中删除它。 一旦你必须在一个大系统中为几个不同的聚集做这个,你就会意识到必须有一个更好,更通用的方法。

请看看它是否可以帮助http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-图的的独立式实体/

你可以直接去https://github.com/refactorthis/GraphDiff的代码;

我知道这是OP的迟到,但由于这是一个非常普遍的问题,我发布了这个情况下,它服务于别人。 我一直在讨论这个问题,我想我有一个相当简单的解决scheme,我所做的是:

  1. 保存主对象(博客例如)通过设置其状态为修改。
  2. 在数据库中查询更新的对象,包括我需要更新的集合。
  3. 查询并转换.ToList()我希望我的集合包含的实体。
  4. 将主对象的集合更新到我从第3步获取的列表。
  5. 保存更改();

在以下示例中,“dataobj”和“_categories”是由我的控制器接收的参数。“dataobj”是我的主要对象,“_categories”是包含用户在视图中select的类别ID的IEnumerable。

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

它甚至适用于多种关系

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

来自entity framework团队:

这是我们意识到的可用性问题,也是我们一直在考虑的问题,并计划在EF6之后做更多的工作。 我已经创build了这个工作项目来跟踪这个问题: http : //entityframework.codeplex.com/workitem/864工作项目还包含一个链接到用户的声音项目 – 我鼓励你投票,如果你有还没有这样做。

如果这对您有影响,请在此投票

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