代码之家  ›  专栏  ›  技术社区  ›  peterfoldi

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

  •  107
  • peterfoldi  · 技术社区  · 14 年前

    实体框架4、POCO对象和ASP.NetMVC2型。我有一个多对多的关系,比如说在BlogPost和标签实体之间。这意味着在我的T4生成的POCO BlogPost类中,我有:

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

    问题是:我不能让它妥善保存关系。我尝试了我发现的一切:

    • 控制器.UpdateModel以及控制器.TryUpdateModel别工作了。
    • 从上下文中获取旧的BlogPost,然后修改集合是行不通的。(从下一点开始采用不同的方法)
    • This 可能会奏效,但我希望这只是一个解决方法,而不是解决方案:(。
    • This 看起来像我需要的,但它不工作(我试图解决它,但不能为我的问题)。
    • 已尝试更改状态/添加/附加/。。。上下文的关系对象。失败。

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

    5 回复  |  直到 7 年前
        1
  •  145
  •   Ladislav Mrnka    14 年前

    让我们这样试试:

    • 将BlogPost附加到上下文。将对象附加到上下文后,对象的状态、所有相关对象和所有关系都设置为未更改。
    • 使用context.ObjectStateManager.ChangeObjectState将您的BlogPost设置为Modified
    • 使用context.ObjectStateManager.ChangeRelationshipState为当前标记和BlogPost之间的关系设置状态。
    • 保存更改

    我猜我的一个评论给了你错误的希望,EF会为你做合并。我对这个问题做了很多研究,我的结论是EF不会为你做这些。我想你也发现了我的问题 MSDN . 实际上,互联网上有很多这样的问题。问题是没有明确说明如何处理这种情况。让我们来看看这个问题:

    问题背景

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

    解决方案

    • 快照-需要相同的上下文=对于断开连接的场景无效
    • 手动同步。

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

    MSDN文档建议将手动同步作为解决方案: Attaching and Detaching objects 说:

    对象附着到对象 处于不变状态的上下文。如果你 需要更改对象的状态 因为你知道 分离状态,使用 以下方法。

    提到的方法是ObjectStateManager=manual change tracking的ChangeObjectState和ChangeRelationshipState。其他MSDN文档文章中也有类似的建议: Defining and Managing Relationships

    如果您使用的是断开连接的 同步。

    此外,还有 blog post

    EF有许多“有用的”操作和设置,如 Refresh , Load , ApplyCurrentValues , ApplyOriginalValues , MergeOption

    EF团队提供的不是真正的合并功能,而是 Self Tracking Entities (STE)这并不能解决问题。首先,只有当同一个实例用于整个处理时,STE才起作用。在web应用程序中,情况并非如此,除非您将实例存储在视图状态或会话中。因此,我对使用EF非常不满意,我将检查NHibernate的特性。第一次观察表明,NHibernate可能有这样的 functionality .

    结论

    related question 在MSDN论坛上。查看Zeeshan Hirani的答案。他是这本书的作者 Entity Framework 4.0 Recipes . 如果他说不支持对象图的自动合并,我相信他。

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

    编辑2:

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

        2
  •  19
  •   Eric J.    11 年前

    我有一个解决问题的办法,上面所描述的拉迪斯拉夫。我为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

        3
  •  9
  •   c0y0teX    12 年前

    我知道这是晚了行动,但由于这是一个非常常见的问题,我张贴了这一点,以防它服务于其他人。

    1. 在数据库中查询更新的对象,包括需要更新的集合。
    2. Query and convert.ToList()我希望集合包含的实体。
    3. 将主对象的集合更新到步骤3中得到的列表。
    4. 保存更改();

        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
  •  7
  •   Eric J.    11 年前

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

    来自实体框架团队:

    http://entityframework.codeplex.com/workitem/864 工作项还包含一个指向用户语音项的链接——如果您还没有投票,我鼓励您投票支持它。

    如果这对您有影响,请在

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

        5
  •  1
  •   Alan Bridges    6 年前

    我发现,如果我不在父实体中使用关系,而只是添加和删除子实体,那么一切都可以正常工作。

    父实体“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}))