Query object pattern

You may not find much mention of query object pattern elsewhere. This pattern is very abstract and most people have their own interpretation and implementation of it. The one I am going to present here is an implementation that I have used successfully. At the heart of query object pattern is the concept that everything of a query (unlike only the filtering criteria in specification pattern) is abstracted away behind an interface so that it can be reused. The simplest form of this interface would look as follows:

public interface IQuery<T> where T : EntityBase<T>
{
  IEnumerable<T> Run(IQueryable<T> queryable);
}

IQuery<T> is a generic interface where T stands for the entity type which is being queried. The Run method on the interface is what runs the query and returns an IEnumerable<T> as a result. Now, unlike specification pattern, the Run method takes in an IQueryable<T> as input. This is where query object makes the difference. Because you have access to an IQueryable, you can write more specific and complete queries than just specifying the filtering criteria. Let's build a query object for employees living in London:

public class EmployeesLivingIn : IQuery<Employee>
{
  public IEnumerable<Employee> Run(IQueryable<Employee> employees)
  {
    return (from e in employees
            where e.ResidentialAddress.City == "London"
            select e).ToList();
  }
}

We have used LINQ syntax here to implement our query. Since we had an IQueryable<T> to work with, we could have written the query using lambda expression as well. That is one big advantage of this implementation of query object. There are times when you would want to use LINQ syntax only, for instance, when using theta joins. There would be other times when a query written using lambda expressions is more ideal. You are free to choose whatever way you want to go. Let's tidy up the previous query object a bit and then we would look into how to run this query from domain services layer. String literal London is hardcoded in the previous query, which we can externalize as we did with previous implementations. Following is how the query class looks after that change:

public class EmployeesLivingIn : IQuery<Employee>
{
  private readonly string city;

  public EmployeesLivingIn(string city)
  {
    this.city = city;
  }

  public IEnumerable<Employee> Run(IQueryable<Employee> employees)
  {
    return (from e in employees
    where e.ResidentialAddress.City == city select e)
           .ToList();
  }
}

Nothing new in the preceding code. How do we use this query object? Instantiating the EmployeesLivingIn class is not difficult but passing IQueryable<Employee> can be tricky. In absence of a query object, the preceding query could have been written as follows:

from e in session.Query<Employee>()
where e.ResidentialAddress.City == city
select e

The IQueryable<Employee> parameter that is passed into Run method is in reality what is returned by ISession.Query<Employee>. We need some component that can pass this value into the Run method. A domain service class cannot be that component because it has no knowledge of ISession or any other NHibernate type. For now, let's say that we add a QueryRunner class in the Persistence project. Definition of a QueryRunner class can be as simple as follows:

public class QueryRunner
{
  private readonly ISession session;

  public QueryRunner(ISession session)
  {
    this.session = session;
  }

  public IEnumerable<T> Run<T>(IQuery<T> query) where T : EntityBase<T>
  {
    return query.Run(session.Query<T>());
  }
}

This class takes a dependency on ISession and also has a method Run which takes in an instance of IQuery<T>. It then executes the Run method on the IQuery object by passing ISession.Query<T> as a parameter. That is simple. Now how do we make this class available to domain services? Well, we would use the design principle we learnt in the last chapter. Let's turn the tables around and say that domain service needs to execute a query. It has an instance of IQuery<T> and needs a capability to run the query contained in that query object. We know that this capability is offered by QueryRunner, so let's add the following interface into domain services project to make use of the capability offered by QueryRunner:

namespace DomainService.Capabilities
{
  public interface IRunQuery
  {
    IEnumerable<T> Run<T>(IQuery<T> query) where T : EntityBase<T>;
  }
}

This interface abstracts the behavior of QueryRunner and offers the capability that domain service needs. All that domain service needs to do at this point is to declare dependency on the IRunQuery interface. Inside domain service, we can choose to create an instance of query object that we need and pass it to the Run method on the IRunQuery interface.

Extending the query object pattern

The query object we saw earlier implemented a simple query involving a single entity. What if you want to write a query that involves more than one entity? If you are using lambda expressions then nothing changes. For instance, the query to fetch employees who have opted for season ticket loan can be written using lambda expression as follows:

public IEnumerable<Employee> Run(IQueryable<Employee> employees)
{
  return (from e in employees
          where e.Benefits.Any(b => b is SeasonTicketLoan)
          select e).ToList();
}

Note

Note that preceding code snippet only shows the Run method on query object to save space.

The problem with the preceding query is that it generates the same complex and inefficient SQL that specification pattern generated. Actually, this query is no different than the one we wrote for specification pattern. A better query using LINQ syntax can be written as follows:

return (from e in employees
join s in seasonTicketLoans on e equals  s.Employee
select e).ToList();

Preceding query when run generates the following SQL which uses an inner join involving only the Employee, Benefit, and SeasonTicketLoan tables:

SELECT employee0_.Id            AS Id0_, 
       employee0_.Firstname     AS Firstname0_, 
       employee0_.Lastname      AS Lastname0_, 
       employee0_.EmailAddress  AS EmailAdd5_0_, 
       employee0_.DateOfBirth   AS DateOfBi6_0_, 
       employee0_.DateOfJoining AS DateOfJo7_0_, 
       employee0_.IsAdmin       AS IsAdmin0_, 
       employee0_.Password      AS Password0_ 
FROM   Employee employee0_, 
       SeasonTicketLoan seasontick1_ 
       INNER JOIN Benefit seasontick1_1_ 
               ON seasontick1_.Id = seasontick1_1_.Id 
WHERE  seasontick1_1_.Employee_Id = employee0_.Id 

Encapsulating this LINQ query inside a query object would require us to make changes to the query object. This query needs access to two IQueryable instances, specifically, IQueryable<Employee> and IQueryable<SeasonTicketLoan>. Our current query object infrastructure only supports a single IQueryable instance. Let's work out how we can extend our query objects to support this scenario. First of all, we would need an implementation of the Run method as follows:

public IEnumerable<Employee> Run(IQueryable<Employee> employees, IQueryable<SeasonTicketLoan> seasonTicketLoans)
{
  return (from e in employees
          join s in seasonTicketLoans on e equals  s.Employee
          select e).ToList();
}

Looking at the preceding code, we can say that we would need a query interface as follows:

public interface IBenefitQuery<T1, in T2>
where T1 : EntityBase<T1>
where T2 : Benefit
{
  IEnumerable<T1> Run(IQueryable<T1> queryable1, IQueryable<T2> queryable2);
}

This interface is slightly different than the previous more generic interface. Parameter T2 is constrained to the Benefit type in here. This is because of limitation of C# generics. I could have defined an interface as follows but that would just not work:

public interface IQuery<T1, in T2>
where T1 : EntityBase<T1>
where T2 : EntityBase<T2>
{
  IEnumerable<T1> Run(IQueryable<T1> queryable1, IQueryable<T2> queryable2);
}

Preceding interface needs to have a class that directly inherits from EntityBase<T2> to be passed in second parameter. Since the SeasonTicketLoan class inherits from Benefit, passing a SeasonTicketLoan instance into the above interface does not work. Hence we need to use the implementation where T2 is constrained to Benefit.

Now that we have our new query interface ready, we would need to extend the IRunQuery interface to work with the new query. As shown in the following code snippet, we would add a new Run method on IRunQuery that would take IBenefitQuery<T1, T2> as input:

public interface IRunQuery
{
  IEnumerable<T> Run<T>(IQuery<T> query) where T : EntityBase<T>;
  IEnumerable<T1> Run<T1, T2>(IBenefitQuery<T1, T2> query)
  where T1 : EntityBase<T1>
  where T2 : Benefit;
}

Following is the implementation of this new method, which is very similar to the previous method:

public IEnumerable<T1> Run<T1, T2>(IBenefitQuery<T1, T2> query) where T1 : EntityBase<T1> where T2 : Benefit
{
  return query.Run(session.Query<T1>(), session.Query<T2>());
}

And that is it. We have got a working and extended example of query object. You can scale this to any level you want really. If you need to write a query using LINQ syntax that needs access to three or more IQueryable instances, then you just add appropriate query interfaces, make required changes on the IRunQuery interface, and you are done.

The limitations of the query object pattern

Query object pattern is one of the easiest and cleanest querying pattern that I have come across so far. There is not much in terms of limitations or downsides except for that fact that there is no way of composing multiple query objects to form a composite query. Composition acts on the WHERE clause of a query and the only way to be able to combine two queries is by refactoring out the WHERE clause in its own class. This is what specification pattern offers. Query objects wrap the entire query and hence combining two or more queries together is not possible. But given the other advantages and simplicity offered by query object pattern, I personally tend to push back on the need to be able to chain multiple queries together. If the project you are working on is too complex or big and there is a significant reuse of queries, then it would make sense to use specification pattern and make full use of the composition benefits offered by it.

That brings us to the end of our quick tour of specification and query object patterns. Though I named the chapter as "advanced data access patterns", I honestly do not find these patterns to be advanced. I call them advanced as they involve bit of learning curve for people new to NHibernate. But now it should be clear to you that they are really simple to understand and implement. Rather, I would not want to keep calling them advanced and keep you from utilizing them in your projects under the name of "simplicity". These patterns are indeed simple, go use them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset