Until Linq2NHibernate is ready for production, here is a method for keeping the specification logic encapsulated even if the domain code is different than the database query code.
Here is the specification interface, where I added the EnsureThatWillBeSatisfiedBy method:
public interface ISpecification<T>
{
bool IsSatisfiedBy(T recipient);
ICriteria EnsureThatWillBeSatisfiedBy(ICriteria criteria);
}
I am also using an extension for ICriteria:
public static class CriteriaExtension
{
public static ICriteria EnsureItWillSatisfy<T>(this ICriteria criteria, ISpecification<T> specificationToSatisfy)
{
return specificationToSatisfy.EnsureThatWillBeSatisfiedBy(criteria);
}
}
The specification looks like this:
class ProductAvailableSpecification: ISpecification<Product>
{
public bool IsSatisfiedBy(Product product)
{
return product.ActiveYN
&& product.ExpirationDate > DateTimeService.Now
&& product.RemainingQuantity > 0;
}
public ICriteria EnsureThatWillBeSatisfiedBy(ICriteria criteria)
{
return criteria
.Add(Expression.Eq("IsActiveYN", 1))
.Add(Expression.Gt("ExpirationDate", DateTimeService.Now))
.Add(Expression.Gt("RemainingQuantity", 0));
}
}
Here is how it can be used in the domain:
ProductAvailableSpecification _productAvailableSpecification = new ProductAvailableSpecification();
public bool CheckFirstProduct()
{
Product product = _products[0];
if (_productAvailableSpecification.IsSatisfiedBy(product))
return true;
return false;
}
and here is how it can be used in the repository to load only products that are satisfying the specification:
ProductAvailableSpecification _productAvailableSpecification = new ProductAvailableSpecification();
public IList<Product> GetProductsSatisfyingCriteria()
{
return Repository<Product>.CreateCriteria()
.EnsureItWillSatisfy(_productAvailableSpecification)
.List<Product>();
}
The specification logic is still written twice, but it is encapsulated in the same class and it is easy to maintain.