entity framework4 – 在哪里放置“ApplyCurrentValues”逻辑?
我正在使用“ 存根技术 ”来更新我的POCO(用于分离的上下文,ASP.NET MVC)。
这是我目前在我的控制器中的代码(可以工作):
[HttpPost] public ActionResult Edit(Review review) { Review originalReview = _userContentService.FindById(review.PostId) as Review; var ctx = _unitOfWork as MySqlServerObjectContext; ctx.ApplyCurrentValues("MyEntities.Posts", review); _unitOfWork.Commit(); // ..snip - MVC stuff.. }
如您所见,到处都有代码味道。 🙂
几点:
- 我基本上都使用dependency injection(基于接口)
- 我使用工作单元模式来抽象ObjectContext并在多个存储库之间提供持久性
- 目前我的IUnitOfWork接口只有一个方法:
void Commit();
- 控制器有
IUserContentService
和IUnitOfWork
注入DI -
IUserContentService
在存储库中调用Find
,它使用ObjectContext
。
以上代码是我不喜欢的两件事:
- 我不想将IUnitOfWork转换为
MySqlServerObjectContext
。 - 我不希望Controller必须关心
ApplyCurrentValues
我基本上希望我的代码看起来像这样:
[HttpPost] public ActionResult Edit(Review review) { _userContentService.Update(review); _unitOfWork.Commit(); // ..snip - MVC stuff.. }
任何想法我怎么能这样做? (或类似的东西)。
我已经很聪明地根据类型(generics,复数的组合)来计算实体集名称,所以不要过于担心。
但我想知道放置ApplyCurrentValues
的最佳位置在ApplyCurrentValues
? 将它放在IUnitOfWork
接口中似乎不合适,因为这是一个持久性(EF)问题。 出于同样的原因,它不属于服务。 如果我把它放在我的MySqlServerObjectContext
类中(有意义),我将从哪里调用它,因为没有任何东西可以直接访问这个类 – 当某些东西请求IUnitOfWork
时它会通过DI注入。
有什么想法吗?
编辑
我在下面使用存根技术有一个解决方案,但问题是如果我已经检索到我正在更新的实体,它会抛出exception,说明具有该密钥的实体已经存在。
这是有道理的,虽然我不知道如何解决这个问题?
我是否需要“检查实体是否已经附加,如果没有,请附上它?”
任何EF4专家都可以提供帮助吗?
编辑
没关系 – 找到了解决方案,请参阅下面的答案。
想出来 – 不容易,所以我会尽力解释。 (对于那些关心的人)
控制器相关代码:
// _userContentService is IUserContentService _userContentService.Update(review);
因此,我的控制器在IUserContentService
上调用一个名为Update
的方法,并通过强类型的Review
对象。
用户内容服务相关代码
public void Update(Post post) { // _userContentRepository is IPostRepository _userContentRepository.UpdateModel(post); }
因此,我的服务在IPostRepository
上调用一个名为UpdateModel
的方法,并通过强类型的Review
对象。
现在,这是棘手的部分。
我实际上没有特定的存储库 。 我有一个名为GenericRepository
的通用存储库 ,它处理所有不同的存储库。
因此当某些东西请求IPostRepository
(我的服务正在做)时,DI会给它一个GenericRepository
。
但现在,我给它一个PostRepository
:
public class PostRepository : GenericRepository, IPostRepository { public void UpdateModel(Post post) { var originalPost = CurrentEntitySet.SingleOrDefault(p => p.PostId == post.PostId); Context.ApplyCurrentValues(GetEntityName (), post); } }
并且因为该类派生自GenericRepository ,它inheritance了所有核心存储库逻辑(查找,添加等)。
起初,我试图将UpdateModel代码放在GenericRepository类本身(然后我就不需要这个特定的存储库),但问题是检索现有实体的逻辑是基于特定的实体密钥, GenericRepository
不会知道。
但最终的结果是缝合深藏在数据层的深处,我最终得到了一个非常干净的控制器。
编辑
这种“存根技术”也有效:
public void UpdateModel(Post post) { var stub = new Review {PostId = post.PostId}; CurrentEntitySet.Attach(stub); Context.ApplyCurrentValues(GetEntityName(), post); }
但问题是因为Post是抽象的,我无法实例化,因此必须检查Post的类型并为每个派生类型创建存根。 不是一个选择。
编辑2(上次)
好吧,让“存根技术”与抽象类一起工作,所以现在解决了并发问题。
我在UpdateModel方法中添加了一个generics类型参数,以及特殊的new()约束 。
执行:
public void UpdateModel(T post) where T : Post, new() { var stub = new T { PostId = post.PostId }; CurrentEntitySet.Attach(stub); Context.ApplyCurrentValues(GetEntityName, post); }
接口:
void UpdateModel(T post) where T : Post, new();
这可以防止我必须手动找出T的类型,防止并发问题,并防止额外的数据库之旅。
非常时髦。
编辑3(我认为最后一次是最后一次)
上面的“存根技术”可以工作,但是如果我事先检索对象,它会抛出一个exception,说明OSM中已存在具有该密钥的实体。
任何人都可以建议如何处理这个?
编辑4(好的 – 这就是它!)
我找到了解决方案,多亏了这个答案: 是否可以检查对象是否已经附加到Entity Framework中的数据上下文?
我曾尝试使用以下代码“检查实体是否已附加”:
ObjectStateEntry entry; CurrentContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry);
但它总是返回null ,即使在我探索OSM时我可以看到我的实体在那里使用相同的密钥。
但是这段代码有效:
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName(), entity), out entry)
也许是因为我使用Pure POCO,OSM很难搞清楚实体密钥,谁知道呢。
哦,我添加的另一件事 – 所以我不必为每个实体添加一个特定的存储库,我创建了一个名为“ [EntityKey] ”的属性(公共属性属性)。
所有POCO都必须有1个用该属性修饰的公共属性,或者我在我的存储库模块中抛出exception。
因此,我的通用存储库然后查找此属性以创建/设置存根。
是的 – 它使用reflection,但它是巧妙的reflection(基于属性),我已经使用reflection来实现T的实体集名称的多层化。
无论如何,问题解决了 – 现在一切正常!