José F. Romaniello

Las aventuras y desventuras de un codificador.

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%'

| More

6 comentarios:

Jose,

Hi, I am the author of the linq-specifications project on Google Code. You have obviously thoroughly thought about this topic and your perspective is a good one.

I have moved on a little from what is seen in the Google Code project and have been meaning to revisit some of the core concepts. Would you be interested in pushing some of your ideas into the project?

José F. Romaniello dijo...

Yes, of course!! I'll contact you the next week.

Darius Damalakas dijo...

I have been thinking of similar problems. We also use repository-like pattern, and it always struck me with a question what if i want to get not only managers which are managing the company, but also some custom criteria, which simply can vary from page to page. 

Jose, would be nice to see some code!

José F. Romaniello dijo...

<span style="color: #808080;">Let said,.... You want preferred customers and from California. It's a valid example of what you are talking about?</span>

If this is the case you can use Retrieve(new PreferredCustomers() & new AdhocSpecification(c => c.State == California));

If Custom criteria means a dynamic criteria. A combo where the user picks properties and apply filters... This is another subject, and yes I did something already without DynamicLinq. I think DynamicLinq is WRONG. The point is that it is easy to construct expression, see only the code from the my last answer http://yoprogramo.net/pregunta/10/cual-es-la-mejor-forma-hacer-busquedas-por-criterio-en . 

Darius,

I use LinqToSql and EF too, and in order to compose two queries, I usually use Extension methods. Of course it's not as complex as this, but it's a very easy way to have more than one condition in place:

var result = context.Managers.ThatManageTheCompany().ThatAreContractors().ThatAreFemale();

Just an easy way out!
Gus

José F. Romaniello dijo...

Yes, it is a nice aproach and i use it too. You can't use boolean operators
with those specifications though.
BTW; the project has been moved to codeplex.
http://linqspecs.codeplex.com
Thank you for your comments.

Publicar un comentario en la entrada