ASP.NET MVC / EF4 / POCO / Repository - 如何更新关系?

13

我在ReviewRecommendations之间有一个1..*的关系。

这部分模型也是由EF4映射的POCO:

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

在一个编辑视图中,我将推荐表示为一组复选框。

当我尝试作为编辑评论的一部分添加一个新的推荐(例如勾选另一个复选框)时,没有任何反应 - 我知道原因是什么...

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

我在StackOverflow的这个问题中找到了很好的答案,但我正在努力弄清楚如何使用POCO / Repository(以及ASP.NET MVC - 分离的上下文)使其工作。

由于我使用的是POCO,因此review.Recommendations是一个ICollection<Recommendation>,因此我无法执行review.Recommendations.Attach。我也没有使用自跟踪实体,因此需要手动处理图形/更改跟踪 - 直到现在都没有问题。

因此,您可以想象场景:

评论:

  • 推荐(ICollection<Recommendation>):
    • 推荐1(Recommendation
    • 推荐2(Recommendation

如果我在编辑视图上,两个复选框已经被选中。第三个(代表推荐3)是未选中的。

但是,如果我勾选该框,则以上模型变为:

评论:

  • 推荐(ICollection<Recommendation>):
    • 推荐1(Recommendation
    • 推荐2(Recommendation
    • 推荐3(Recommendation

因此,我需要将推荐3作为新实体附加到图中。

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

编辑

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

ReviewController

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

UserContentService

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

通用仓库 - 用作 GenericRepository<Post>

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);
}
因此,需要为每个建议调用“更新”(或“添加”或“删除”)存储库方法,具体取决于它是新的/修改的/已删除的。
3个回答

8

我接受了@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替换了。

当然,这并不是一个并发安全的解决方案。

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

幸运的是,我有一个“后来者胜利”的并发模式,所以这对我来说不是问题。

我喜欢POCO,但当你处于无状态环境(MVC)和没有更改跟踪的组合时,它们真的很麻烦。


当使用自动模型绑定时,这可能是一个相当大的安全漏洞,因为恶意请求也可以发送通常在表单上不可编辑的修改字段。 - Ladislav Mrnka
1
@Ladislav Mrnka - 这就是为什么你要将排除列表传递给TryUpdateModel重载。上面只是一个简单的例子。 - RPM1984
@RPM1984,有没有一种全面的方法可以将从客户端接收到的模型及其所有导航属性(包括单个和集合)反映到存储中,包括删除客户端删除的实体? - Shimmy Weitzhandler
问题在于相关的集合被设置为“已添加”,即使它们已经存在! - Shimmy Weitzhandler

6
使用分离的对象图是EF中我最不喜欢的缺点。这真是一件让人头疼的事情。首先,您必须自己处理它,EF不会帮助您。这意味着除了Review之外,您还必须发送有关所做更改的某些信息。当您将Review附加到上下文时,它会将Review及其所有Recommendation所有关系设置为Unchanged状态。ApplyCurrentValues仅适用于标量值,正如您已经发现的那样。因此,您必须使用有关所做更改的其他信息,并使用ObjectContext.ObjectStateManager.ChangeRelationshipState将关系的状态设置为Added
我个人放弃了这种方法,我首先从数据库中加载对象图,然后将我的更改合并到附加的图形中并保存它。
我在这里更深入地回答了类似的问题。

1
我个人放弃了这种方法,现在是先从数据库加载对象图,将我的更改合并到附加的图中,然后保存它。这正是我所做的,哈哈。 - RPM1984
在花费了几天的时间后,最终被卡在ChangeRelationshipState拒绝与EntityState.Modified一起工作上,我也放弃了并启用了外键属性。你的博客文章是一篇有趣的阅读。 - user247702
@Stijn:关系不能具有修改状态。更改关系意味着删除旧关系并创建新关系。 - Ladislav Mrnka
@LadislavMrnka 是的,我已经想到了,但是我首先必须检索旧关系才能删除它。因此,我发现将FK公开是较小的罪恶。 - user247702

4
也许我需要更多的上下文,但这样做有什么问题吗:
recommendations.Add(newRecomendation)

?

In reply to comment:

Ok so whats wrong with

SomeServiceOrRepository.AddNewRecommendation( newRecommendation )

或者

SomeServiceOrRepository.AddNewRecommendation( int parentId, newRecommendation )

“Last Sentence? You mean the two questions?”的意思是“最后一句话?你指的是那两个问题?”。
这应该不难。总结我的答案,我认为您正在以“困难的方式”进行操作,真正应该专注于发布与您要完成的CRUD操作对应的表单值。
如果一个新实体可以在编辑的实体同时进入,那么您应该使用不同的前缀,以便模型绑定器可以识别。即使您有多个新项目,您也可以使用相同的[0]语法只需将“name”字段添加前缀New或其他内容。
在这种情况下,很多时候不能依赖Entity Framework图形功能,因为从集合中删除实体并不意味着应该将其设置为删除状态。
如果表单不可变,还可以尝试使用ObjectSet的泛型附加函数:
theContect.ObjectSet<Review>().Attach( review )

有很多方法可以解决这个问题。也许您可以发布您的控制器和视图代码?

@jfar - 编辑了问题。请注意我正在使用ApplyCurrentValuesAttach(review)只会修改标量值,而不是建议。 - RPM1984
@RPM1984 - 文档中写道:“如果要附加的对象具有相关对象,则这些对象也将附加到对象上下文中。” 我很确定我已经使用了attach来做你试图做的事情。 - John Farrell
1
别担心 - 找到了。我认为它不能与POCO/detached上下文一起使用。请查看以下语句:“如果您正在使用没有代理的POCO实体,则必须调用DetectChanges方法以同步对象上下文中的相关对象。如果您正在使用断开连接的对象,则必须手动管理同步。” 我正在使用从表单值构建的断开连接的对象。 - RPM1984
看起来你们都没有检查我提供的链接。我想我在 Msdn 上找到了所有与这个问题相关的信息。 - Ladislav Mrnka
1
@Ladislav Mrnka - 在你发表答案之前,我们在深夜处理这个问题。所以你是对的,我们没有检查链接,它根本不存在! ;) - John Farrell
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接