Linq specifications: from Argentina with Love

You know, like “from Rusia with love”, this is the Criolla version.

Well, I was talking with my friend Fabian Figueredo from YoProgramo.net about specification pattern and compositions with Linq and then I figure out a nice idea that I will use soon.

If you read my blog, I’m using often the Dao pattern, with the interface described by Fabio Maulo here . The interface has these two major methods:

IEnumerable<TEntity> Retrieve(Expression<Func<TEntity, bool>> predicate);
int Count(Expression<Func<TEntity, bool>> predicate);

This interface is really nice and allows me to build code like this:

dao.Retrieve(Customers.Prefered())
dao.Retrieve(Customers.WithoutActivitySince(new DateTime(2009, 12, 01)))

These kind of queries are easy to write. The problem comes when you want to operate queries, let say apply boolean operators.

What if we want:

  • Prefered customers without activity since a specific date.
  • Prefered customers with activity since a specific date.
  • Not prefered customers without activity since a specific date.

So, the problem is that we have our specifications objects, but we need to operate.

Note: I have seen a project linq-specifications, but it seems to not help with this task.

The solution

Imagine an in-memory (fake) dao of strings:

public class SampleRepository : ReadOnlyCollection<string>, IDaoQueryable<string>
{
    public SampleRepository() : base(new []{"Jose", "Manuel", "Julian"})
    {}

    public IEnumerable<string> Retrieve(Specification<string> specfication)
    {
        return this.AsQueryable().Where(specfication.Spec());
    }
}

The tests:

[Test]
public void adhoc_should_work()
{
    var specification = new AdHocSpecification<string>(n => n.StartsWith("J"));

    var result = new SampleRepository()
                        .Retrieve(specification);

    result.Satisfy(r => r.Contains("Jose")
                      && r.Contains("Julian")
                      && !r.Contains("Manuel"));
}

[Test]
public void negate_should_work()
{
    var specification = new NegateSpecification<string>(n => n.StartsWith("J"));

    var result = new SampleRepository()
                        .Retrieve(specification);

    result.Satisfy(r => !r.Contains("Jose")
                      && !r.Contains("Julian")
                      && r.Contains("Manuel"));
}

[Test]
public void and_should_work()
{
    var startWithJ = new AdHocSpecification<string>(n => n.StartsWith("J"));
    var endsWithE = new AdHocSpecification<string>(n => n.EndsWith("e"));

    var result = new SampleRepository()
                        .Retrieve(startWithJ & endsWithE);

    result.Satisfy(r => !r.Contains("Jose")
                      && !r.Contains("Julian")
                      && r.Contains("Manuel"));
}


[Test]
public void or_should_work()
{
    var startWithJ = new AdHocSpecification<string>(n => n.StartsWith("J"));
    var endsWithN = new AdHocSpecification<string>(n => n.EndsWith("n"));

    var result = new SampleRepository()
                        .Retrieve(startWithJ | endsWithN);

    result.Satisfy(r => r.Contains("Jose")
                      && r.Contains("Julian")
                      && !r.Contains("Manuel"));
}

[Test]
public void combination_sample_should_work()
{
    var startWithJ = new AdHocSpecification<string>(n => n.StartsWith("J"));
    var endsWithN = new AdHocSpecification<string>(n => n.EndsWith("n"));
    

    var result = new SampleRepository()
                        .Retrieve(startWithJ | !endsWithN);
        
    result.Satisfy(r => r.Contains("Jose")
                      && !r.Contains("Julian")
                      && !r.Contains("Manuel"));
}

As you can see I want binary operators &, | , and !.

The solution:

public abstract class Specification<T>
{
    public abstract Expression<Func<T, bool>> Spec();

    public static Specification<T> operator &(Specification<T> spec1, Specification<T> spec2)
    {
        return new AndSpecification<T>(spec1.Spec(), spec2.Spec());
    }

    public static Specification<T> operator |(Specification<T> spec1, Specification<T> spec2)
    {
        return new OrSpecification<T>(spec1.Spec(), spec2.Spec());
    }
    public static Specification<T> operator !(Specification<T> spec1)
    {
        return new NegateSpecification<T>(spec1.Spec());
    }
}

public class AdHocSpecification<T> :Specification<T>
{
    private readonly Expression<Func<T, bool>> _specification;

    public AdHocSpecification(Expression<Func<T, bool>> specification)
    {
        _specification = specification;
    }

    public override Expression<Func<T, bool>> Spec()
    {
        return _specification;
    }
}
public class AndSpecification<T> : Specification<T>
{
    private readonly Expression<Func<T, bool>> _spec1;
    private readonly Expression<Func<T, bool>> _spec2;

    public AndSpecification(Expression<Func<T, bool>> spec1, Expression<Func<T, bool>> spec2)
    {
        _spec1 = spec1;
        _spec2 = spec2;
    }

    public override Expression<Func<T, bool>> Spec()
    {
        ParameterExpression param = Expression.Parameter(typeof(T),"x");
        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(
                Expression.Invoke(_spec1, param), 
                Expression.Invoke(_spec2, param)), param);
    }
}

public class OrSpecification<T> : Specification<T>
{
    private readonly Expression<Func<T, bool>> _spec1;
    private readonly Expression<Func<T, bool>> _spec2;

    public OrSpecification(Expression<Func<T, bool>> spec1, Expression<Func<T, bool>> spec2)
    {
        _spec1 = spec1;
        _spec2 = spec2;
    }

    public override Expression<Func<T, bool>> Spec()
    {
        ParameterExpression param = Expression.Parameter(typeof(T), "x");
        return Expression.Lambda<Func<T, bool>>(
            Expression.OrElse(
                Expression.Invoke(_spec1, param),
                Expression.Invoke(_spec2, param)), param);
    }
}

public class NegateSpecification<T> : Specification<T>
{
    private readonly Expression<Func<T, bool>> _expression;

    public NegateSpecification(Expression<Func<T, bool>> expression)
    {
        _expression = expression;
    }

    public override Expression<Func<T, bool>> Spec()
    {
        ParameterExpression param = Expression.Parameter(typeof(T), "x");
        return Expression.Lambda<Func<T, bool>>(Expression.Not(Expression.Invoke(_expression, param)), param);
    }
}

Note:

  • I’ve defined the three operators in the base class for specifications.
  • Lambda expressions are lazy-constructed.

The AdHoc, And, Or and Negate specification are not meant to be used directly.  You must write your own specifications inheriting from Specification<T> and then you can operate when needed.

That said; I don’t write specifications for less important queries

Less important note: Yes this work with Nhibernate Linq (criteria version) and should work with EF.

var spec1 = new AdHocSpecification<Person>(p => p.FirstName.StartsWith("A"));
var spec2 = new AdHocSpecification<Person>(p => p.LastName.StartsWith("A"));

using(var s = sf.OpenSession())
using (var tx = s.BeginTransaction())
{
    s.Save(new Person {FirstName = "Alberto", LastName = "Arnaldo"});
    tx.Commit();
}

using(var s = sf.OpenSession())
using(var tx = s.BeginTransaction())
{
    var result = s.Linq<Person>().Where((spec1 & spec2).Spec());
    result.Count().Satisfy(r => r == 1);
    tx.Commit();
}

The generated sql query is:

21:01:55,906 DEBUG SQL:0 - SELECT count(*) as y0_ FROM Person this_ WHERE (this_.FirstName like @p0 and this_.LastName like @p1);@p0 = 'A%', @p1 = 'A%'

blog comments powered by Disqus
  • Categories

  • Archives