-
Notifications
You must be signed in to change notification settings - Fork 27
Introduction
In order to define our first specification named CustomerFromCountrySpec
we need to inherit from Specification<T>
:
public abstract class Specification<T>
{
public abstract Expression<Func<T, bool>> ToExpression();
}
So this is our implementation:
using LinqSpecs;
public enum Country { Argentina, France, Italia, ... }
public class CustomerFromCountrySpec : Specification<Customer>
{
public Country Country { get; set; }
public CustomerFromCountrySpec(Country country)
{
Country = country;
}
public override Expression<Func<Customer, bool>> ToExpression()
{
return c => c.Country == Country;
}
}
Simple as is, to use this class, your repository or DAO should implement these kind of methods:
public IEnumerable<T> Find(Specification<T> specification)
{
return [a queryable source].Where(specification).ToList();
}
public int Count(Specification<T> specification)
{
return [a queryable source].Count(specification);
}
The usage is very simple:
var spec = new CustomerFromCountrySpec(Country.Argentina);
var customersFromArgentina = customerRepository.Find(spec);
An alternative way of exposing specifications is with a static class:
public static class CustomerSpecs
{
public static Specification<Customer> FromCountry(Country country)
{
return new CustomerFromCountrySpec(country);
}
public static Specification<Customer> EligibleForDiscount(decimal discount)
{
return new AdHocSpecification<Customer>(
c => c.IsPreferred && !c.HasDebt &&
c.LastPurchaseDate > DateTime.Today.AddDays(-30));
}
}
Usage:
customerRepository.Find(
CustomerSpecs.FromCountry(argentina) &&
CustomerSpecs.EligibleForDiscount(3223));
In classic implementation of the specification pattern by Eric Evans and Martin Fowler a Specification
class has one method called IsSatisfiedBy
that returns a boolean value:
bool IsSatisfiedBy(T candidate)
This method checks whether the candidate object matches some criteria. Such approach is simple and convenient when a query is executed in memory, but it can't be used in queries to a database.
In LinqSpecs all specifications produce an expression tree:
Expression<Func<T, bool>> ToExpression()
An expression can be translated to a piece of SQL query, or MongoDb query, or anything. For example, you can pass expression as a filter to IQueryable<T>
data source from Entity Framework or NHibernate, your filter will be processed on the server side.