Query Objects with the Repository Pattern

Nate Kohari (whose primate brain is far too small to comprehend this post [inside joke, he’s actually really sharp]) was asking on Twitter today about how to structure his repositories: Per aggregate root, Per entity, or just one repository for everything?

The two suggestions that rose to the top were the per-aggregate and one repository approaches. Jeremy and I use a the one repository approach at work and it’s working well for us so far.  The was one compelling argument for the per-aggregate repository and that is that you could encapsulate your aggregate-specific queries inside your aggregate-specific repositories.

Thinking about this a little more, I felt it had a little smell to it, namely the potential for business logic (also known as ‘where’ clauses) to creep into the repository which would be bad. Perhaps a better alternative would be to encapsulate the specific logic of a given query into an object. You could then have this object produce something that the repository could (blindly, decoupled) use to query on.

This approach allows you to maintain the one repository approach, yet still have encapsulated domain-specific queries. Plus, you can test your queries independently of the repository which is a huge benefit.

Creating and Passing Around Expression Trees

What might the query object produce that the repository could blindly use?  In the .NET 3.5 world, you could use expression trees!  These come in quite handy and play well with LINQ-style (IQueryable) behavior of Linq2NHibernate, Linq2Objects, and Linq2JustAboutAnythingThatInvolvesAListOfThings.

Producing an expression tree is actually quite simple:  Have a method or property that returns Expression<Func<T,bool>>. Yep. That’s it. We can all go home now.  In case you actually wanted to see some code, here’s a simple, extremely naive example of what I mean. Let’s say we wanted to find all our customers that have significant sales with our company, but whose discount is really low (for some reason).  We’d like to find these customers and give them a better discount since they do so much business with us. Your query probably (wouldn’t) look something like this:

public class TopCustomersWithLowDiscountQuery
{
    public bool IncludePreferred { get; set; }
    public decimal DiscountThreshold { get; set; }
    public int SalesThreshold { get; set; }

    public Expression<Func<Customer, bool>> AsExpression()
    {
        return (c =>
                c.Preferred == IncludePreferred
                && c.Discount < DiscountThreshold
                && c.AnnualSales > SalesThreshold);
    }
}

Using this query object, we can still get all the benefits of delayed execution that LINQ/IQueryable provides us without forcing us to sprinkle little surprises of business logic everywhere in LINQ queries.

Using the Expression Tree to Query

To actually use the expression tree, you can call the Where() method on any IEnumerable<T> or IQueryable<T>.  Consider this silly/contrived example:

var query = new TopCustomersWithLowDiscountQuery
{
    IncludePreferred = true,
    DiscountThreshold = 3,
    SalesThreshold = 100000
};

var customerList = new List<Customer>
{
    new Customer {Id = 1, Preferred = true},
    new Customer {Id = 2, Preferred = false},
    new Customer {Id = 3, Preferred = true}
};

var filteredCustomers = 
    customerList
        .AsQueryable()
        .Where(query.AsExpression());

 

Once GetEnumerator() is called on filteredCustomers, the magic happens and you’ll end up with an IEnumerable<T> that has only 2 elements in id (ID=1, and ID=3).

Full Code Example

Here’s the full code of the test fixture I was using for these examples. Yes, I know it’s not very realistic and there are lots of potential problems with the logic in the query object, but the point was to illustrate how you might go about encapsulating LINQ where clauses, so please go easy on me :)

[TestFixture]
    public class TopCustomersWithLowDiscountQueryTester
    {
        [Test]
        public void preferred_customers_should_be_selected_when_IncludePreferred_is_true()
        {
            _customers.AddRange(new[]
            {
                new Customer {Id = 1, Preferred = true},
                new Customer {Id = 2, Preferred = false},
                new Customer {Id = 3, Preferred = true}
            });

            Results.Count().ShouldEqual(2);
            Results.ElementAt(0).Id.ShouldEqual(1);
            Results.ElementAt(1).Id.ShouldEqual(3);
        }

        [Test]
        public void high_discount_customers_should_not_be_selected()
        {
            _query.IncludePreferred = false;

            var high = 15m;
            var low = 1m;

            _customers.AddRange(new[]
            {
                new Customer {Id = 1, Discount = high},
                new Customer {Id = 2, Discount = low},
                new Customer {Id = 3, Discount = high}
            });

            Results.Count().ShouldEqual(1);
            Results.ElementAt(0).Id.ShouldEqual(2);
        }

        [SetUp]
        public void SetUp()
        {
            _query = new TopCustomersWithLowDiscountQuery
            {
                IncludePreferred = true,
                DiscountThreshold = 3,
                SalesThreshold = 100000
            };

            _customers = new List<Customer>();
            _resultsCached = null;
        }

        private TopCustomersWithLowDiscountQuery _query;
        private List<Customer> _customers;
        private IEnumerable<Customer> _resultsCached;

        public IEnumerable<Customer> Results
        {
            get { return _resultsCached ?? _customers.AsQueryable().Where(_query.AsExpression()); }
        }
    }

    public static class SpecificationExtensions
    {
        public static void ShouldEqual(this object actual, object expected)
        {
            Assert.AreEqual(actual, expected);
        }
    }

 

Coming up…

Stay tuned: I’m going to do a follow-on post on how you can make the query object code a little more elegant, as well as chain them together with AndAlso and OrElse expressions.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Chad Myers

Chad Myers is the Director of Development for Dovetail Software, in Austin, TX, where he leads a premiere software team building complex enterprise software products. Chad is a .NET software developer specializing in enterprise software designs and architectures. He has over 12 years of software development experience and a proven track record of Agile, test-driven project leadership using both Microsoft and open source tools. He is a community leader who speaks at the Austin .NET User's Group, the ADNUG Code Camp, and participates in various development communities and open source projects.
This entry was posted in LINQ, Repositories. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://anydiem.com Sean Scally

    Your code examples look like they’ve been mangled with “BLOCKED EXPRESSION”… either that, or you have some interesting naming standards I am not familiar with ;)

  • http://chadmyers.lostechies.com Chad Myers

    @Sean

    Nice catch. That would be Community Server protecting you from seeing the letters s, e, and x next to each other and fainting.

    I’ll see what I can do about getting these fixed somehow. Sorry for the inconvenience.

  • http://anydiem.com Sean Scally

    No worries, I liked the topic of the post since this is something I’ve been struggling with; I’ve been experimenting with separating queries into their own classes thus far to get them under test, but been unhappy with the overall structure. I also haven’t used the approach on anything complicated enough to require multiple aggregates yet, so this is a timely subject for me.

  • krishnan

    Is there a general code pattern here that is not dependent on LINQ ? I would like to use the repository pattern so that I don’t need to worry about whether the data is fetched from the DB or JMX( Java ).