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

pet*_*ldi 107 asp.net-mvc poco entity-framework-4

实体框架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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

Lad*_*nka 143

我们这样试试吧:

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

编辑:

我想我的一条评论给了你错误的希望EF会为你做合并.我对这个问题玩了很多,我的结论是EF不会为你做这件事.我想你也在MSDN上找到了我的问题.实际上,互联网上存在大量此类问题.问题是没有明确说明如何处理这种情况.那么让我们来看看问题:

问题背景

EF需要跟踪实体的更改,以便持久性知道哪些记录必须更新,插入或删除.问题是跟踪更改是ObjectContext的责任.ObjectContext只能跟踪附加实体的更改.根本不跟踪在ObjectContext外部创建的实体.

问题描述

基于以上描述,我们可以清楚地说明EF更适合于实体始终附加到上下文的连接场景 - 通常用于WinForm应用程序.Web应用程序需要断开连接的场景,其中在请求处理之后关闭上下文并且实体内容作为HTTP响应传递给客户端.下一个HTTP请求提供实体的修改内容,该内容必须重新创建,附加到新上下文并保持不变.娱乐通常发生在上下文范围之外(具有持久性无知的分层架构).

那么如何处理这种断开连接的场景呢?使用POCO课程时,我们有3种方法来处理变更跟踪:

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

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

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

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

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

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

此外还有与EF v1相关的博客文章,它批评了EF的这种行为.

解决的原因

EF有许多"有用"的操作和设置,如Refresh,Load,ApplyCurrentValues,ApplyOriginalValues,MergeOption等.但是通过我的调查,所有这些功能仅适用于单个实体,并且仅影响标量属性(=不是导航属性和关系).我宁愿不用嵌套在实体中的复杂类型来测试这个方法.

其他提出的解决方

而不是真正的合并功能EF团队提供了一种称为自我跟踪实体(STE)的东西,它不能解决问题.首先,STE仅在相同实例用于整个处理时才起作用.在Web应用程序中,除非您将实例存储在视图状态或会话中,否则不是这种情况.由于我对使用EF非常不满意,我将检查NHibernate的功能.首先观察说NHibernate可能具有这样的功能.

结论

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

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

编辑2:

正如你所看到的那样,这已经被添加到MS Connect作为2007年的建议.MS已经关闭它作为下一版本要做的事情,但实际上没有做任何事情来改善除STE之外的这个差距.

  • 这是我读过的关于SO的最佳答案之一.您已经清楚地说明了关于该主题的许多MSDN文章,文档和博客文章未能通过的内容.EF4本身并不支持更新"分离"实体的关系.它只为您自己实现它提供了工具.谢谢! (6认同)
  • 我读过的最好的解释之一!非常感谢 (2认同)
  • EF团队计划在EF6之后解决这个问题.您可能想投票支持http://entityframework.codeplex.com/workitem/864 (2认同)

bre*_*ick 19

我找到了Ladislav上面描述的问题的解决方案.我已经为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


c0y*_*teX 9

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

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

在下面的示例中,"dataobj"和"_categories"是我的控制器接收的参数"dataobj"是我的主要对象,"_ category"是包含用户在视图中选择的类别的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();
Run Code Online (Sandbox Code Playgroud)

它甚至适用于多种关系


Eri*_* J. 7

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

来自实体框架团队:

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

如果这会影响您,请投票选择该功能

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