Using Query Specifications with Linq to NHibernate

On my current project, I have been writing a system for staging flat files from a database. A fairly boring system but pretty simple to grasp and implement. Early on I made the decision to do some exploration through my data access strategy. Since my data needs were read only and I needed to work with an entire set of entities which was fairly small, I moved away from the full blown repository pattern I usually use and went for a simple generic gateway class. I wanted to see how far I could go with a fully generic solution. My initial implementation made it all the way to the IoC container and looked something like this.

namespace Foo.Bar.Data
{
    public interface IGateway<T>
    {
        IEnumerable<T> GetCollection();
    }

    public class Gateway<T> : IGateway<T> where T : ILoadable
    {
        protected readonly IUnitOfWorkManager worker;

        public Gateway(IUnitOfWorkManager worker)
        {
            this.worker = worker;
        }

        public virtual IEnumerable<T> GetCollection()
        {
            return worker.On(session =>
                                     session.Query<T>()
                                 );
        }

    }
}

This implementation got me out the door with a release. As requirements changed and the project went on, I discovered some complications with this initial simple design. The first complication was the introduction of a Cash Flow file. The application currently reads data from the database and writes that data to a file. The new requirement was to read data from an Excel spreadsheet and write it to a file. After some initial hand wringing, I discovered that my design didn’t really care about this specific quirk. I was able to easily fit this new CashFlowGateway into the system.

public class CashFlowGateway : IGateway
{
    readonly IFileReader cashFlowFileReader;

    public CashFlowGateway(IFileReader cashFlowFileReader)
    {
        this.cashFlowFileReader = cashFlowFileReader;
    }

    public IEnumerable GetCollection()
    {
        //we may need to do more than this...
        return cashFlowFileReader.Read();
    }
}

The Cash Flow file reader is a simple wrapper around the open source Excel Data Reader project. This solution seemed elegant to me and fit into my generic system. The next feature we uncovered was the need to support more complicated selection criteria. Specifically, we have an entity called Balance in our system, I needed to be able to select balances of a specific type. But I wanted to hold true to the generic nature of my system. So, I modified my gateway interface to accept a IQuery based query.

public interface IGateway<T>
{
    IEnumerable<T> GetCollection();
    IEnumerable<T> GetByQuery(IQuery<T> query);
}

public interface IQuery<T>
{
    IQueryable<T> Reduce(IQueryable<T> items);
}

This allows me to create type specific queries and execute them via a my simple generic gateway. The gateway ends up looking like this.

public class Gateway<T> : IGateway<T> where T : ILoadable
{
    protected readonly IUnitOfWorkManager worker;

    public Gateway(IUnitOfWorkManager worker)
    {
        this.worker = worker;
    }

    public virtual IEnumerable<T> GetCollection()
    {
        return worker.On(session =>
                                 session.Query<T>()
                             );
    }

    public IEnumerable<T> GetByQuery(IQuery<T> query)
    {
        return worker.On(session => 
                            query.Reduce(session.Query<T>())
                            );
    }
}

And an example of a query object.

public class GetBalanceByLoadDate : IQuery<Balance>
{
    readonly DateTime loadDate;

    public GetBalanceByLoadDate(DateTime loadDate)
    {
        this.loadDate = loadDate;
    }

    public IQueryable<Balance> Reduce(IQueryable<Balance> items)
    {
        return items.Where(x => x.LoadDate == loadDate)
                    .Where(x => new[] { "1002000100", "2002000100" }.Contains(x.GLAccountNumber));
    }
}

This is a simple implementation that meets my current needs, but it would be easy to extend into a more complex specification pattern that allowed ANDing and ORing queries together. A nice side benefit of this pattern is that the queries are easily unit testable or applicable to other contexts. They have no direct dependency on NHibernate, which is something that is only possible with version 3 and worth the price of the upgrade. To illustrate this decoupling from NHibernate, let’s consider the Cash Flow gateway after implementing the new query interface.

public class CashFlowGateway : IGateway<CashFlow>
{
    readonly IFileReader<CashFlow> cashFlowFileReader;

    public CashFlowGateway(IFileReader<CashFlow> cashFlowFileReader)
    {
        this.cashFlowFileReader = cashFlowFileReader;
    }

    public IEnumerable<CashFlow> GetCollection()
    {
        //we may need to do more than this...
        return cashFlowFileReader.Read();
    }

    public IEnumerable<CashFlow> GetByQuery(IQuery<CashFlow> query)
    {
        return query.Reduce(GetCollection().AsQueryable());
    }
}

Now that is hot.

Update: I forgot to mention this whole train of thought was inspired by Liam Mclennan’s post on using query classes with nhibernate.

Follow me on Mastodon!