Linq Expressions, The Specification Pattern and Repositories - Part 3

In the previous to parts to this series (here and here) I showed the basic infrastructure framework for using Linq-based specifications with a flexible repository interface. In this article I dig deeper into the Expression based find functionality and it's implementation.

The Goal

Specification based query support on the repository is a great feature for encapsulation of any query logic. My major problem with this approach was the creation of trivial specifications on a per-domain-object basis. This would litter the code with specifications that had very little functionality contained within them. Generally this is not a bad thing but, in this instance, for the sake of maintainabilty and simplicity I wanted a to be able to perform ad-hoc queries.

The solution was to make my repository able to accept queries like :

            var repository = new Repository<Person>(session);
            
var people repository
                .FindAll(p 
=> p.Name == "steve");

The AdHoc specification

Since I have already made the decision that my MatchingCriteria are of type Expression, the AdHoc specification is trivial - but very cool. The implementation looks like :

    public class AdHoc<T> : Specification<T> where T : IEntity
    {
        
private readonly Expression<Func<T, bool>> expression;

        public 
AdHoc(Expression<Func<T, bool>> expression)
        {
            
this.expression expression;
        
}

        
public override Expression<Func<T, bool>> MatchingCriteria
        {
            
get return expression}
        }
    }

Simplifying the repository using the AdHoc specification

By utilising this simple but powerful specification class we can simplify the api to our IRepository. We are now able to add a FindOne and FindAll overload that accept Linq based queries in an AdHoc fashion. The implementation is as simple as :

        public IQueryable<T> FindAll(Expression<Func<T, bool>> expression)
        {
            
return FindAll(new AdHoc<T>(expression));
        
}

This functionality is able to be consumed in the following fashion (satisfying our goal for this task) :

            var people repository.FindAll(p => p.Name == "steve");

Conclusion

In combination with the flexibility built into the IRepository interface we now have a basis for a data access layer with maximum flexibility. Sql queries are created at execution time to reflect the criteria that we have applied through domain specifications - be they concrete or ad-hoc.

kick it on DotNetKicks.com

August 12, 2008 12:44 by steven.burman
E-mail | Permalink | Comments (5) | Comment RSSRSS comment feed

Related posts

Comments

October 24. 2008 04:03

Jean-Philippe Leconte

Your implementation looks mighty queer to me. You have an IRepository interface that exposes the IQueryable interface. Why not make the IRepository interface derive from IQueryable; the implementation would simply wrap the top IQueryable for a type (DataContext.GetTable<T> in LinqToSQL, ISession.Linq<T> in NHibernate iirc), making it completely available to Linq. Instead of having to reimplement FindOne, FindAll, etc... you could simply use First, FirstOrDefault and Where...

Jean-Philippe Leconte

October 24. 2008 10:06

steven.burman

@Jean-Philippe

Interesting approach but doesn't fit in with my need to define specifications and have them query against the database.

In fact, more recently I am actually returning IEnumerable from the repository so I can be assured that I have no late sql execution when I am not expecting it.

steven.burman

June 17. 2009 01:02

Kurt Johnson

Is it possible to pass a function into a specification? Such as a Person value object with first and last name properties. I am trying to filter on FullName as a string return of a concatenation of the names. When that failed, I switched to using a Func<MyUserObject, bool> return that doesn't work in QuerySpecification as a MatchingCriteria.

What direction should I go? (I can provide code, as required).

Kurt Johnson

June 17. 2009 08:32

steven.burman

I can see how problems may be introduced by working against concatenated fields. It is important to remeber that even though we may be abstracted away from the ORM that eventually the query needs to be decompiled into something that the ORM can understand.

I would suggest you will have better success by querying against the name properties individually. Otherwise, you will just have to experiment to see how far you can push it.

steven.burman

June 17. 2009 23:29

Kurt Johnson

Exactly.

I took a guess that my ORM(nhib) needed me to translate my concatenation as an expression tree first, then pass it. Apparently, I am having a mapping problem. I will let you know. The possibility of using specifications with Linq and Nhibernate is compelling.

I am also working with fluent-nhibernate right now to determine if there's a mapping solution that could serve as a workaround.

Thanks for all your hard work and the reply!
Kurt

Kurt Johnson

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

July 4. 2009 20:15