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: TwitterTribe | Repository Pattern | NHibernate
Category: Development, Software, C#
Comments, Pingbacks:
Oh, and it also has full LINQ support.
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!"
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.
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.
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.
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.
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.
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.
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
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
Leave a comment:
