The Repository Pattern – I’m Sold!

posted on 10/05/08 at 08:59:10 pm by Joel Ross

Lately, I've been playing with NHibernate and the Repository pattern - and struggling with it a bit. I understand the concept, but it's the implementation that's been bothering me. I was questioning whether to use one repository, or to have many - essentially one per entity. You can see me questioning it a bit in the Entities And Repositories Discussion I posted a while ago, where I asked Nate Kohari whether he used one repository or many. Here's the relevant part of that conversation. I've filtered it quite a bit to capture our back and forth.

RossCode: nkohari: do you have one repository or multiple repositories?
nkohari: RossCode: lately i've been using one
nkohari: but you can only get away with that in some cases
RossCode: nkohari: how do you handle custom queries? Pass in ICriteria?
nkohari: yeah, in the past i have, but lately i've moved to linq for nh
nkohari: so it's all Expression<Func<T, bool>>s
RossCode: nkohari: but then aren't your queries and how you query data in your controllers now?
nkohari: RossCode: touche ;)
nkohari: chadmyers was just talking about that
nkohari: he suggests creating query objects
nkohari: which is actually a very good idea

I didn't have a very good understanding of NHibernate at the time, and I certainly didn't get what he meant by query objects. Plus, it got lost in the middle of much larger conversation.

Fast forward to this past week. I was adding NHibernate to a project, and struggling with the same thing. I had an IRepository<T> that I was working against in my controllers. Then I had actual implementations like CustomerRepository, OrderRepository, etc. But when I needed to do something non-generic (like get a list of customers by a non-key id, like LastName), it quickly fell apart. Now my CustomerRepository had a method called GetCustomersByLastName. How do you use that method when you are working against IRepository<T>? Well, the first and obvious solution is to extract another interface - ICustomerRepository - and reference that in the controller. But that quickly gets overly complex.

So I went back to the tribe, and asked for some advice. I laid out what I was running into, and Nate pointed me to a blog that has information about using query objects. Once I saw it, it was so obvious! But it takes seeing it to really cement the idea. I immediately added it, and within an hour, my code was greatly simplified and much more generic. Not to mention easier to maintain in the long run.

So what exactly did I do? Well, I removed a lot of code. I had a base repository class that I removed. I had three or four custom repositories (OrderRepository, CustomerRepository, etc.) that I removed - and there would have been more of those if I wasn't so early in the development process.

I also added some code. I added a single Repository implementation, and I added a QueryBase<T> class, to help me encapsulate my queries.

Let's start with the Repository class:

   1:  public class Repository<T> : IRepository<T>
   2:  {
   3:    private ISession Session { get; set; }
   4:          
   5:    public Repository(ISession session)
   6:    {
   7:      Session = session;
   8:    }
   9:   
  10:    public IQueryable<T> GetList()
  11:    {
  12:      return (from entity in Session.Linq<T>() select entity);
  13:    }
  14:   
  15:    public T GetById(int id)
  16:    {
  17:      return Session.Get<T>(id);
  18:   
  19:    }
  20:   
  21:    public void Save(T entity)
  22:    {
  23:      Session.SaveOrUpdate(entity);
  24:    }
  25:   
  26:    public T GetOne(QueryBase<T> query)
  27:    {
  28:      return query.SatisfyingElementFrom(Session.Linq<T>());
  29:    }
  30:   
  31:    public IQueryable<T> GetList(QueryBase<T> query)
  32:    {
  33:   
  34:      return query.SatisfyingElementsFrom(Session.Linq<T>());
  35:    }
  36:  }

Note that I'm also using Linq For NHibernate, but the part that is interesting is at the end - the last two methods. Each take a QueryBase<T> object. This object defines what query to run. For example, if I want to get customers by last name, I could create a CustomersByLastNameQuery that inherits from QueryBase<Customer>. We'll get to how to do that, but first, QueryBase<T>:

   1:  public abstract class QueryBase<T> 
   2:  {
   3:    public abstract Expression<Func<T, bool>> MatchingCriteria { get; }
   4:   
   5:    public T SatisfyingElementFrom(IQueryable<T> candidates)
   6:    {
   7:      return SatisfyingElementsFrom(candidates).Single();
   8:    }
   9:   
  10:    public IQueryable<T> SatisfyingElementsFrom(IQueryable<T> candidates)
  11:    {
  12:      return candidates.Where(MatchingCriteria).AsQueryable();
  13:    }
  14:  }

This defines the mechanics of query objects. The Repository calls SatisfyingElementFrom (for one) or SatisfyingElementsFrom (for many), and the MatchingCriteria is used to determine how the query is built. This makes encapsulating queries easy - just implement MatchingCriteria. Here's one to get customers by last name:

   1:  public class CustomerByLastNameQuery : QueryBase<Customer>
   2:  {
   3:    private string _lastName;
   4:          
   5:    public CustomerByLastNameQuery(string lastName)
   6:    {
   7:      _lastName = lastName;
   8:    }
   9:   
  10:    public override Expression<Func<Customer, bool>> MatchingCriteria
  11:    {
  12:      get { return cust => cust.LastName == _lastName; }
  13:    }
  14:  }

As you can see, implementing your own query is relatively easy. And since the original method returns IQueryable<T>, you can later filter the results further, group them, sort them, etc., and still get the benefits of delayed execution. For completeness, here's how you could call the repository:

   1:  IQueryable<Customer> customers = repository.GetList(new CustomerByLastNameQuery(lastName));

By doing the above, I now have exactly what I want: simplicity in my repositories, and the ability to encapsulate and control how my entities are queried.

Tags: | |

Categories: Development, Software, C#