Encapsulating Entity access in EntityFramework by using the Repository pattern


EntityFramework provides a very easy way to query your entities and thus the database but it creates a very strong dependency between the calling code and the data access layer(EF in this case).

The Repository pattern allows you to encapsulate any entity access logic and also to decouple your business logic layer from your data access layer, in other words, enables separation of concerns, plus it makes unit testing a lot easier.

Consider the following entity data model:

model

And a WCF Service that exposes Orders and its aggregated OrderDetails:

public class OrderService : IOrderService
{
    private MFEntitiesContainer _context = new MFEntitiesContainer();

    public IEnumerable<Order> ListOrders()
    {
        return _context.OrderSet.Include("OrderDetails")
                                .Where(o => o.Customer.Active);
    }

    public IEnumerable<Order> ListOrdersByCustomer(int customerId)
    {
        return _context.OrderSet.Include("OrderDetails")
                                .Where(o => o.Customer.Active && o.Customer.Id == customerId);
    }
}

Lets see how we can refactor the OrderService to use the Repository pattern instead of querying the EF’s ObjectContext.

Consider that when working with Order entities its OrderDetails must always be loaded and that I can only work with Orders whose Customer is still active.

Notice that I had to code the customer is still active predicate and that if I’m querying Order entities in any other place I will have to write it there too. I’m also using EntityFramework lazy-load access logic (.Include call) everywhere which means that if someday I want to switch my DAL to NHibernate I might have to refactor a lot of code.

The Repository pattern is very useful in these cases and is often implemented with a generic interface that looks like this:

public interface IRepository<TEntity>
{
    IQueryable<TEntity> Query();
    void Add(TEntity entity);
    void Attach(TEntity entity);
    void Delete(TEntity entity);
    void SaveChanges();
}

Thanks to EntityFramework’s ObjectContext class we can implement a base repository class that works for any entity:

public class EntityRepository<TEntity> : IRepository<TEntity>
    where TEntity : EntityObject, new()
{
    public EntityRepository(ObjectContext context)
    {
        this.ObjectContext = context;
        this.FetchEntitySetName();
    }

    protected ObjectContext ObjectContext { get; private set; }
    protected string EntitySetName { get; private set; }

    //looks for an IQueryable<TEntity> property in the ObjectContext
    //and gets its name to be used in other methods
    private void FetchEntitySetName()
    {
        var entitySetProperty =
           this.ObjectContext.GetType().GetProperties()
               .Single(p => p.PropertyType.IsGenericType && typeof(IQueryable<>)
               .MakeGenericType(typeof(TEntity)).IsAssignableFrom(p.PropertyType));

        this.EntitySetName = entitySetProperty.Name;
    }

    //to be implemented by derived classes if needed
    protected virtual IQueryable<TEntity> BuildQuery(ObjectQuery<TEntity> query)
    {
        return query;
    }

    public virtual IQueryable<TEntity> Query()
    {
        var entitySet = String.Format("[{0}]", this.EntitySetName);
        var baseQuery = this.ObjectContext.CreateQuery<TEntity>(entitySet);
        return this.BuildQuery(baseQuery);
    }

    public virtual void Add(TEntity entity)
    {
        this.ObjectContext.AddObject(this.EntitySetName, entity);
    }

    public virtual void Attach(TEntity entity)
    {
        this.ObjectContext.AttachTo(this.EntitySetName, entity);
    }

    public virtual void Delete(TEntity entity)
    {
        if (entity.EntityState == EntityState.Detached)
            this.ObjectContext.AttachTo(this.EntitySetName, entity);

        this.ObjectContext.DeleteObject(entity);
    }

    public void SaveChanges()
    {
        this.ObjectContext.SaveChanges();
    }
}

Notice the BuildQuery method that we can use in derived classes if we need to provide base queries to access a specific entity. In this case we would create an OrderRepository class like the following:

public class OrderRepository : EntityRepository<Order>
{
    public OrderRepository(MFEntitiesContainer context) : base(context) { }

    protected override IQueryable<Order> BuildQuery(System.Data.Objects.ObjectQuery<Order> query)
    {
        return query.Include("OrderDetails").Where(o => o.Customer.Active);
    }
}

If you’re familiar with Inversion of Control and Dependency Injection concepts you’re already thinking on how to refactor the OrderService in order to use the OrderRepository implementation but depend only on the IRepository interface. For this post I didn’t consider using any specific IoC container so I hard-coded one that works, although I’m a big fan of Unity 🙂

public static class YourFavIoCContainer
{
    //only works for IRepository<T> for blog purposes..
    public static T Resolve<T>(params object[] constructorParameters)
    {
        Type repositoryType = null;
        if (typeof(IRepository<Order>).IsAssignableFrom(typeof(T)))
            repositoryType = typeof(OrderRepository);
        else if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(IRepository<>))
        {
            Type entityType = typeof(T).GetGenericArguments()[0];
            repositoryType = typeof(EntityRepository<>).MakeGenericType(entityType);
        }
        else
            throw new InvalidOperationException("Only IRepository<T> implementations can be resolved");

        return (T)Activator.CreateInstance(repositoryType, constructorParameters);
    }
}

The OrderService can now be refactored to use the Repository pattern like this:

public class OrderService : IOrderService
{
    private MFEntitiesContainer _context = new MFEntitiesContainer();

    public IEnumerable<Order> ListOrders()
    {
        return YourFavIoCContainer.Resolve<IRepository<Order>>(_context).Query();
    }

    public IEnumerable<Order> ListOrdersByCustomer(int customerId)
    {
        return YourFavIoCContainer.Resolve<IRepository<Order>>(_context)
                                        .Query().Where(o=>o.Customer.Id == customerId);
    }
}

Some folks don’t like to do custom queries outside the Repository world so for cases like the ListOrdersByCustomer operation you can also encapsulate the customer predicate in the OrderRepository by creating an IOrderRepository interface that derives from IRepository<Order>:

public interface IOrderRepository : IRepository<Order>
{
    IQueryable<Order> QueryByCustomer(int customerId);
}

public class OrderRepository : EntityRepository<Order>, IOrderRepository
{
    public OrderRepository(MFEntitiesContainer context) : base(context) { }

    protected override IQueryable<Order> BuildQuery(System.Data.Objects.ObjectQuery<Order> query)
    {
        return query.Include("OrderDetails").Where(o => o.Customer.Active);
    }

    public IQueryable<Order> QueryByCustomer(int customerId)
    {
        return this.Query().Where(o => o.Customer.Id == customerId);
    }
}

And the refactored ListOrdersByCustomer operation should look like this:

public IEnumerable<Order> ListOrdersByCustomer(int customerId)
{
    return YourFavIoCContainer.Resolve<IOrderRepository>(_context).QueryByCustomer(customerId);
}

Now we only need to get rid of the MFEntitiesContainer (the ObjectContext instance) since it represents our last dependency to Entity Framework objects. I shouldn’t be creating nor managing the ObjectContext instance in my service but instead It should be managed using a unit of work pattern that should last as long as my service is activated (works well with WCF’s SessionMode).

Since the purpose of this post was all about the Repository pattern I’ll try to address the issues I mentioned above in another post (soon I hope!). I’ve done something similar before with NHibernate using Sharp-Architecture and WCF, so I might try that here. If anyone has thoughts on the matter please contribute!

Happy coding!

Advertisements

15 thoughts on “Encapsulating Entity access in EntityFramework by using the Repository pattern

  1. public class EntityRepository : IRepository
    where TEntity : EntityObject, new()
    {
    public EntityRepository(ObjectContext context)
    {
    this.ObjectContext = context;
    this.FetchEntitySetName();
    }

    When I try to access this.ObjectContext… No such thing exists what am I missing?

  2. I’m trying to get my head around EF and your post has helped me immensely. Our DB design is such that we don’t do hard deletes, so all reads have to include the clause ‘IsDeleted ==0’. Your method will make our code much DRYer. Thank you!

    • Hello Dean,

      I’m glad to hear that my post helped you.

      Indeed, the repository is the ideal place to encapsulate the IsDeleted clause.

      Thanks for your feedback,
      Manuel Felício.

  3. So each and every time I would like to add a single method to the repository (add more functionality other
    then the base repository) I would have to add a new interface and a new line in the initialization of my IoC?

    It looks like a lot of work for one method..

    Are there any other possibilities to encapsulate the entity access?

    • @Naor,

      Of course not.. that would be a lot of work indeed. You just need to create a new interface and put all the new methods there.

      For example, if you want to add functionality that only applies to Orders, you would create an IOrderRepository and add methods there. If you want to add functionality that applies to all entities you can create a new interface (and put the methods there) that derives from IRepository, create a new base class for your repositories that extends the base Repository class and implements that new interface.

      Your IoC must be updated as well, if you make your classes depend on the new interfaces.

  4. 2011-12-20 SSDK MVC3 Reference Architecture discussion (part 2)…

    Date: 20111220 Attendees: Sergiy Beslik; Pavlo Prystupa Agenda: # SSDK MVC3 Reference Architecture discussion Meeting minutes: # Presentation Layer: ## Other MVCContrib helpers ### Research on what helpers will be used is included in WBS…….

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s