In the previous to parts to this series (here and here) I showed the basic infrastructure framework for using Linq-based specifications with a flexible repository interface. In this article I dig deeper into the Expression based find functionality and it's implementation.
The Goal
Specification based query support on the repository is a great feature for encapsulation of any query logic. My major problem with this approach was the creation of trivial specifications on a per-domain-object basis. This would litter the code with specifications that had very little functionality contained within them. Generally this is not a bad thing but, in this instance, for the sake of maintainabilty and simplicity I wanted a to be able to perform ad-hoc queries.
The solution was to make my repository able to accept queries like :
var repository = new Repository<Person>(session);
var people = repository
.FindAll(p => p.Name == "steve");
The AdHoc specification
Since I have already made the decision that my MatchingCriteria are of type Expression, the AdHoc specification is trivial - but very cool. The implementation looks like :
public class AdHoc<T> : Specification<T> where T : IEntity
{
private readonly Expression<Func<T, bool>> expression;
public AdHoc(Expression<Func<T, bool>> expression)
{
this.expression = expression;
}
public override Expression<Func<T, bool>> MatchingCriteria
{
get { return expression; }
}
}
Simplifying the repository using the AdHoc specification
By utilising this simple but powerful specification class we can simplify the api to our IRepository. We are now able to add a FindOne and FindAll overload that accept Linq based queries in an AdHoc fashion. The implementation is as simple as :
public IQueryable<T> FindAll(Expression<Func<T, bool>> expression)
{
return FindAll(new AdHoc<T>(expression));
}
This functionality is able to be consumed in the following fashion (satisfying our goal for this task) :
var people = repository.FindAll(p => p.Name == "steve");
Conclusion
In combination with the flexibility built into the IRepository interface we now have a basis for a data access layer with maximum flexibility. Sql queries are created at execution time to reflect the criteria that we have applied through domain specifications - be they concrete or ad-hoc.