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


107

实体框架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;

我从ObjectContext实例中请求BlogPost和相关标签,然后将其发送到另一层(在MVC应用程序中查看)。稍后,我返回具有更改的属性和更改的关系的更新的BlogPost。例如,它具有标签“ A”,“ B”和“ C”,而新标签是“ C”和“ D”。在我的特定示例中,没有新的标签,并且标签的属性从不更改,因此唯一需要保存的是更改后的关系。现在,我需要将其保存在另一个ObjectContext中。(更新:现在我尝试在相同的上下文实例中执行此操作,但也失败了。)

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

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

在大多数情况下,“无效”表示我使用给定的“解决方案”,直到它不产生错误并至少保存BlogPost的属性为止。关系发生的情况各不相同:通常,标记会使用新的PK再次添加到Tag表中,并且保存的BlogPost会引用这些PK,而不引用原始的PK。当然,返回的Tag具有PK,在保存/更新方法之前,我检查了PK,它们等于数据库中的PK,因此EF可能认为它们是新对象,而这些PK是临时对象。

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

顺便说一句:我认为一对多关系也会发生同样的问题(谷歌和我的同事也这么说)。我会在家里尝试一下,但是即使该方法无法解决我的应用程序中的六个多对多关系:(。


请发布您的代码。这是常见的情况。
约翰·法瑞尔

1
我有这个问题的自动化解决方案,它隐藏在下面的答案,这么多会想念它,但请看看,因为它会为您节省了工作的地狱在这里看帖子
brentmckendrick

@brentmckendrick我认为另一种方法更好。而不是通过导线发送整个修改后的对象图,为什么不直接发送增量?在这种情况下,您甚至不需要生成DTO类。如果您对这两种方法都有意见,请在stackoverflow.com/questions/1344066/calculate-object-delta进行讨论。
HappyNomad13年

Answers:


145

让我们这样尝试:

  • 将BlogPost附加到上下文。在将对象附加到上下文中后,将对象的状态,所有相关对象和所有关系设置为“不变”。
  • 使用context.ObjectStateManager.ChangeObjectState将BlogPost设置为Modified
  • 遍历标签集合
  • 使用context.ObjectStateManager.ChangeRelationshipState设置当前Tag和BlogPost之间的关系状态。
  • 保存更改

编辑:

我想我的评论之一给了您错误的希望,EF将为您完成合并。我在这个问题上发挥了很多作用,我的结论是EF不会为您做到这一点。我认为您也在MSDN上找到了我的问题。实际上,Internet上存在大量此类问题。问题在于没有明确说明如何处理这种情况。因此,让我们看一下问题:

问题背景

EF需要跟踪实体上的更改,以便持久性知道哪些记录需要更新,插入或删除。问题在于,跟踪更改是ObjectContext的责任。ObjectContext只能跟踪附加实体的更改。在ObjectContext外部创建的实体完全不会被跟踪。

问题描述

根据上面的描述,我们可以清楚地指出EF更适用于始终将实体附加到上下文的连接方案-这对于WinForm应用程序是典型的。Web应用程序需要断开连接的场景,其中在请求处理后关闭上下文,并将实体内容作为HTTP响应传递给客户端。下一个HTTP请求提供了实体的已修改内容,该内容必须重新创建,附加到新上下文并保持不变。娱乐通常发生在上下文范围之外(具有持久性忽略的分层体系结构)。

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

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

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

MSDN文档提出手动同步作为解决方案:附加和分离对象说:

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

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

如果要使用断开连接的对象,则必须手动管理同步。

此外,还有与EF v1相关的博客文章,确切地批评了EF的这种行为。

解决原因

EF具有许多“有用的”操作和设置,如RefreshLoadApplyCurrentValuesApplyOriginalValuesMergeOption等。但是据我调查,所有这些功能仅适用于单个实体,并且仅影响标量属性(不影响导航属性和关系)。我宁愿不使用嵌套在实体中的复杂类型来测试此方法。

其他建议的解决方案

EF团队提供了称为自跟踪实体(STE)的解决方案,而不是真正的合并功能。首先,仅当在整个处理过程中使用相同的实例时,STE才起作用。在Web应用程序中不是这样,除非您将实例存储在视图状态或会话中。因此,我对使用EF感到非常不满意,因此我将检查NHibernate的功能。初步观察认为NHibernate可能具有这种功能

结论

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

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

编辑2:

如您所见,MS Connect已作为2007年的建议添加到MS Connect中。MS已将其关闭,作为下一版本中的工作,但实际上,除了STE之外,没有任何其他措施可以改善此差距。


7
这是我在SO上阅读的最佳答案之一。您已经清楚地说明了有关该主题的MSDN文章,文档和博客文章中有多少没能理解。EF4本质上不支持从“分离的”实体中更新关系。它仅提供工具供您自己实现。谢谢!
tyriker 2010年

1
因此,过去几个月后,与EF4相比,NHibernate与该问题相关吗?
2011年

1
这在NHibernate中得到了很好的支持:-)无需手动合并,在我的示例中是3级深对象图,问题有答案,每个答案都有注释,问题也有注释。NHibernate的能坚持/合并的对象图,无论它是多么复杂ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html另一个满意的NHibernate用户:codinginstinct.com/2009/11/...
迈克尔Buen

2
我读过的最好的解释之一!非常感谢
marvelTracker 2012年

2
EF团队计划解决EF6之后的问题。您可能需要投票给entityframework.codeplex.com/workitem/864
Eric J.

19

我对拉迪斯拉夫在上面描述的问题有解决方案。我为DbContext创建了一个扩展方法,该方法将根据提供的图和持久化图的差异自动执行添加/更新/删除操作。

当前,使用实体框架时,您将需要手动执行联系人的更新,检查每个联系人是否是新联系人并添加,检查是否已更新和编辑,检查是否已删除,然后从数据库中删除。一旦必须对大型系统中的几个不同集合执行此操作,您就会开始意识到必须有一种更好,更通用的方法。

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

您可以在这里直接转到代码https://github.com/refactorthis/GraphDiff


我确定您可以轻松解决问题,但现在情况不佳。
Shimmy Weitzhandler

1
您好Shimmy,对不起,终于有一些时间来看看。今晚我会调查一下。
brentmckendrick

这个图书馆很棒,为我节省了很多时间!谢谢!
lordjeb

9

我知道对OP来说已经很晚了,但是由于这是一个非常普遍的问题,因此我将其发布以防其他人使用。我一直在解决这个问题,我想我有一个相当简单的解决方案,我的工作是:

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

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

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

它甚至适用于多种关系


7

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

来自实体框架团队:

这是我们已经意识到的可用性问题,也是我们一直在考虑的问题,并计划在EF6之后进行更多的工作。我已经创建了这个工作项目来跟踪问题:http : //entityframework.codeplex.com/workitem/864该工作项目还包含指向该用户语音项目的链接-如果有的话,我建议您投票给它还没有这样做。

如果这对您有影响,请在以下位置对该功能进行投票

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


EF6之后?在乐观的情况下会是哪一年?
quetzalcoatl

@quetzalcoatl:至少它在他们的雷达中:-)自EF 1开始,EF已经走了很长一段路,但仍有路要走。
Eric J.

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}))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.