Entity Framework 4 - Where to put “ApplyCurrentValues” Logic?

我的梦境 提交于 2019-11-30 05:23:30
RPM1984

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!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!