Encapsulating repository queries

I've moved away from putting query (eg GetCondition / FindCondition) o n my repositories. Now I create queries objects for each would be method. Why?
February 19 2013

I’ve recently started structuring my repositories a little differently.  I’ve moved away from creating a method per query on my repository (and interface) and instead providing a query parameter in my Get  / Find methods (and their data paging variants).  Here’s how a repository would look using the previous method.

public interface IRepository<T> where T : class, IDomainObject
{
    T GetById(int id);
    IEnumerable<T> GetAll();
    void Save(T entity);
    void Delete(T entity);
}

public interface IUserRepository : IRepository<User>
{
    bool Exists(string username, int id);
    bool EmailExists(string email, int id); 
}

public class UserRepository : AbstractRepository<User>. IUserRepository
{
    public UserRepository(ISessionManager sessionManager) : base(sessionManager) { }
    
    public bool Exists(string username, int id)
    {
        return Query.Any(u => u.Username == username && u.Id != id);
    }
    
    public bool Exists(string email, int id)
    {
        return Query.Any(u => u.Email == email && u.Id != id);
    }
}

 

Seems pretty typical (I’m using Nhibernate and Query is a IQueryable of Session property on the base repo). Ecapsulating my queries in their own classes I get a the following code to do the same thing

public interface IQueryFilter<T> where T : class, IDomainObject
{
    Expression<Func<T, bool>> GetFilter();
}

public class ExistingUserEmailAddressFilter : IQueryFilter<User>
{
    public string Email { get; set; }

    public int UserId { get; set; }

    public Expression<Func<User, bool>> GetFilter()
    {
        return user => user.Email == Email && user.Id != UserId;
    }
}

public class ExistingUserEmailAddressFilter : IQueryFilter<User>
{
    public string Username { get; set; }

    public int UserId { get; set; }

    public Expression<Func<User, bool>> GetFilter()
    {
        return user => user.Username = Username && user.Id != UserId;
    }
}

The IQueryFilter concrete classes live in my domain layer.

My query class contains the parameters it needs to build the query.  I also use PredicateBuilder – a cool helper class that is great when you need to build up more complex queries, such as when the query is conditional on values of the properties of your query object. e.g.  A query for and advanced search screen.

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                     Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
                                                   Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
    }
}
 

Then, in my AbstractRepository I have the methods for using the filter via IQueryable:

public AbstractRepository<T> where T : class, IDomainObject
{
    // Other stuff removed...
    
    public IEnumerable<T> GetByFilter(IQueryFilter<T> filter)
    {
        return Query.Where(filter.GetFilter()).ToList();
    }
    
    protected IQueryable<T> Query
    {
        get { return Session.Query<T>(); }
    }
}

Now whenever I need a new query for my database I inherit a class from IQueryFilter. 

This might seem like more work and perhaps it is.  I’ve gone this way because it’s been done before and because I can easily test my query logic.  Note the simple query above to search for an existing email address.  To test this I created a few simple unit tests (2 infact)

[TestFixture]
public class ExistingUserEmailAddressFilterTests
{
    protected ExistingUserEmailAddressFilter filter;

    [SetUp]
    public void SetUp()
    {
        filter = new ExistingUserEmailAddressFilter();
    }

    [Test]
    public void Given_the_user_already_has_this_email_address_the_email_doesnt_already_exist()
    {
        var user = new List<User>
            {
                new User {EmailAddress = "test@test.com"}.WithId(1)
            };

        filter.EmailAddress = "test@test.com";
        filter.UserId = 1;
        
        Assert.IsFalse(user.Any(filter.GetFilter().Compile()));
    }

    [Test]
    public void Given_another_user_already_has_this_email_address_the_email_already_exists()
    {
        var user = new List<User>
            {
                new User {EmailAddress = "test@test.com"}.WithId(1)
            };

        filter.EmailAddress = "test@test.com";
        filter.UserId = 2;

        Assert.IsTrue(user.Any(filter.GetFilter().Compile()));
    }
}

WithId is a test helper allowing me to set the protected (setter) Id property so I can test.

public static class TestHelpers
{
    public static TModel WithId<TModel>(this TModel model, int id) where TModel : IHasId
    {
        var idProp = model.GetType().GetProperty("Id", BindingFlags.Instance | BindingFlags.Public);

        idProp.SetValue(model, id);

        return model;
    }
}

The effort to test these queries if I put them on the repository would be rather difficult.  Admittedly this isn’t perfect, because the Linq provider for your ORM may generate different (or not be able to generate) SQL for your query. Still, this problem exists if I do the query in the Repository.

Am I mad?  Thoughts?

Post a comment

comments powered by Disqus