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

entity-framework repository-pattern unit-of-work

Accepted Answer

@Chris Hardie is right; EF automatically implements the UoW. However, a lot of users fail to notice that EF also includes a default implementation of a generic repository pattern:

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

And the tool itself comes with a rather solid generic repository implementation.

When DbContext provides everything you require, why bother going to the hassle of designing a ton of other interfaces and properties? If you wish to use command query segregation and abstract the DbContext behind application-level APIs, you could accomplish something as easy 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 wouldn't have to worry about adding three or more separate repositories to business code that works with three or more entity sets if you used these three interfaces for all of your entity access. It is easier to utilize IoC because all three of your interfaces are implemented by the same class. Of course, you would still use IoC to guarantee that there is only one DbContext object per web request.

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
}

No matter how many different things your dependence needs to interact with, you need just inject a single interface into it now:

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

Zzz-33-zzz is one of my favorite aspects of this design. In this instance, theRecipeController is the consumer, however the actual consumer in a practical application would be a command handler. (Normally, for a query handler, you would consumeIReadEntities just because you don't want to change any state; you only want to return data.) But let's utilize just one in this instance.RecipeController as a consumer, consider the ramifications of dependency:

Let's say you've created a set of unit tests for the aforementioned action. You create the Controller in each of these unit tests by sending a mock into the constructor. Then, let's assume your client decides they want to permit individuals to add to or create new Cookbooks when producing new recipes.

You would need to inject a new repository instance if your interface pattern was repository-per-entity or repository-per-aggregate.IRepository<Cookbook> (Or, adopting @Chris Hardie's suggestion, insert code to add yet another repository to the UoW instance) into your controller constructor. All of your other unit tests would break right away, forcing you to go back and change the construction code in each one while passing yet another fake object and expanding your dependency array. All of your other unit tests will still at least compile despite the aforementioned. Writing new test(s) to cover the new cookbook capabilities is all that has to be done.

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

Popular Answer

The codefizzle, Dyksta's post, and the preceding answers are all wrong, I'm (not) sorry to report. for no other reason than the huge WTF that they employ EF entities as domain (business) objects.

Update: Read Repository Model for Novices for a less complex explanation (in straightforward language).

The bottom line is that ANY repository interface shouldn't be connected to ANY persistence (ORM) detail. Only items that make sense for the rest of the program are dealt with in the repo interface (domain, maybe UI as in presentation). A LOT of folks fall into the trap of thinking that they can reuse their EF entities or that they can build business objects on top of them (MS leading the pack, with intention, I suppose).

Although it occasionally occurs, it's really uncommon. In reality, a lot of domain objects—bad modeling—are 'built' in accordance with database standards. Repositories serve the aim of separating persistent form from the rest of the app, particularly the business layer.

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

A domin object should never be aware of joins, EF, persistence, etc. It shouldn't be aware of the database engine you're using or even that you have one. If you want it to be decoupled from the persistence information, do the same with the remainder of the app.

The repository interface is limited in what it can learn from the higher layers. Therefore, a generic domain repository interface might resemble 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 interact with the database via EF. However, this is how the implementation seems.

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

 }

There isn't really a generic repository for you. The only time a concrete generic repository is used is when ANY domain object is serialized and kept in a table similar to a key-value store. With an ORM, this is not the case.

How about searching?

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

The read/view model appropriate for use in the query context is the UserData.

If you don't mind that your DAL is aware of view models, you can use EF directly for querying in a answer handler and won't need a query repository.

Conclusion

  • EF entities shouldn't be known to your business object.
  • The repo interface will only employ domain objects or view models (or any other app context object that isn't a persistence detail) because the ORM will be used by the repository., but it never makes the ORM public to the rest of the app.
  • You don't instruct the repo how to utilize IQueryable with a repo interface, for example.
  • If you simply want to access the database in a more convenient or cool way and you're working with a straightforward CRUD application where you don't require (be sure about it), use EF directly for all data instead. Although the app will be closely tied to EF, you will at least eliminate the middleman and it will be on purpose rather than by accident.

Keep in mind that misusing the repository will render it useless, and your program will remain tightly connected to persistence as a result (ORM).

If you think that the ORM's purpose is to magically store your domain objects, you are mistaken. On top of relational tables, the ORM simulates an OOP storage system. Don't utilize the ORM outside of persistence because it has nothing to do with domain and everything to do with 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