我正在使用“stub technique”更新我的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..
}
如您所见,到处都是代码气味。:)
有几点:
我使用依赖注入(基于接口)来实现基本的所有功能
我使用工作单元模式抽象objectcontext并跨多个存储库提供持久性
目前我的iUnitofWork接口只有一个方法:
void Commit();
控制器通过DI注入
IUserContentService
和IUnitOfWork
。IUserContentService
在使用Find
的存储库中调用ObjectContext
。这两件事我不喜欢我上面的代码:
我不想把iUnitOfWork当作
MySqlServerObjectContext
。我不希望控制器必须关心
ApplyCurrentValues
我基本上希望我的代码看起来像这样:
[HttpPost]
public ActionResult Edit(Review review)
{
_userContentService.Update(review);
_unitOfWork.Commit();
// ..snip - MVC stuff..
}
有什么办法吗?(或类似的东西)。
我已经有了根据类型(泛型的组合,复数)计算实体集名称的智能,所以不要太担心这个问题。
但我想知道最好放在哪里?把它放在
ApplyCurrentValues
接口中似乎不合适,因为这是一个持久性(ef)问题。因为同样的原因它不属于服务。如果我把它放在我的IUnitOfWork
类中(有意义),我从哪里调用它,因为没有任何东西可以直接访问这个类-当有东西请求MySqlServerObjectContext
时,它通过di注入。有什么想法吗?
编辑
在使用存根技术的下面我有一个解决方案,但是问题是,如果我事先检索了我正在更新的实体,它就会抛出一个异常,说明一个具有该密钥的实体已经存在。
这是有道理的,虽然我不确定如何解决这个问题?
我是否需要“检查实体是否已附加,如果没有,则附加它?”
有什么EF4专家可以帮忙吗?
编辑
无所谓-找到了解决方案,请参阅下面的答案。
最佳答案
明白了-不容易,所以我会尽力解释清楚。(对于那些关心的人)
控制器相关代码:
// _userContentService is IUserContentService
_userContentService.Update(review);
因此,我的控制器调用一个名为
Update
onIUserContentService
的方法,通过强类型Review
对象。用户内容服务相关代码
public void Update(Post post)
{
// _userContentRepository is IPostRepository
_userContentRepository.UpdateModel(post);
}
因此,我的服务调用一个名为
UpdateModel
onIPostRepository
的方法,通过强类型Review
对象。现在,这里是棘手的部分。
实际上我没有特定的存储库。我有一个名为
GenericRepository<T> : IRepository<T>
的通用存储库,它处理所有不同的存储库。所以当某个东西请求
IPostRepository
(我的服务正在做的)时,di会给它一个GenericRepository<Post>
。但现在,我给它一个
PostRepository
:public class PostRepository : GenericRepository<Post>, IPostRepository
{
public void UpdateModel(Post post)
{
var originalPost = CurrentEntitySet.SingleOrDefault(p => p.PostId == post.PostId);
Context.ApplyCurrentValues(GetEntityName<Post>(), post);
}
}
而且由于类派生自genericrepository,因此它继承了所有核心存储库逻辑(find、add等)。
起初,我试图把这个更新模型代码放在GuangiCueLood类本身中(然后我就不需要这个特定的存储库),但问题是检索现有实体的逻辑是基于一个特定的实体密钥,而
GenericRepository<T>
不知道。但最终的结果是缝合被隐藏在数据层的深处,最后我得到了一个非常干净的控制器。
编辑
这种“存根技术”也适用于:
public void UpdateModel(Post post)
{
var stub = new Review {PostId = post.PostId};
CurrentEntitySet.Attach(stub);
Context.ApplyCurrentValues(GetEntityName<Post>(), post);
}
但问题是因为post是抽象的,我不能实例化,因此必须检查post的类型并为每个派生类型创建存根。不是一个真正的选择。
编辑2(上次)
好吧,使用“存根技术”处理抽象类,现在并发问题解决了。
我向updateModel方法添加了一个泛型类型参数,以及特殊的new() constraint。
实施:
public void UpdateModel<T>(T post) where T : Post, new()
{
var stub = new T { PostId = post.PostId };
CurrentEntitySet.Attach(stub);
Context.ApplyCurrentValues(GetEntityName<Post>, post);
}
接口
void UpdateModel<T>(T post) where T : Post, new();
这就避免了我必须手动计算t的类型,防止并发问题,也防止了对db的额外访问。
非常棒。
编辑3(我以为最后一次是最后一次)
上面的“存根技术”起作用,但是如果我事先检索对象,它会抛出一个异常,说明在OSM中已经存在一个具有该关键字的实体。
有人能建议如何处理这个问题吗?
编辑4(好-就这样!)
我找到了解决办法,多亏了这个答案:Is is possible to check if an object is already attached to a data context in Entity Framework?
我试图使用以下代码“检查实体是否已附加”:
ObjectStateEntry entry;
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry);
但它总是返回空值,即使当我浏览osm时,我也可以看到我的实体在那里使用相同的键。
但这条规则有效:
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), entity), out entry)
也许因为我使用的是纯poco,osm很难找出实体密钥,谁知道呢。
哦,还有一件事我添加了-为了不必为每个实体添加特定的存储库,我创建了一个名为“[EntityKey](公共属性属性)的属性。
所有poco都必须有一个用该属性修饰的公共属性,否则我会在存储库模块中抛出异常。
因此,我的通用存储库将查找此属性,以便创建/设置存根。
是的-它使用反射,但它是聪明的反射(基于属性),我已经在使用反射对t中的实体集名称进行多极化。
不管怎样,问题解决了-现在一切正常!