With the completion of LINQ to NHibernate for NHibernate 3.0, we can easily implement the specification pattern. In this recipe, I'll show you how to set up and use the specification pattern with the NHibernate repository.
Download the LinqSpecs library from http://linqspecs.codeplex.com. Copy LinqSpecs.dll
from the Downloads
folder to your solution's libs
folder.
Complete the Setting up an NHibernate Repository recipe.
Eg.Core.Data
and Eg.Core.Data.Impl
, add a reference to LinqSpecs.dll
.IRepository
interface.IEnumerable<T> FindAll(Specification<T> specification); T FindOne(Specification<T> specification);
NHibernateRepository
:public IEnumerable<T> FindAll(Specification<T> specification) { var query = GetQuery(specification); return Transact(() => query.ToList()); } public T FindOne(Specification<T> specification) { var query = GetQuery(specification); return Transact(() => query.SingleOrDefault()); } private IQueryable<T> GetQuery( Specification<T> specification) { return session.Query<T>() .Where(specification.IsSatisfiedBy()); }
Eg.Core.Data.Queries
:public class MoviesDirectedBy : Specification<Movie> { private readonly string _director; public MoviesDirectedBy(string director) { _director = director; } public override Expression<Func<Movie, bool>> IsSatisfiedBy() { return m => m.Director == _director; } }
Eg.Core.Data.Queries
, using the following code:public class MoviesStarring : Specification<Movie> { private readonly string _actor; public MoviesStarring(string actor) { _actor = actor; } public override Expression<Func<Movie, bool>> IsSatisfiedBy() { return m => m.Actors.Any(a => a.Actor == _actor); } }
The specification pattern allows us to separate the process of selecting objects from the concern of which objects to select. The repository handles selecting objects, while the specification objects are concerned only with the objects that satisfy their requirements.
In our specification objects, the IsSatisfiedBy
method of the specification objects returns a LINQ expression to determine which objects to select.
In the repository, we get an IQueryable
from the session, pass this LINQ expression to the Where
method, and execute the LINQ query. Only the objects that satisfy the specification will be returned.
For a detailed explanation of the specification pattern, check out http://martinfowler.com/apsupp/spec.pdf.
To use our new specifications with the repository, use the following code:
var movies = repository.FindAll( new MoviesDirectedBy("Stephen Spielberg"));
We can also combine specifications to build more complex queries. For example, the following code will find all movies directed by Steven Speilberg starring Harrison Ford:
var movies = repository.FindAll( new MoviesDirectedBy("Steven Spielberg") & new MoviesStarring("Harrison Ford"));
This may result in expression trees that NHibernate is unable to parse. Be sure to test each combination.