Implementing Domain Queries
My current Repository interface looks something like this:
public interface IRepository{T FindOne<T>(int id);
T FindBy<T>(Expression<Func<T, bool>> expression);
IEnumerable<T> FindAllBy<T>(Expression<Func<T, bool>> expression);
IEnumerable<T> FindAll<T>();T FindOneBy<T>(Expression<Func<T, bool>> expression);
void Save<T>(T target);
void Update<T>(T target);
void SaveOrUpdate<T>(T target);
void Delete<T>(T target);
IEnumerable<T> Query<T>(Expression<Func<T, bool>> expression);
}It’s modeled after the Repository class that Jeremy and Chad put in the early versions in Fluent NHibernate (now removed). It relies almost completely on Linq expressions for where statements, which make queries very easy to write and understand. Like this one:
_repository.FindBy<User>(user => user.Enabled == true);This works great for small where statements, but when you need more control over the query, the current implementation doesn’t really allow you to fully take advantage of NHibernate or the Linq provider. Another downside is your code is not really DRY when you have small where statements littered everywhere.
So to solve these problems I implemented a very simple Domain Query pattern. Domain Queries are classes that encapsulate complicated or common queries into self contained objects that can be reused throughout your application. I had some very simple goals for this implementation:
- Keep it simple to access from the executing code.
- I wanted full access to all of Nhibernate’s functionality when I needed it.
- Keep it testable.
So Let’s start with the end and then show the beginning. I wanted it really simple to access a domain query. This is what I had in mind for executing a domain query:
_repostiory.FindAll(Queries.GetAllActiveUsers());To get there I started with a really simple interface. Because there were two basic queries I wanted to execute: return a single object or a collection of an object (I don’t have a use case for things like GetScalar yet) I need two methods on my interface:
public interface IDomainQuery<T>{T ExecuteUniqueResult(ISession session);IEnumerable<T> ExecuteList(ISession session);}I then added the following methods to my Repository:
public T FindOne<T>(IDomainQuery<T> query)
{return query.ExecuteUniqueResult(Session);
}public IEnumerable<T> Query<T>(IDomainQuery<T> query)
{return query.ExecuteList(Session);
}
My repository is responsible for knowing how to access the session. Passing the ISession object to the domain query helps me meet two of my goals, I have full access to everything on the session and it is easy to test my domain queries since they are not responsible for managing the Unit of Work. I can create different UoW contexts in my application and my integration tests.
To create the domain queries, I use the Template pattern to abstract the IDomainQuery aspects and allow the concrete classes only deal with the query construction. I have two abstract classes right now: LinqDomainQuery and CriteriaDomainQuery. It’s pretty obvious what each of these do. The CriteriaDomainQuery utilizes the DetachedCriteria functionality. The LinqDomainQuery obviously utilizes the Linq provider. I could easily create an HQLDomainQuery and an SQLDomainQuery as well, but following YAGNI I don’t need them yet. Here is the LinqDomainQuery:
public abstract class LinqDomainQuery<TResult> : IDomainQuery<TResult>{protected abstract IQueryable<TResult> GetQuery(ISession session);public TResult ExecuteUniqueResult(ISession session)
{return GetQuery(session).SingleOrDefault();
}public IEnumerable<TResult> ExecuteList(ISession session)
{return GetQuery(session).ToList();
}}Some important things to Note: Notice that I call the SingleOrDefault() and ToList() methods. This keeps me from having deferred execution bugs crop up. I had some issues during testing because I was closing the session faster than I was actually executing the query. Doing that here prevented that from happening. Also notice that it the generic type is TResult. With the Select statement, you can perform projections very easily. I can return DTO data directly from the query, giving me precisely the SQL statement I need and no need to map between complicated Entities to flattened DTOS.
Here is an example of selecting a DTO from a Linq Query:
public class LoadBuilderDataQuery : LinqDomainQuery<LoadBuilderData>{private readonly int _page;private readonly int _rows;public LoadBuilderDataQuery(int page, int rows){_page = page;_rows = rows;}protected override IQueryable<LoadBuilderData> GetQuery(ISession session){return
session.Linq<OrderTicket>().Skip(_page*_rows).Take(_rows).Select(t => new LoadBuilderData()
{LoadId = t.Id,PromisedDate = t.PromisedDate,Address = t.Destination.Address1,City = t.Destination.City,State = t.Destination.State,Zip = t.Destination.Zip,CustomerName = t.Order.Customer.CustomerName,StoreNumber = t.FulfillingStore.StoreNumber,OrderNumber = t.Order.OrderNumber,OrderSuffix = t.OrderSuffix});
I have fully tested this query as well. Here is an example, I’m leaving some of the setup and UoW handling out for brevity, you can get the idea.
//do some setup in the base class
public class when_retrieving_first_result_set : LoadBuilderQueryTests{//unit of work helper opens and closes the session for me
private Because of =
() => result = UnitOfWorkHelper.Use(session => new LoadBuilderDataQuery(0, 10).ExecuteList(session));
private It should_return_10_rows = () => result.ToArray().Length.Should().Equal(10);
private It should_return_LoadBuilderData =
() => result.ToArray()[0].Should().Be.OfType(typeof (LoadBuilderData));
}Here is the SQL Query generated:
Now back to the beginning. To reduce some of the complexity of calling these classes, I wrapped then in static methods to make them easier to call:
public class Queries{public static IDomainQuery<LoadBuilderData> GetLoadBuilderData(int page, int row){return new LoadBuilderDataQuery([page, row);}}This is just some syntactic sugar, but small things like that keep your application easy to read and understand.
This is a very simple way to implement domain queries. You can go a lot further with this. Tim Scott took my simple approach and really took the training wheels off. If I can’t get him to post about it it will.