lunes, 29 de junio de 2015

Specification Pattern en .NET

Esta entrada es para que no se me olvide. Siempre que trabajo con árboles de expresiones me paso la primera hora mirando la documentación tratando de recordar las 2 malditas líneas que te permiten combinar 2 expresiones Lambda.


public interface ISpecification<T> where T : class
{
    ISpecification<T> AND(ISpecification<T> s);
 
    ISpecification<T> AND(Expression<Func<T, bool>> criteria);
 
    ISpecification<T> OR(ISpecification<T> s);
 
    ISpecification<T> OR(Expression<Func<T, bool>> criteria);
 
    Func<T, bool> IsSatisfied();
 
    Expression<Func<T, bool>> matchingCriteria();
}
public sealed class Specification<T> : ISpecification<T> where T : class
{
    private Expression<Func<T, bool>> _matchingCriteria;
 
    public Specification(Expression<Func<T, bool>> matchingCriteria)
    {
        if (matchingCriteria == null) { throw new ArgumentNullException("matchingCriteria"); }
        _matchingCriteria = matchingCriteria;
    }
 
    public Func<T, bool> IsSatisfied()
    {
        return _matchingCriteria.Compile();
    }
 
    public Expression<Func<T, bool>> matchingCriteria()
    {
        return _matchingCriteria;
    }
 
    public ISpecification<T> AND(ISpecification<T> criteria)
    {
        var invokedExpr = Expression.Invoke(criteria.matchingCriteria(), _matchingCriteria.Parameters.Cast<Expression>());
 
        return new Specification<T>(Expression.Lambda<Func<T, bool>>(Expression.AndAlso(_matchingCriteria.Body, invokedExpr), _matchingCriteria.Parameters));
    }
 
    public ISpecification<T> AND(Expression<Func<T, bool>> criteria)
    {
        var invokedExpr = Expression.Invoke(criteria, _matchingCriteria.Parameters.Cast<Expression>());
 
        return new Specification<T>(Expression.Lambda<Func<T, bool>>(Expression.AndAlso(_matchingCriteria.Body, invokedExpr), _matchingCriteria.Parameters));
    }
 
    public ISpecification<T> OR(ISpecification<T> s)
    {
        var invokedExpr = Expression.Invoke(s.matchingCriteria(), _matchingCriteria.Parameters.Cast<Expression>());
 
        return new Specification<T>(Expression.Lambda<Func<T, bool>>(Expression.OrElse(_matchingCriteria.Body, invokedExpr), _matchingCriteria.Parameters));
    }
 
    public ISpecification<T> OR(Expression<Func<T, bool>> criteria)
    {
        var invokedExpr = Expression.Invoke(criteria, _matchingCriteria.Parameters.Cast<Expression>());
 
        return new Specification<T>(Expression.Lambda<Func<T, bool>>(Expression.OrElse(_matchingCriteria.Body, invokedExpr), _matchingCriteria.Parameters));
    }
}
internal class Program
  {
      private static void Main(string[] args)
      {
          List<persona> listaPers = new List<persona>();
          listaPers.Add(new persona { nombre = "jose", edad = 17, localidad = "caceres" });
          listaPers.Add(new persona { nombre = "juan", edad = 19, localidad = "caceres" });
          listaPers.Add(new persona { nombre = "manolo", edad = 19, localidad = "badajoz" });
          listaPers.Add(new persona { nombre = "alfonso", edad = 17, localidad = "don benito" });
          listaPers.Add(new persona { nombre = "pepe", edad = 34, localidad = "navalmoral" });
 
          ISpecification<persona> CacereñosMenoresde18 = new Specification<persona>(p => p.edad < 18).AND(p => p.localidad == "caceres");
          ISpecification<persona> PacensesMayoresDe18 = new Specification<persona>(p => p.edad > 18).AND(p => p.localidad == "badajoz");
          ISpecification<persona> CacereñosMenoresde18ORPacensesMayoresDe18 = CacereñosMenoresde18.OR(PacensesMayoresDe18);
 
          var res = listaPers.Where(CacereñosMenoresde18ORPacensesMayoresDe18.IsSatisfied());
          foreach (persona p in res)
          {
              Console.WriteLine(p.nombre);
          }
          Console.Read();
      }
  }

No hay comentarios:

Publicar un comentario