实体框架4 - 在哪里放置“ApplyCurrentValues”逻辑? [英] Entity Framework 4 - Where to put "ApplyCurrentValues" Logic?

查看:208
本文介绍了实体框架4 - 在哪里放置“ApplyCurrentValues”逻辑?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的是 存根技术 更新我的POCO(用于分离的上下文,ASP.NET MVC)。



这是我现在在我的控制器(它的工作)中的代码:

  [HttpPost] 
public ActionResult评论originalReview = _userContentService.FindById(review.PostId)as Review;评论(评论)
{

var ctx = _unitOfWork as MySqlServerObjectContext;
ctx.ApplyCurrentValues(MyEntities.Posts,review);
_unitOfWork.Commit();
// ..snip - MVC东西..
}

可以看到,有代码闻到无处不在。 :

几点:


  1. 我使用依赖注入(interface-基础上)基本上所有的东西

  2. 我使用工作单元模式来抽象ObjectContext并在多个存储库中提供持久性

  3. 目前我的 IUnitOfWork 接口只有一个方法: void Commit();

  4. 控制器有 IUserContentService IUnitOfWork 注入DI

  5. IUserContentService call 在存储库中查找,它们使用 ObjectContext

这两个东西我不喜欢我上面的代码:


  1. 我不想投 IUnitOfWork 作为 MySqlServerObjectContext

  2. 我不想让Controller关心 ApplyCurrentValues

我基本上希望我的代码看起来像这样:

  [HttpPost] 
public ActionResult Edit(Review review)
{
_userContentService.Update(review);
_unitOfWork.Commit();
// ..snip - MVC东西..
}

任何想法我该怎么办? (或类似的东西)。



我已经有智慧根据类型(泛型,多元化的组合)制定实体集名称,所以不用担心



但是,我想知道最好的地方放在 ApplyCurrentValues 是< STRONG>?将它放在 IUnitOfWork 接口中似乎不合适,因为这是持久性(EF)的关注。由于同样的原因,它不属于服务。如果我把它放在我的 MySqlServerObjectContext 类(有意义)中,我将从哪里调用这个,因为没有直接访问这个类 - 它是通过DI注入的 IUnitOfWork



任何想法?



编辑



我有一个解决方案,使用存根技术,但问题是如果我已经检索到我事先更新的实体,它会引发异常说明具有该密钥的实体已经存在。



尽管我不知道如何解决这个问题,但这是有道理的。



我需要检查实体是否已经附加,否则附加?



任何EF4专家可以帮助吗? p>

编辑



Nevermind - 找到解决方案,请参见下面的回答。

解决方案

想出来 - 不容易,所以我尽量解释最好。 (对于那些关心的人)



控制器相关代码

  // _userContentService是IUserContentService 
_userContentService.Update(review);

所以,我的控制器调用一个名为的方法更新 IUserContentService ,通过强类型的评论对象。



用户内容服务相关代码

  public void Update(Post post)
{
// _userContentRepository是IPostRepository
_userContentRepository.UpdateModel(post);
}

所以,我的服务调用一个名为 UpdateModel on IPostRepository ,通过强类型的评论对象。



现在,这里是棘手的部分。



我实际上没有特定的存储库。我有一个名为 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 ,它继承了所有核心存储库逻辑(查找,添加等)。



起初,我试图把这个 UpdateModel 代码,然后我不需要这个特定的存储库,但是问题是现有实体的检索逻辑是基于一个特定的实体密钥, 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()约束



实施:

 code> public void UpdateModel< T>(T post)其中T:Post,new()
{
var stub = new T {PostId = post.PostId};
CurrentEntitySet.Attach(stub);
Context.ApplyCurrentValues(GetEntityName< Post> post,post;
}

界面:

  void UpdateModel< T>(T post)其中T:Post,new(); 

这样可以防止我手动找出T类,防止并发问题,额外的旅行到DB。



相当groovy。



编辑3(我以为最后一次是最后一次)



上面的stub技术可以正常工作,但是如果我事先获取对象,它会抛出一个异常,指出具有该键的实体已经存在于OSM中。



任何人都可以建议如何处理?



编辑4 OK - 这是它!)



我找到了解决方案,感谢这个SO答案:是否可以检查是否对象已经附加到Entity Framework中的数据上下文



我试图使用以下代码检查实体是否附加:

  ObjectStateEntry条目; 
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(entity,out entry);

但是总是返回 null ,即使我探索了OSM可以用相同的密钥看到我的实体。



但是这个代码可以用:

  CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName< T>(),实体),输入项)

可能是因为我使用的是Pure POCO,OSM在弄清楚实体密钥时遇到困难,谁知道。



哦和另外一件事我添加了 - 所以我不必为每个实体添加一个特定的存储库,我创建了一个名为 [EntityKey] (公共属性属性)的属性。



所有POCO必须有1个公共属性用该属性装饰,或者我在我的存储库模块中抛出异常。



所以我的通用存储库然后看起来为此创建/设置存根。



是 - 它使用但是它是巧妙的反思(基于属性),我已经在使用反射来实现T的实体集名称。



无论如何,问题解决 - 所有工作罚款现在!


I'm using the "stub technique" to update my POCO's (used in a detached context, ASP.NET MVC).

This is the code i currently have in my controller (which works):

[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..
}

As you can see, there is code smell everywhere. :)

A few points:

  1. I use Dependency Injection (interface-based) for basically everything
  2. I use the Unit of Work pattern to abstract ObjectContext and provide persistence across multiple repositories
  3. Currently my IUnitOfWork interface has only 1 method: void Commit();
  4. Controller have IUserContentService and IUnitOfWork inject by DI
  5. IUserContentService calls Find in Repositories, which use the ObjectContext.

These are two things i don't like with my above code:

  1. I don't want to cast the IUnitOfWork as MySqlServerObjectContext.
  2. I don't want the Controller to have to care about ApplyCurrentValues

I basically want my code to look like this:

[HttpPost]
public ActionResult Edit(Review review)
{
   _userContentService.Update(review);
   _unitOfWork.Commit();
   // ..snip - MVC stuff..
}

Any ideas how i can do that? (or something similar).

I already have smarts to work out the entity set name based on the type (combination of generics, pluralization), so don't worry too much about that.

But i'm wondering where the best place to put ApplyCurrentValues is? It doesn't seem appropriate to put it in the IUnitOfWork interface, as this is a persistence (EF) concern. For the same reason it doesn't belong in the Service. If i put it in my MySqlServerObjectContext class (makes sense), where would i call this from, as nothing directly has access to this class - it is injected via DI when something requests IUnitOfWork.

Any thoughts?

EDIT

I have a solution below using the stub technique, but the problem is if i had retrieved the entity i am updating beforehand, it throws an exception, stating an entity with that key already exists.

Which makes sense, although i'm not sure how can resolve this?

Do i need to "check if the entity is already attached, if not, attach it?"

Can any EF4 experts out there help?

EDIT

Nevermind - found the solution, see answer below.

解决方案

Figured it out - wasn't easy, so i'll try to explain best i can. (for those who care)

Controller Relevant Code:

// _userContentService is IUserContentService
_userContentService.Update(review);

So, my controller calls a method called Update on IUserContentService, passing through the strongly-typed Review object.

User Content Service Relevant Code

public void Update(Post post)
{
   // _userContentRepository is IPostRepository
   _userContentRepository.UpdateModel(post);
}

So, my service calls a method called UpdateModel on IPostRepository, passing through the strongly-typed Review object.

Now, here is the tricky part.

I actually have no specific Repositories. I have a generic repository called GenericRepository<T> : IRepository<T>, which handles all the different repositories.

So when something requests a IPostRepository (which my service was doing), DI would give it a GenericRepository<Post>.

But now, i give it a 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);
   }
}

And because the class derives from GenericRepository, it inherits all the core repository logic (Find, Add, etc).

At first, i tried to put that UpdateModel code in the GenericRepository class itself (and then i wouldn't have needed this specific repository), but the problem is the logic to retrieve the existing entity is based on a specific entity key, which the GenericRepository<T> would not know about.

But the end result is the stitching is hidden deep down in the depths of the data layer, and i end up with a really clean Controller.

EDIT

This "stub technique" also works:

public void UpdateModel(Post post)
{
   var stub = new Review {PostId = post.PostId};
   CurrentEntitySet.Attach(stub);
   Context.ApplyCurrentValues(GetEntityName<Post>(), post);
}

But the problem is because Post is abstract, i cannot instantiate and therefore would have to check the type of Post and create stubs for every single derived type. Not really an option.

EDIT 2 (LAST TIME)

Okay, got the "stub technique" working with abstract classes, so now the concurrency issue is solved.

I added a generic type parameter to my UpdateModel method, and the special new() constraint.

Implementation:

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);
}

Interface:

void UpdateModel<T>(T post) where T : Post, new();

This prevents me from having to figure out the type of T manually, prevents concurrency issues and also prevents an extra trip to the DB.

Pretty groovy.

EDIT 3 (i thought the last time was the last time)

The above "stub technique" works, but if i retrieve the object beforehand, it throws an exception stating an entity with that key already exists in the OSM.

Can anyone advise how to handle this?

EDIT 4 (OK - this is it!)

I found the solution, thanks to this SO answer: Is is possible to check if an object is already attached to a data context in Entity Framework?

I had tried to "check if the entity is attached" using the following code:

ObjectStateEntry entry;
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry);

But it always returned null, even through when i explored the OSM i could see my entity there with the same key.

But this code works:

CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), entity), out entry)

Maybe because i'm using Pure POCO's, the OSM had trouble figuring out the entity key, who knows.

Oh and one other thing i added - so that i don't have to add a specific repository for each entity, i created an attribute called "[EntityKey]" (public property attribute).

All POCO's must have 1 public property decorated with that attribute, or i throw an exception in my repository module.

So my generic repository then looks for this property in order to create/setup the stub.

Yes - it uses reflection, but it's clever reflection (attribute-based) and i'm already using reflection for plularization of entity set names from T.

Anyway, problem solved - all working fine now!

这篇关于实体框架4 - 在哪里放置“ApplyCurrentValues”逻辑?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆