Geeks With Blogs
.net alternatives by Michel Grootjans

Since I published the state of my project goals, I got a few questions about my repository implementation, so here goes…

The ‘classic’ repository interface looks like this:

public interface IRepository<T>
{
    T GetById(int id);
    IEnumerable<T> GetAll();
    T SaveOrUpdate(T entity);
    void Delete(T entity);
    //...
}

This interface has some issues to me. First of all, it’s data-centric. I know that’s the whole point of a repository, but bear with me. Second, it exposes far too many methods. One method in particular scares me: GetAll()! This is the one method you REALLY don’t want to execute in production when you have massive amounts of data. And if you’re lucky, you will have a few eager fetches with a nice SELECT N+1. Yikes.

The implementations of this interface tend to become a long list of different queries.

public class CustomerRepository : IRepository<Customer>
{
    public Customer GetById(int id) {...}
    public IEnumerable<Customer> GetAll(){...}
    public Customer SaveOrUpdate(Customer entity){...}
    public void Delete(Customer entity){...}

    public IEnumerable<Customer> GetByName(string customerName){...}
    public IEnumerable<Customer> GetByTotalOrderAmount(double orderAmount){...}
    public IEnumerable<Customer> GetByCity(string zipCode){...}
    public IEnumerable<Customer> GetNewCustomersSince(DateTime date){...}
    ...
}

An alternative

I’ve been looking for an alternative to this. The goals I had in mind are:

  • I want an easy interface to interact with the database
  • Every time I need a new query, I don’t want to have to add a new method to an existing object. I want a new object that encapsulates the query.
  • I want to be free to choose which query API call I make against NHibernate: LINQ, criteria or hql.

A single IRepository

My IRepository interface is a simple one:

public interface IRepository
{
    void Add<T>(T entity);
    void Remove<T>(T entity);
    IQueryResult<T> Query<T>(IQuery<T> query);
}

Add() and Remove() just look and feel like standard collection methods, and the implied behavior is simply forwarded to the database. Since I’m using NHibernate under the hood, there’s no need to have a Save() or an Update() statement, NHibernate’s autoflush will take care of this.

The interesting part is the last method: Query(). This method takes a query object, and returns a query result object. When you call this method, you can do this:

var customer = repository.Query(new GetCustomerByName("ALFKI"))
                         .UniqueResult();

Lets take a closer look at the repository implementation:

public class NHibernateRepository : IRepository
{
    public void Add<T>(T entity)
    {
        CurrentSession.Save(entity);
    }

    public void Remove<T>(T entity)
    {
        CurrentSession.Delete(entity);
    }

    public IQueryResult<T> Query<T>(IQuery<T> query)
    {
        return query.Execute(CurrentSession);
    }

    private static ISession CurrentSession
    {
        // use whatever suits you ... out of scope of this post
    }
}

IQuery<T>

The query object can use the NHibernate query, criteria or LINQ, the repository doesn’t care. This offers several advantages:

  • You only need one repository (or DAO, or whatever you want to name it)
  • The repository implementation doesn’t have to change every time you need a new query
  • You can test each query object in isolation
  • Your services have to reference only one repository

Lets go and take a look at what a query object implementation looks like:

 
public class GetCustomerByName : IQuery<Customer>
{
    private readonly string name;

    public GetCustomerByName(string name)
    {
        this.name = name;
    }

    public IQueryResult<Customer> Execute(ISession session)
    {
        var query = session.CreateQuery("from Customer where Name=:name")
            .SetString("name", name);
        return new QueryResult<Customer>(query);
    }
}

Again, for someone familiar with NHibernate, no magical tricks here. This is where you can choose what NHibernate API you want to use. Her goes the criteria API:

public IQueryResult<Customer> Execute(ISession session)
{
    var criteria = session.CreateCriteria<Customer>().Add(Restrictions.Eq("Name", name));
    return new CriteriaResult<Customer>(criteria);
}

And the LINQ API:

public IQueryResult<Customer> Execute(ISession session)
{
    var query = session.Linq<Customer>().Where(c => c.Name == name);
    return new LinqResult<Customer>(query);
}

IQueryResult<T>

The IQueryResult<T> interface should speak for itself. Once you have performed a query against the repository, you can either get its unique result, or a list of results.

public interface IQueryResult<T>
{
    T UniqueResult();
    IEnumerable<T> List();
}

At this time of writing, you only need three implementations of IQueryResult<T>: QueryResult<T>, CriteriaResult<T> and LinqResult<T>. The implementation you choose to use is free and the decision is made inside the query object, exactly where you choose which query type you want to make.

public class QueryResult<T> : IQueryResult<T>
{
    private readonly IQuery query;

    public QueryResult(IQuery query)
    {
        this.query = query;
    }

    public T UniqueResult()
    {
        return query.UniqueResult<T>();
    }

    public IEnumerable<T> List()
    {
        return query.List<T>();
    }
}

public class CriteriaResult<T> : IQueryResult<T>
{
    private readonly ICriteria criteria;

    public CriteriaResult(ICriteria criteria)
    {
        this.criteria = criteria;
    }

    public T UniqueResult()
    {
        return criteria.UniqueResult<T>();
    }

    public IEnumerable<T> List()
    {
        return criteria.List<T>();
    }
}

public class LinqResult<T> : IQueryResult<T>
{
    private readonly IQueryable<T> query;

    public LinqResult(IQueryable<T> query)
    {
        this.query = query;
    }

    public T UniqueResult()
    {
        return query.First();
    }

    public IEnumerable<T> List()
    {
        return query;
    }
}

Drawbacks of this implementation

Maybe I could split the IRepository in two: one interface for data-modifying methods (as in command - CQS), and a second interface for querying (as in Query - CQS). At this moment I don’t see the need to do this, but it’s an idea I keep in the back of my head.

One drawback to this implementation that I’m aware of: the ISession interface of NHibernate leaks out of the declared interfaces. And you know what: I don’t care. Don’t kid yourself, NHibernate has a huge impact on the design and architecture of the application. I’m on Ayende’s side on this one: don’t try to abstract your DAL, it doesn’t work and you’ll find out too late.

Another design issue as it is implemented right now, the services querying the repository have to know the implementation of the query objects. I’m not sure this is a real drawback. Any ideas are welcome.

Code available for download

Since the whole point of this blog post was showing you some code, I posted it in a small google project. You can download it and run it, it works fine with SQLite in memory, no install necessary, no database required. I have a felling some of you running 64-bit might run into some problems with SQLite. Contact me if this happens.

Posted on Monday, August 16, 2010 8:00 AM .net , design | Back to top


Comments on this post: An alternative repository

# re: An alternative repository
Requesting Gravatar...
Excellent post!
First I should add though is a 'Take' method for doing paging. After all, unbounded result sets are evil ;-).
Left by Jan Van Ryswyck on Aug 15, 2010 8:36 PM

# re: An alternative repository
Requesting Gravatar...
Hehe, this was our previous step.
public interface IRepository<T>
{
void Add(T entity);
void Remove(T entity);
IQueriable<T> Query();
}
Current one is
public interface IRepository : IQueriable<T>
{
void Add(T entity);
void Remove(T entity);
}
Feel nice. And we also use NHibernate.
Left by Mike Chaliy on Aug 15, 2010 8:41 PM

# re: An alternative repository
Requesting Gravatar...
@Jan - hey, commit whatever change you want, you're allowed

@Mike - are you only using NHibernate.Linq? I had some issues with particular queries and had to revert to criteria/hql in those cases. Limiting the repo to IQueryable<T> would not have worked for me.
Left by Michel Grootjans on Aug 15, 2010 10:50 PM

# re: An alternative repository
Requesting Gravatar...
You could try to reduce your dependency with the ISession object by using a container, like StructureMap. It may not solve everything, but it can help.
Left by Pierre-Alain Vigeant on Aug 16, 2010 3:53 PM

# re: An alternative repository
Requesting Gravatar...
I felt the same way about the too many methods thing so I used this:

IEnumerable<T> GetByQuery(Experssion<Func<T, bool>> filter);
with NHibernate.Linq the implementation looks like this

return Session.Linq<T>().Where(filter);

Works great!

Thanks for the post
Left by Sean Mahan on Aug 16, 2010 9:25 PM

# re: An alternative repository
Requesting Gravatar...
You should really look at NCommon (http://github.com/riteshrao/ncommon/)
Left by @seagile on Nov 10, 2010 10:26 PM

Your comment:
 (will show your gravatar)


Copyright © Michel Grootjans | Powered by: GeeksWithBlogs.net