asp.net-mvc 保存 EF4 POCO 对象更改时更新关系
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3635071/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Update relationships when saving changes of EF4 POCO objects
提问by peterfoldi
Entity Framework 4, POCO objects and ASP.Net MVC2. I have a many to many relationship, lets say between BlogPost and Tag entities. This means that in my T4 generated POCO BlogPost class I have:
实体框架 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;
I ask for a BlogPost and the related Tags from an instance of the ObjectContext and send it to another layer (View in the MVC application). Later I get back the updated BlogPost with changed properties and changed relationships. For example it had tags "A" "B" and "C", and the new tags are "C" and "D". In my particular example there are no new Tags and the properties of the Tags never change, so the only thing which should be saved is the changed relationships. Now I need to save this in another ObjectContext. (Update: Now I tried to do in the same context instance and also failed.)
我从 ObjectContext 的一个实例中请求一个 BlogPost 和相关的标签,并将它发送到另一个层(MVC 应用程序中的视图)。后来我取回更新后的博客帖子,其中更改了属性和更改了关系。例如,它有标签“A”、“B”和“C”,而新标签是“C”和“D”。在我的特定示例中,没有新标签并且标签的属性永远不会改变,因此唯一应该保存的是已更改的关系。现在我需要将它保存在另一个 ObjectContext 中。(更新:现在我尝试在同一个上下文实例中执行但也失败了。)
The problem: I can't make it save the relationships properly. I tried everything I found:
问题:我不能让它正确保存关系。我尝试了我发现的一切:
- Controller.UpdateModel and Controller.TryUpdateModel don't work.
- Getting the old BlogPost from the context then modifying the collection doesn't work. (with different methods from the next point)
- Thisprobably would work, but I hope this is just a workaround, not the solution :(.
- Tried Attach/Add/ChangeObjectState functions for BlogPost and/or Tags in every possible combinations. Failed.
- Thislooks like what I need, but it doesn't work (I tried to fix it, but can't for my problem).
- Tried ChangeState/Add/Attach/... the relationship objects of the context. Failed.
- Controller.UpdateModel 和 Controller.TryUpdateModel 不起作用。
- 从上下文中获取旧的 BlogPost 然后修改集合不起作用。(从下一点开始使用不同的方法)
- 这可能会奏效,但我希望这只是一种解决方法,而不是解决方案:(。
- 在每种可能的组合中为博客帖子和/或标签尝试了附加/添加/更改对象状态函数。失败的。
- 这看起来像我需要的,但它不起作用(我试图修复它,但不能解决我的问题)。
- 尝试 ChangeState/Add/Attach/... 上下文的关系对象。失败的。
"Doesn't work" means in most cases that I worked on the given "solution" until it produces no errors and saves at least the properties of BlogPost. What happens with the relationships varies: usually Tags are added again to the Tag table with new PKs and the saved BlogPost references those and not the original ones. Of course the returned Tags have PKs, and before the save/update methods I check the PKs and they are equal to the ones in the database so probably EF thinks that they are new objects and those PKs are the temp ones.
“不起作用”意味着在大多数情况下,我一直在研究给定的“解决方案”,直到它不产生错误并至少保存了 BlogPost 的属性。关系发生的情况各不相同:通常标签会再次添加到带有新 PK 的标签表中,并且保存的 BlogPost 会引用这些而不是原始 PK。当然,返回的标签有 PK,在保存/更新方法之前,我检查了 PK,它们与数据库中的相同,所以 EF 可能认为它们是新对象,而这些 PK 是临时对象。
A problem I know about and might make it impossible to find an automated simple solution: When a POCO object's collection is changed, that should happen by the above mentioned virtual collection property, because then the FixupCollection trick will update the reverse references on the other end of the many-to-many relationship. However when a View "returns" an updated BlogPost object, that didn't happen. This means that maybe there is no simple solution to my problem, but that would make me very sad and I would hate the EF4-POCO-MVC triumph :(. Also that would mean that EF can't do this in the MVC environment whichever EF4 object types are used :(. I think the snapshot based change tracking should find out that the changed BlogPost has relationships to Tags with existing PKs.
我知道一个问题,可能无法找到自动化的简单解决方案:当 POCO 对象的集合发生更改时,这应该由上面提到的虚拟集合属性发生,因为然后 FixupCollection 技巧将更新另一端的反向引用的多对多关系。然而,当视图“返回”更新的 BlogPost 对象时,这并没有发生。这意味着我的问题可能没有简单的解决方案,但这会让我非常难过,我会讨厌 EF4-POCO-MVC 的胜利:(。这也意味着 EF 无法在 MVC 环境中执行此操作使用 EF4 对象类型 :(。我认为基于快照的更改跟踪应该发现更改后的 BlogPost 与具有现有 PK 的标签有关系。
Btw: I think the same problem happens with one-to-many relations (google and my colleague say so). I will give it a try at home, but even if that works that doesn't help me in my six many-to-many relationships in my app :(.
顺便说一句:我认为一对多关系也会出现同样的问题(谷歌和我的同事是这么说的)。我会在家里试一试,但即使这行得通,在我的应用程序中的六个多对多关系中也无济于事:(。
回答by Ladislav Mrnka
Let's try it this way:
让我们这样试试:
- Attach BlogPost to context. After attaching object to context the state of the object, all related objects and all relations is set to Unchanged.
- Use context.ObjectStateManager.ChangeObjectState to set your BlogPost to Modified
- Iterate through Tag collection
- Use context.ObjectStateManager.ChangeRelationshipState to set state for relation between current Tag and BlogPost.
- SaveChanges
- 将 BlogPost 附加到上下文。将对象附加到上下文对象的状态后,所有相关对象和所有关系都设置为未更改。
- 使用 context.ObjectStateManager.ChangeObjectState 将您的 BlogPost 设置为 Modified
- 遍历标签集合
- 使用 context.ObjectStateManager.ChangeRelationshipState 为当前 Tag 和 BlogPost 之间的关系设置状态。
- 保存更改
Edit:
编辑:
I guess one of my comments gave you false hope that EF will do the merge for you. I played a lot with this problem and my conclusion says EF will not do this for you. I think you have also found my question on MSDN. In reality there is plenty of such questions on the Internet. The problem is that it is not clearly stated how to deal with this scenario. So lets have a look on the problem:
我想我的评论之一给了您错误的希望,即 EF 会为您进行合并。我在这个问题上玩了很多,我的结论是 EF 不会为你做这个。我想你也在MSDN上找到了我的问题。事实上,互联网上有很多这样的问题。问题是没有明确说明如何处理这种情况。那么让我们来看看这个问题:
Problem background
问题背景
EF needs to track changes on entities so that persistance knows which records have to be updated, inserted or deleted. The problem is that it is ObjectContext responsibility to track changes. ObjectContext is able to track changes only for attached entities. Entities which are created outside the ObjectContext are not tracked at all.
EF 需要跟踪实体上的更改,以便持久性知道必须更新、插入或删除哪些记录。问题是 ObjectContext 有责任跟踪更改。ObjectContext 只能跟踪附加实体的更改。在 ObjectContext 之外创建的实体根本不会被跟踪。
Problem description
问题描述
Based on above description we can clearly state that EF is more suitable for connected scenarios where entity is always attached to context - typical for WinForm application. Web applications requires disconnected scenario where context is closed after request processing and entity content is passed as HTTP response to the client. Next HTTP request provides modified content of the entity which has to be recreated, attached to new context and persisted. Recreation usually happends outside of the context scope (layered architecture with persistance ignorace).
基于上面的描述,我们可以清楚地表明 EF 更适合于实体始终附加到上下文的连接场景 - 典型的 WinForm 应用程序。Web 应用程序需要断开连接的场景,其中上下文在请求处理后关闭,实体内容作为 HTTP 响应传递给客户端。下一个 HTTP 请求提供必须重新创建、附加到新上下文并持久化的实体的修改内容。娱乐通常发生在上下文范围之外(具有持久性无知的分层架构)。
Solution
解决方案
So how to deal with such disconnected scenario? When using POCO classes we have 3 ways to deal with change tracking:
那么如何处理这种断开连接的场景呢?使用 POCO 类时,我们有 3 种方法来处理更改跟踪:
- Snapshot - requires same context = useless for disconnected scenario
- Dynamic tracking proxies - requires same context = useless for disconnected scenario
- Manual synchronization.
- 快照 - 需要相同的上下文 = 对于断开连接的场景无用
- 动态跟踪代理 - 需要相同的上下文 = 对于断开连接的场景无用
- 手动同步。
Manual synchronization on single entity is easy task. You just need to attach entity and call AddObject for inserting, DeleteObject for deleting or set state in ObjectStateManager to Modified for updating. The real pain comes when you have to deal with object graph instead of single entity. This pain is even worse when you have to deal with independent associations (those that don't use Foreign Key property) and many to many relations. In that case you have to manually synchronize each entity in object graph but also each relation in object graph.
在单个实体上手动同步是一项简单的任务。您只需要附加实体并调用 AddObject 进行插入,调用 DeleteObject 进行删除或将 ObjectStateManager 中的状态设置为 Modified 进行更新。当您必须处理对象图而不是单个实体时,真正的痛苦就来了。当您必须处理独立关联(那些不使用外键属性的关联)和多对多关系时,这种痛苦会更加严重。在这种情况下,您必须手动同步对象图中的每个实体以及对象图中的每个关系。
Manual synchronization is proposed as solution by MSDN documentation: Attaching and Detaching objectssays:
MSDN 文档建议手动同步作为解决方案:附加和分离对象说:
Objects are attached to the object context in an Unchanged state. If you need to change the state of an object or the relationship because you know that your object was modified in detached state, use one of the following methods.
对象以未更改状态附加到对象上下文。如果因为知道对象在分离状态下被修改而需要更改对象的状态或关系,请使用以下方法之一。
Mentioned methods are ChangeObjectState and ChangeRelationshipState of ObjectStateManager = manual change tracking. Similar proposal is in other MSDN documentation article: Defining and Managing Relationshipssays:
提到的方法是 ObjectStateManager = 手动更改跟踪的 ChangeObjectState 和 ChangeRelationshipState。类似的建议在其他 MSDN 文档文章中:定义和管理关系说:
If you are working with disconnected objects you must manually manage the synchronization.
如果您正在处理断开连接的对象,您必须手动管理同步。
Moreover there is blog postrelated to EF v1 which criticise exactly this behavior of EF.
此外,还有与 EF v1 相关的博客文章,其中批评了 EF 的这种行为。
Reason for solution
解决原因
EF has many "helpful" operations and settings like Refresh, Load, ApplyCurrentValues, ApplyOriginalValues, MergeOptionetc. But by my investigation all these features work only for single entity and affects only scalar preperties (= not navigation properties and relations). I rather not test this methods with complex types nested in entity.
EF 有许多“有用”的操作和设置,如Refresh、Load、ApplyCurrentValues、ApplyOriginalValues、MergeOption等。但根据我的调查,所有这些功能仅适用于单个实体,并且仅影响标量属性(= 不影响导航属性和关系)。我宁愿不使用嵌套在实体中的复杂类型来测试此方法。
Other proposed solution
其他建议的解决方案
Instead of real Merge functionality EF team provides something called Self Tracking Entities(STE) which don't solve the problem. First of all STE works only if same instance is used for whole processing. In web application it is not the case unless you store instance in view state or session. Due to that I'm very unhappy from using EF and I'm going to check features of NHibernate. First observation says that NHibernate perhaps has such functionality.
EF 团队没有提供真正的合并功能,而是提供了一种称为自跟踪实体(STE) 的东西,它不能解决问题。首先,仅当整个处理使用相同的实例时,STE 才有效。在 Web 应用程序中,除非您将实例存储在视图状态或会话中,否则情况并非如此。因此,我对使用 EF 非常不满意,我将检查 NHibernate 的功能。第一个观察表明 NHibernate 可能具有这样的功能。
Conclusion
结论
I will end up this assumptions with single link to another related questionon MSDN forum. Check Zeeshan Hirani's answer. He is author of Entity Framework 4.0 Recipes. If he says that automatic merge of object graphs is not supported, I believe him.
我将通过指向MSDN 论坛上另一个相关问题的单个链接来结束这个假设。检查 Zeeshan Hirani 的回答。他是Entity Framework 4.0 Recipes 的作者。如果他说不支持对象图的自动合并,我相信他。
But still there is possibility that I'm completely wrong and some automatic merge functionality exists in EF.
但是仍然有可能我完全错了,EF 中存在一些自动合并功能。
Edit 2:
编辑2:
As you can see this was already added to MS Connectas suggestion in 2007. MS has closed it as something to be done in next version but actually nothing had been done to improve this gap except STE.
正如您所看到的,这已在 2007 年作为建议添加到MS Connect中。MS 已将其关闭,作为在下一个版本中要做的事情,但实际上除了 STE 之外没有采取任何措施来改善这一差距。
回答by brentmckendrick
I have a solution to the problem that was described above by Ladislav. I have created an extension method for the DbContext which will automatically perform the add/update/delete's based on a diff of the provided graph and persisted graph.
我对 Ladislav 上面描述的问题有一个解决方案。我为 DbContext 创建了一个扩展方法,它将根据提供的图和持久图的差异自动执行添加/更新/删除。
At present using the Entity Framework you will need to perform the updates of the contacts manually, check if each contact is new and add, check if updated and edit, check if removed then delete it from the database. Once you have to do this for a few different aggregates in a large system you start to realize there must be a better, more generic way.
目前使用实体框架,您需要手动执行联系人的更新,检查每个联系人是否是新的并添加,检查是否更新并编辑,检查是否已删除然后从数据库中删除它。一旦您必须为大型系统中的几个不同聚合执行此操作,您就会开始意识到必须有一种更好、更通用的方法。
Please take a look and see if it can help http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/
You can go straight to the code here https://github.com/refactorthis/GraphDiff
你可以直接看这里的代码https://github.com/refactorthis/GraphDiff
回答by c0y0teX
I know it's late for the OP but since this is a very common issue I posted this in case it serves someone else. I've been toying around with this issue and I think I got a fairly simple solution, what I do is:
我知道 OP 已经晚了,但由于这是一个非常常见的问题,因此我发布了此信息,以防它为其他人服务。我一直在玩这个问题,我想我得到了一个相当简单的解决方案,我要做的是:
- Save main object (Blogs for example) by setting its state to Modified.
- Query the database for the updated object including the collections I need to update.
- Query and convert .ToList() the entities I want my collection to include.
- Update the main object's collection(s) to the List I got from step 3.
- SaveChanges();
- 通过将其状态设置为已修改来保存主对象(例如博客)。
- 查询数据库以获取更新的对象,包括我需要更新的集合。
- 查询和转换 .ToList() 我希望我的集合包含的实体。
- 将主要对象的集合更新到我从步骤 3 中获得的列表。
- 保存更改();
In the following example "dataobj" and "_categories" are the parameters received by my controller "dataobj" is my main object, and "_categories" is an IEnumerable containing the IDs of the categories the user selected in the view.
在下面的示例中,“dataobj”和“_categories”是我的控制器接收的参数“dataobj”是我的主要对象,“_categories”是一个包含用户在视图中选择的类别的 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();
It even works for multiple relations
它甚至适用于多种关系
回答by Eric J.
The Entity Framework team is aware that this is a usability issue and plans to address it post-EF6.
实体框架团队意识到这是一个可用性问题,并计划在 EF6 之后解决它。
From the Entity Framework team:
来自实体框架团队:
This is a usability issue that we are aware of and is something we have been thinking about and plan to do more work on post-EF6. I have created this work item to track the issue: http://entityframework.codeplex.com/workitem/864The work item also contains a link to the user voice item for this--I encourage you to vote for it if you have not done so already.
这是我们意识到的可用性问题,也是我们一直在考虑并计划在 EF6 之后做更多工作的问题。我创建了这个工作项目来跟踪这个问题:http: //entityframework.codeplex.com/workitem/864该工作项目还包含一个指向用户语音项目的链接——如果你有的话,我鼓励你投票支持它还没有这样做。
If this impacts you, vote for the feature at
如果这对您有影响,请为该功能投票
回答by Alan Bridges
All of the answers were great to explain the problem, but none of them really solved the problem for me.
所有的答案都很好地解释了问题,但没有一个真正为我解决了问题。
I found that if I didn't use the relationship in the parent entity but just added and removed the child entities everything worked just fine.
我发现,如果我不在父实体中使用关系,而只是添加和删除子实体,一切正常。
Sorry for the VB but that is what the project I am working in is written in.
对 VB 感到抱歉,但这就是我正在从事的项目所写的内容。
The parent entity "Report" has a one to many relationship to "ReportRole" and has the property "ReportRoles". The new roles are passed in by a comma separated string from an Ajax call.
父实体“Report”与“ReportRole”具有一对多关系,并具有“ReportRoles”属性。新角色通过来自 Ajax 调用的逗号分隔字符串传入。
The first line will remove all the child entities, and if I used "report.ReportRoles.Remove(f)" instead of the "db.ReportRoles.Remove(f)" I would get the error.
第一行将删除所有子实体,如果我使用“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}))