Entity Framework 6 Code First - Is Repository Implementation a Good One?

entity-framework repository-pattern unit-of-work

Question

I am about to implement an Entity Framework 6 design with a repository and unit of work.

There are so many articles around and I'm not sure what the best advice is: For example I realy like the pattern implemented here: for the reasons suggested in the article here

However, Tom Dykstra (Senior Programming Writer on Microsoft's Web Platform & Tools Content Team) suggests it should be done in another article: here

I subscribe to Pluralsight, and it is implemented in a slightly different way pretty much every time it is used in a course so choosing a design is difficult.

Some people seem to suggest that unit of work is already implemented by DbContext as in this post, so we shouldn't need to implement it at all.

I realise that this type of question has been asked before and this may be subjective but my question is direct:

I like the approach in the first (Code Fizzle) article and wanted to know if it is perhaps more maintainable and as easily testable as other approaches and safe to go ahead with?

Any other views are more than welcome.

1
50
1/25/2014 11:30:13 AM

Accepted Answer

@Chris Hardie is correct, EF implements UoW out of the box. However many people overlook the fact that EF also implements a generic repository pattern out of the box too:

var repos1 = _dbContext.Set<Widget1>();
var repos2 = _dbContext.Set<Widget2>();
var reposN = _dbContext.Set<WidgetN>();

...and this is a pretty good generic repository implementation that is built into the tool itself.

Why go through the trouble of creating a ton of other interfaces and properties, when DbContext gives you everything you need? If you want to abstract the DbContext behind application-level interfaces, and you want to apply command query segregation, you could do something as simple as this:

public interface IReadEntities
{
    IQueryable<TEntity> Query<TEntity>();
}

public interface IWriteEntities : IReadEntities, IUnitOfWork
{
    IQueryable<TEntity> Load<TEntity>();
    void Create<TEntity>(TEntity entity);
    void Update<TEntity>(TEntity entity);
    void Delete<TEntity>(TEntity entity);
}

public interface IUnitOfWork
{
    int SaveChanges();
}

You could use these 3 interfaces for all of your entity access, and not have to worry about injecting 3 or more different repositories into business code that works with 3 or more entity sets. Of course you would still use IoC to ensure that there is only 1 DbContext instance per web request, but all 3 of your interfaces are implemented by the same class, which makes it easier.

public class MyDbContext : DbContext, IWriteEntities
{
    public IQueryable<TEntity> Query<TEntity>()
    {
        return Set<TEntity>().AsNoTracking(); // detach results from context
    }

    public IQueryable<TEntity> Load<TEntity>()
    {
        return Set<TEntity>();
    }

    public void Create<TEntity>(TEntity entity)
    {
        if (Entry(entity).State == EntityState.Detached)
            Set<TEntity>().Add(entity);
    }

    ...etc
}

You now only need to inject a single interface into your dependency, regardless of how many different entities it needs to work with:

// NOTE: In reality I would never inject IWriteEntities into an MVC Controller.
// Instead I would inject my CQRS business layer, which consumes IWriteEntities.
// See @MikeSW's answer for more info as to why you shouldn't consume a
// generic repository like this directly by your web application layer.
// See http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91 and
// http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92 for more info
// on what a CQRS business layer that consumes IWriteEntities / IReadEntities
// (and is consumed by an MVC Controller) might look like.
public class RecipeController : Controller
{
    private readonly IWriteEntities _entities;

    //Using Dependency Injection 
    public RecipeController(IWriteEntities entities)
    {
        _entities = entities;
    }

    [HttpPost]
    public ActionResult Create(CreateEditRecipeViewModel model)
    {
        Mapper.CreateMap<CreateEditRecipeViewModel, Recipe>()
            .ForMember(r => r.IngredientAmounts, opt => opt.Ignore());

        Recipe recipe = Mapper.Map<CreateEditRecipeViewModel, Recipe>(model);
        _entities.Create(recipe);
        foreach(Tag t in model.Tags) {
            _entities.Create(tag);
        }
        _entities.SaveChanges();
        return RedirectToAction("CreateRecipeSuccess");
    }
}

One of my favorite things about this design is that it minimizes the entity storage dependencies on the consumer. In this example the RecipeController is the consumer, but in a real application the consumer would be a command handler. (For a query handler, you would typically consume IReadEntities only because you just want to return data, not mutate any state.) But for this example, let's just use RecipeController as the consumer to examine the dependency implications:

Say you have a set of unit tests written for the above action. In each of these unit tests, you new up the Controller, passing a mock into the constructor. Then, say your customer decides they want to allow people to create a new Cookbook or add to an existing one when creating a new recipe.

With a repository-per-entity or repository-per-aggregate interface pattern, you would have to inject a new repository instance IRepository<Cookbook> into your controller constructor (or using @Chris Hardie's answer, write code to attach yet another repository to the UoW instance). This would immediately make all of your other unit tests break, and you would have to go back to modify the construction code in all of them, passing yet another mock instance, and widening your dependency array. However with the above, all of your other unit tests will still at least compile. All you have to do is write additional test(s) to cover the new cookbook functionality.

47
7/10/2014 3:35:50 PM

Popular Answer

I'm (not) sorry to say that the codefizzle, Dyksta's article and the previous answers are wrong. For the simple fact that they use the EF entities as domain (business) objects, which is a big WTF.

Update: For a less technical explanation (in plain words) read Repository Pattern for Dummies

In a nutshell, ANY repository interface should not be coupled to ANY persistence (ORM) detail. The repo interface deals ONLY with objects that makes sense for the rest of the app (domain, maybe UI as in presentation). A LOT of people (with MS leading the pack, with intent I suspect) make the mistake of believing that they can reuse their EF entities or that can be business object on top of them.

While it can happen, it's quite rare. In practice, you'll have a lot of domain objects 'designed' after database rules i.e bad modelling. The repository purpose is to decouple the rest of the app (mainly the business layer) from its persistence form.

How do you decouple it when your repo deals with EF entities (persistence detail) or its methods return IQueryable, a leaking abstraction with wrong semantics for this purpose (IQueryable allows you to build a query, thus implying that you need to know persistence details thus negating the repository's purpose and functionality)?

A domin object should never know about persistence, EF, joins etc. It shouldn't know what db engine you're using or if you're using one. Same with the rest of the app, if you want it to be decoupled from the persistence details.

The repository interface know only about what the higher layer know. This means, that a generic domain repository interface looks like this

public interface IStore<TDomainObject> //where TDomainObject != Ef (ORM) entity
{
   void Save(TDomainObject entity);
   TDomainObject Get(Guid id);
   void Delete(Guid id);
 }

The implementation will reside in the DAL and will use EF to work with the db. However the implementation looks like this

public class UsersRepository:IStore<User>
 {
   public UsersRepository(DbContext db) {}


    public void Save(User entity)
    {
       //map entity to one or more ORM entities
       //use EF to save it
    }
           //.. other methods implementation ...

 }

You don't really have a concrete generic repository. The only usage of a concrete generic repository is when ANY domain object is stored in serialized form in a key-value like table. It isn't the case with an ORM.

What about querying?

 public interface IQueryUsers
 {
       PagedResult<UserData> GetAll(int skip, int take);
       //or
       PagedResult<UserData> Get(CriteriaObject criteria,int skip, int take); 
 }

The UserData is the read/view model fit for the query context usage.

You can use directly EF for querying in a query handler if you don't mind that your DAL knows about view models and in that case you won't be needing any query repo.

Conclusion

  • Your business object shouldn't know about EF entities.
  • The repository will use an ORM, but it never exposes the ORM to the rest of the app, so the repo interface will use only domain objects or view models (or any other app context object that isn't a persistence detail)
  • You do not tell the repo how to do its work i.e NEVER use IQueryable with a repo interface
  • If you just want to use the db in a easier/cool way and you're dealing with a simple CRUD app where you don't need (be sure about it) to maintain separation of concerns then skip the repository all together, use directly EF for everything data. The app will be tightly coupled to EF but at least you'll cut the middle man and it will be on purpose not by mistake.

Note that using the repository in the wrong way, will invalidate its use and your app will still be tightly coupled to the persistence (ORM).

In case you believe the ORM is there to magically store your domain objects, it's not. The ORM purpose is to simulate an OOP storage on top of relational tables. It has everything to do with persistence and nothing to do with domain, so don't use the ORM outside persistence.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow