public virtual ICollection<Tag> Tags {
// getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;
我从ObjectContext实例中请求一个BlogPost和相关的Tag,并将其发送给另一个层(在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 楼
让我们这样尝试:将BlogPost附加到上下文。将对象附加到上下文后,对象的状态将被设置为“不变”。
使用context.ObjectStateManager.ChangeObjectState将您的BlogPost设置为“已修改”。
通过Tag集合进行迭代
使用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文档文章中也有类似的建议:定义和管理关系说:
如果使用断开连接的对象,则必须手动管理
同步。 br />
此外,还有与EF v1相关的博客文章,这些文章准确地批评了EF的这种行为。
解决原因
EF具有许多“有用的”操作和设置,如“刷新”,“加载”,“ ApplyCurrentValues”,“ ApplyOriginalValues”,“ MergeOption”等。但是,根据我的调查,所有这些功能仅适用于单个实体,并且仅影响标量属性(不影响导航属性和关系)。我宁愿不使用嵌套在实体中的复杂类型来测试此方法。
其他提议的解决方案
EF团队提供了不能解决问题的自称为跟踪实体(STE),而不是真正的合并功能。首先,仅当在整个处理过程中使用相同的实例时,STE才起作用。在Web应用程序中不是这样,除非您将实例存储在视图状态或会话中。因此,我对使用EF感到非常不满意,因此我将检查NHibernate的功能。初步观察认为NHibernate可能具有这种功能。
结论
我将以单个链接结束到MSDN论坛上另一个相关问题的结论。检查Zeeshan Hirani的答案。他是Entity Framework 4.0 Recipes的作者。如果他说不支持对象图的自动合并,那么我相信他。
但是仍然有可能我完全错了,并且EF中存在一些自动合并功能。
编辑2:
正如您所看到的,它已作为2007年的建议添加到MS Connect中。MS已将其关闭,作为下一版本中的工作,但实际上没有任何改进之处除STE以外的间隙。
评论
这是我在SO上阅读的最佳答案之一。您已经清楚地说明了有关该主题的MSDN文章,文档和博客文章中有多少没能理解。 EF4本质上不支持从“分离的”实体中更新关系。它仅提供工具供您自己实现。谢谢!
– tyriker
2010年11月17日在16:34
那么,过去几个月后,与EF4相比,NHibernate与该问题相关吗?
– CallMeLaNN
2011年4月7日,下午3:15
这在NHibernate中得到了很好的支持:-)无需手动合并,在我的示例中是3级深对象图,问题有答案,每个答案都有注释,问题也有注释。 NHibernate可以持久化/合并对象图,无论它多么复杂ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html另一个满意的NHibernate用户:codinginstinct.com/2009/11/…
–迈克尔·布恩(Michael Buen)
2011年8月9日,11:30
我读过的最好的解释之一!非常感谢
– marvelTracker
2012年6月14日上午10:14
EF小组计划解决EF6之后的问题。您可能需要投票给entityframework.codeplex.com/workitem/864
– Eric J.
13年2月11日在20:04
#2 楼
我对拉迪斯拉夫在上面描述的问题有解决方案。我已经为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
评论
我敢肯定,您可以轻松解决此问题,我现在做得不好。
– Shimmy Weitzhandler
13年7月4日在3:58
您好Shimmy,对不起,终于有一些时间来看看。今晚我会调查一下。
–重构
13年7月13日在14:10
这个图书馆很棒,为我节省了很多时间!谢谢!
– lordjeb
15年3月24日在17:22
#3 楼
我知道对OP来说已经很晚了,但是由于这是一个非常普遍的问题,因此我将其发布以防其他人使用。我一直在处理这个问题,我想我得到了一个相当简单的解决方案,
我要做的是:
保存主对象(博客例如,通过将其状态设置为Modified。
向数据库查询包含我需要更新的集合的更新对象。
查询并转换.ToList()我希望我的集合包含的实体。
将主对象的集合更新到步骤3中得到的列表。
SaveChanges();
在下面的示例中,“ 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();
它甚至适用于多种关系
#4 楼
实体框架团队意识到这是一个可用性问题,并计划在EF6之后解决。来自实体框架团队:
这是一个我们已经意识到并且一直在考虑的可用性问题,并计划在EF6之后做更多的工作。我已经创建了这个工作项目来跟踪问题:http://entityframework.codeplex.com/workitem/864该工作项目还包含指向该用户语音项目的链接-如果有的话,我建议您投票给它还没有这样做。
如果这对您有影响,请在以下位置对该功能进行投票
http://entityframework.codeplex.com/workitem/864
评论
EF6之后?在乐观的情况下会是哪一年?
–quetzalcoatl
13年2月12日在21:59
@quetzalcoatl:至少在他们的雷达中:-)自EF 1开始,EF就走了很长一段路,但仍有路要走。
– Eric J.
13年2月12日在22:10
#5 楼
所有答案都可以很好地解释问题,但没有一个问题真正为我解决了问题。我发现,如果我不在父实体中使用关系,而只是添加和删除了该关系子实体一切正常。
对VB表示抱歉,但这是我正在从事的项目所编写的内容。
父实体“报告”具有一个与“ 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}))
评论
请发布您的代码。这是常见的情况。我有一个自动解决此问题的方法,它隐藏在下面的答案中,因此很多人会错过它,但是请看一下它,因为它可以节省您的工作,请参阅此处
@brentmckendrick我认为另一种方法更好。而不是通过导线发送整个修改后的对象图,为什么不直接发送增量?在这种情况下,您甚至不需要生成DTO类。如果您对这两种方法都有意见,请在stackoverflow.com/questions/1344066/calculate-object-delta进行讨论。