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

Category: Development, Software, C#

11 comments

Comments, Pingbacks:

Comment from: Trevor [Visitor] Email
If you think this technique is good, then you should check out LLBLGen, it will blow your mind. None of the "plumbing" code you write in your example is required, you can just naturally query anything you want...or, in a sense, the code in your example above already exists, for *everything*.

Oh, and it also has full LINQ support.
PermalinkPermalink 10/06/08 @ 12:54
Comment from: James Thigpen [Visitor] Email · http://www.jamesthigpen.com
Oooh this is a great blog article.

Pretty much everyone runs into this problem when learning about repositories, and so few people discuss the steps for how they get to a solution as opposed to "Here's my solution! Ta-da!"
PermalinkPermalink 10/07/08 @ 12:49
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
James,

Thanks for the comment! I think the thought process to get to a solution is just about as important as the actual solution, so that's what I try to do in my posts.
PermalinkPermalink 10/07/08 @ 22:12
Comment from: Chris Holmes [Visitor] Email · http://www.chrisholmesonline.com
@Trevor:

Problem with LLBLGenPro (and I know, because we use it) is that it generates its own entities. So if you want the full ease-of-use with LLBL then you have to corrupt your application by using their entities throughout. That's not a viable option for a lot of applications, so now you add a second layer of mapping to map LLBL objects to your POCO's, or DTO's, or whatever. If you want to shield your application from the specifics of LLBL you'll still end up writing a wrapper around the query syntax of LLBL (which is actually fairly easy to do thanks to the way LLBL is setup, because it separates relations from prefetch paths).

NHibernate has the distinct advantage of mapping directly from the DB to your C# objects. Personally, I'd rather use NHibernate any day.
PermalinkPermalink 10/09/08 @ 12:11
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
@Trevor:

I've been down the code generation path as well - I'm a major contributor to RCM's Kinetic Framework (http://www.codeplex.com/KineticFramework).

The problem with generating code is that you lose the ability to choose how your entities are arranged - what relationships are exposed, how they are grouped, etc. Most gen models are basically the ActiveRecord pattern, which applies in most cases for me. But in those ones where doing it that way doesn't apply, you end up fighting the generation tool.
PermalinkPermalink 10/09/08 @ 12:19
Comment from: Nick [Visitor] Email
Can't you also use an XML mapping file with Linq to SQL? Are there advantages to NHibernate over this approach?
PermalinkPermalink 10/26/08 @ 07:05
Comment from: Steinard [Visitor] Email
Hi!

Nice read. Just got one question?

Where in your architecture are the query objects defined and from where in your architecture can they be created and invoked?

Cheers,
Steinard.
PermalinkPermalink 03/30/09 @ 12:51
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
Steinard,

One of the best way I've seen it done is to have the query objects be extension methods on your repository that handled the creation and usage of the query objects for you. So you could have:

public IQueryable<Customer%gt;GetByLastName(this IRepository<Customer> repository, string lastName)

This method would then build the query object and pass it to the repository. I think it's a good balance between having custom repositories and making how you query your domain discoverable.
PermalinkPermalink 03/30/09 @ 22:05
Comment from: Steinard [Visitor] Email
Hi!

Yes, that would make sense for people able to use .Net 3.5.

During this last week I've created some good old fashioned query objects and of course placed them in the Data Access Layer to avoid spreading querying logic around in the architecture.

I've seen convenience methods that enable passing in criterias to repositories/daos and IMHO it's not good when whatever part of the architecture start assembling queries.

Cheers,
Steinar.
PermalinkPermalink 04/04/09 @ 14:57
Comment from: peter [Visitor] Email
Hey! Very nice. now the questions!!
Using the repository pattern how can switch the data access from native ADO to Entity to nHibernate. I presume a seperate implementation for each, Will the Query objects still work??

Thanks
PermalinkPermalink 11/04/09 @ 18:03
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
Peter,

If everything you're using supports Linq and delayed execution, then in theory all you should have to do is change out your repository implementation. And if you're doing it right, that should just be a change in your IoC configuration! :-)

Joel
PermalinkPermalink 11/04/09 @ 21:19

Leave a comment:

Your email address will not be displayed on this site.
Your URL will be displayed.

Allowed XHTML tags: <p, ul, ol, li, dl, dt, dd, address, blockquote, ins, del, span, bdo, br, em, strong, dfn, code, samp, kdb, var, cite, abbr, acronym, q, sub, sup, tt, i, b, big, small>
(Line breaks become <br />)
(Set cookies for name, email and url)
(Allow users to contact you through a message form (your email will NOT be displayed.))