【问题标题】:ASP.NET MVC / EF4 / POCO / Repository - How to Update Relationships?ASP.NET MVC / EF4 / POCO / Repository - 如何更新关系?
【发布时间】:2011-06-06 22:22:41
【问题描述】:

我在 ReviewRecommendations 之间存在 1..* 关系。

我的模型的相关部分(也是EF4映射的POCO):

public class Review
{
   public ICollection<Recommendations> Recommendations { get; set; }
}

编辑视图上,我将建议表示为一组复选框。

当我尝试添加新推荐作为编辑评论的一部分(例如选中另一个框)时,什么都没有发生 - 我知道为什么......

我使用“存根技术”来更新我的实体 - 例如,我创建一个具有相同键的实体,将其附加到图形,然后执行 ApplyCurrentValues。但这仅适用于标量属性,不适用于导航属性。

我发现 this StackOverflow question 看起来不错,但我正在努力研究如何让它与 POCO/Repository(和 ASP.NET MVC - 分离上下文)一起使用。

当我使用 POCO 时,review.RecommendationsICollection&lt;Recommendation&gt;,所以我不能这样做 review.Recommendations.Attach。我也没有使用自我跟踪实体,所以我需要手动处理图形/更改跟踪 - 直到现在这还不是问题。

因此您可以可视化场景:

评论:

  • 建议 (ICollection&lt;Recommendation&gt;):
    • RecommendationOne (Recommendation)
    • RecommendationTwo (Recommendation)

如果我在编辑视图中,则其中两个复选框已被选中。第三个(代表 RecommendationThree)未选中

但是如果我选中那个框,上面的模型就变成了:

评论:

  • 建议(ICollection&lt;Recommendation&gt;):
    • RecommendationOne (Recommendation)
    • RecommendationTwo (Recommendation)
    • 推荐三 (Recommendation)

所以我需要将 RecommendationThree 作为一个新实体附加到图表中。

我需要隐藏字段来比较现有实体的发布数据吗?还是应该将实体存储在 TempData 中并将其与发布的实体进行比较?

编辑

为避免混淆,以下是完整的应用堆栈调用:

审核控制器

[HttpPost]
public ActionResult Edit(Review review)
{
   _service.Update(review); // UserContentService
   _unitOfWork.Commit();
}

用户内容服务

public void Update<TPost>(TPost post) where TPost : Post, new()
{
   _repository.Update(post); // GenericRepository<Post>
}

GenericRepository - 用作GenericRepository&lt;Post&gt;

public void Update<T2>(T2 entity) where T2 : class, new()
{
   // create stub entity based on entity key, attach to graph.

   // override scalar values
   CurrentContext.ApplyCurrentValues(CurrentEntitySet, entity);
}

因此,需要为每个推荐调用 Update(或 AddDelete)存储库方法,具体取决于它是新的/修改的/删除的。

【问题讨论】:

    标签: asp.net-mvc entity-framework-4 repository poco


    【解决方案1】:

    使用分离的对象图是我最喜欢的 EF 缺点。简直是蛋疼。首先,您必须自己处理它。 EF 不会帮你的。这意味着除了Review,您还必须发送一些有关所做更改的信息。当您将Review 附加到上下文时,它会将Review 所有Recommendation所有关系 设置为Unchanged 状态。 ApplyCurrentValues 仅适用于您已经找到的标量值。因此,您必须使用有关所做更改的附加信息,并使用 ObjectContext.ObjectStateManager.ChangeRelationshipState 将关系状态设置为 Added

    我个人放弃了这种方法,我正在从数据库加载对象图,首先将我的更改合并到附加图中并保存。

    我回答了类似的问题more deeply here

    【讨论】:

    • “我个人放弃了这种方法,我从数据库加载对象图,首先将我的更改合并到附加图中并保存它。” - 这就是正是我所做的,哈哈。
    • 在这上面花了几天,最后被困在ChangeRelationshipState 拒绝使用EntityState.Modified,我也放弃并启用了外键属性。您的blog post 读起来很有趣。
    • @Stijn:关系不能有修改状态。更改关系意味着删除旧关系并创建新关系。
    • @LadislavMrnka 是的,我想通了,但我首先必须检索旧关系才能删除它。所以我发现暴露 FK 是较小的邪恶。
    【解决方案2】:

    我接受了@jfar 的回答,因为他让我走上了正确的道路,但我想我会在此处添加一个答案以使其他人受益。

    关系没有更新的原因如下:

    1) 完全断开连接的场景。 ASP.NET = 无状态,新的上下文更新了每个 HTTP 请求。

    2) MVC 创建的已编辑实体(模型绑定),但图中不存在。

    3) 当使用没有更改跟踪的 POCO 时,对实体执行 .Attach 会将其添加到图表中,但实体和任何子关系将保持不变。

    4) 我使用存根实体技巧和ApplyCurrentValues 来更新实体,但这仅适用于标量属性,不适用于导航属性。

    所以 - 为了使上述工作正常,我必须明确设置对象的 EntityState(由于 ApplyCurrentValues 而自动发生),还有导航属性.

    还有一个问题 - 我如何知道导航属性是否已添加/修改/删除?我没有可比较的对象 - 只有一个我知道被“编辑”过的实体,但我不知道 什么 被编辑了。

    所以最终的解决方案是这样的:

    [HttpPost]
    public ActionResult Edit(Review review)
    {
       var existingReview = _service.FindById(review.Id); // review is now in graph.
       TryUpdateModel(existingReview); // MVC equivalent of "ApplyCurrentValues" - but works for ALL properties - including navigationals
       _unitOfWork.Commit(); // save changed
    }
    

    就是这样。我什至不需要我的_service.Update 方法——因为我不再需要存根技巧——因为评论在检索图中,ApplyCurrentValuesTryUpdateModel 替换。

    现在当然 - 这不是一个并发证明解决方案。

    如果我加载评论编辑视图,并且在我点击“提交”之前其他人更改了评论,我的更改可能会丢失。

    幸运的是,我有一个“last-in-wins”并发模式,所以这对我来说不是问题。

    我喜欢 POCO,但是当您拥有无状态环境 (MVC) 和无变更跟踪的组合时,他们会很痛苦。

    【讨论】:

    • 在使用自动模型绑定时,这可能是一个相当大的安全漏洞,因为恶意请求还可以发送通常在表单上不可编辑的修改字段。
    • @Ladislav Mrnka - 这就是您将排除列表传递给TryUpdateModel 重载的原因。以上只是一个简单的例子。
    • @RPM1984,是否有一种全面的方法来反映从客户端收到的模型及其所有导航属性,包括单个和集合到商店的导航属性 - 包括删除客户端删除的实体?
    • 问题是相关集合被设置为Added即使它们已经存在!
    【解决方案3】:

    也许我需要更多上下文,但有什么问题:

    recommendations.Add(newRecomendation)
    

    ?

    回复评论:

    好的,有什么问题

    SomeServiceOrRepository.AddNewRecommendation( newRecommendation )
    

    SomeServiceOrRepository.AddNewRecommendation( int parentId, newRecommendation )
    

    最后一句话?你是说这两个问题吗?

    这应该不难。

    总结我的回答,我认为您正在“艰难地”做事,并且确实应该专注于发布与您尝试完成的 CRUD 操作相对应的表单值。

    如果一个新实体可能与您编辑的实体同时出现,您真的应该为它们添加不同的前缀,以便模型绑定器可以接收到它。即使您有多个新项目,您也可以使用相同的 [0] 语法,只需在“名称”字段前加上 New 或其他内容即可。

    在这种情况下,很多时候您不能依赖实体框架的图形功能,因为从集合中删除实体绝不意味着应该将其设置为删除。

    如果表单是不可变的,您也可以尝试使用 ObjectSet 的通用附加函数:

    theContect.ObjectSet<Review>().Attach( review )
    

    解决这个问题的方法很多。也许您可以发布您的控制器并查看代码?

    【讨论】:

    • 只将项目添加到集合中,我还需要将该实体添加到图表中。记住 - 分离的上下文,POCO,没有 STE。在这些情况下,EF 不知道需要将此建议添加到数据库中 - 我需要告诉它。
    • 回复 - 编辑。我怎么知道应该添加/修改/删除该建议?我需要将所有“旧”建议与新建议进行比较吗?看问题的最后一句话。
    • @jfar - 已编辑问题。阅读您的编辑 - 请记住我使用ApplyCurrentValuesAttach(review) 只会修改标量值 - 而不是推荐值。
    • 别担心 - 找到了。而且我认为它不适用于 POCO/分离的上下文。查看此声明:“如果您使用没有代理的 POCO 实体,则必须调用 DetectChanges 方法来同步对象上下文中的相关对象。如果您正在使用断开连接的对象,则必须手动管理同步。”我使用断开连接的对象(从表单值构造)。
    • @Ladislav Mrnka - 在你发布答案之前,我们已经在深夜工作了。所以你的权利,我们没有检查链接,它不存在! ;)
    猜你喜欢
    • 2011-07-04
    • 2011-06-18
    • 2011-04-07
    • 2011-01-03
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多