13
Mar
09

Specification Pattern and LINQ to NHibernate

We love Specifications!

Oh no, not another post about the Specification pattern! Oh yes, to add to the existing body of knowledge found here, here, here, here, here, here, and in many other places, I’d like to present my own take on the pattern.

My particular focus is on smooth integration with LINQ, and the approach described below manages this but does have some limitations.

Consider this a proof-of-concept if you will. It certainly hasn’t been exercised in production code with real world queries yet.

Wouldn’t it be nice…

…If we could write something like this and have NHibernate.Linq interpret the method call to SQL:

Specifications in Linq
var withId100 = 
     from shift in repository.Query<Shift>()
     where shift.Has.IdEqual(100)
     select shift;
         
var withRunningSheets = 
     from shift in repository.Query<Shift>()
     where shift.Has.ARunningSheet()
     select shift;

var involvingUser = 
     from shift in repository.Query<Shift>()
     where shift.Has.InvolvementOf(loggedOnUser)
     select shift;

It is possible!

If we rewrite the embedded LINQ as a method chain we can see that IQueryable<T>.Where takes an Expression<Func<T,bool>> parameter, so our specification (e.g. IdEqual, HasInvolvementOf) needs to return this type.

Exploiting partial function application we can make the signature of our specification would look like:

Specification signature
   public readonly Func<long, Expression<Func<Shift,bool>>> IdEqual =
           id => shift => shift.Id == id;

This does look a little daunting, but we can ease the eyestrain by judicious use of a using alias:

Easier on the eye
namespace VicPol.ERS.Model.RunningSheets
{
    using Predicate = Expression<Func<Shift, bool>>;
    public class Specifications
    {
        public readonly Func<long, Predicate> IdEqual =
                id => shift => shift.Id == id;
    }
}

What we have here is a field member (IdEqual) that returns a function that takes a long and returns an expression tree representing a function that takes a Shift and returns a boolean. (Did you get that?)

Linq is interested in the expression tree. In the examples above (Specifications in Linq) ‘shift.Has.IdEqual(100)’ is calling the function returned by IdEquals with the value 100. The Where extension method takes the return value.

This compiles fine but, as you’d expect, NHibernate.Linq doesn’t know what IdEqual means.

This leaves us with the challenge of transforming the method call into the expression returned by the method. Ideally NHibernate.Linq could do this for us, but given the current rework going on around Linq providers for NHibernate I’ve not even attempted this.

Instead we cheat and overload the Where method again to provide an implementation that does this translation.

The method translator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace VicPol.ERS.Model.Persistence
{
    public static class SpecificationExtensions
    {
        public static IQueryable<T> Where<T>(this IQueryable<T> source, Expression<Func<T, Expression<Func<T, bool>>>> spec)
        {
            return source.Where(getPartiallyAppliedSpecification(spec));
        }
         
        public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Expression<Func<T, Expression<Func<T, bool>>>> spec)
        {
            return source.Where(getPartiallyAppliedSpecification(spec).Compile());
        }
         
        public static bool Satisfies<T>(this T source, Func<T, Expression<Func<T, bool>>> spec)
        {
            return IsSatisfiedBy(spec(source), source);
        }
         
        public static bool IsSatisfiedBy<T>(this Expression<Func<T, bool>> source, T candidate)
        {
            return source.Compile()(candidate);
        }
         
        private static Expression<Func<T, bool>> getPartiallyAppliedSpecification<T>(
                Expression<Func<T, Expression<Func<T, bool>>>> specificationExpression)
        {
            //We're expecting a call to a function provided by some field member.
            var invocationExpression = specificationExpression.Body as InvocationExpression;
            if(invocationExpression == null) throw new ArgumentException("Expected invocation expression");
            var memberExpression = invocationExpression.Expression as MemberExpression;
            if(memberExpression == null) throw new ArgumentException("Expected invocation expression to reference member");
         
            //The members return value is itself a function. 
            //This is what gets evaluated by the LINQ QueryProvider. 
            //We cache this to avoid repeated reflection and instance creation.
            MulticastDelegate openSpec;
            if(!cache.TryGetValue(memberExpression.Member, out openSpec))
            {
                //If the function is not cached, we create an instance of the member's declaring 
                //type and invoke the member to get the function.
                var member = memberExpression.Member as FieldInfo;
                if(member == null) throw new ArgumentException("Expected member expression to reference field");
                if(!member.FieldType.IsSubclassOf(typeof(MulticastDelegate)))
                    throw new ArgumentException("Expected member to be a subclass of MulticastDelegate (e.g. Func<>)");
         
                var instance = Activator.CreateInstance(member.DeclaringType);
                openSpec = (MulticastDelegate) member.GetValue(instance);
         
                lock(cacheLock)
                    if(!cache.ContainsKey(memberExpression.Member))
                        cache.Add(memberExpression.Member, openSpec);
            }
         
            //Now we take the arguments provided to the specification expression and invoke the function with them.
            //This results in the partially applied expression function that is passed to the QueryProvider.
            var args = invocationExpression.Arguments.Select(
                    arg => Expression.Lambda(arg, null).Compile().DynamicInvoke())
                    .ToArray();
            return (Expression<Func<T, bool>>) openSpec.DynamicInvoke(args);
        }
         
        public static int CacheSize
        {
            get { return cache.Count; }
        }
         
        public static void ClearCache()
        {
            cache.Clear();
        }
         
        private static readonly object cacheLock = new object();
        private static readonly IDictionary<MemberInfo, MulticastDelegate> cache =
                new Dictionary<MemberInfo, MulticastDelegate>();
    }
}

There’s quite a bit going on here. Firstly we have the extension methods IQueryable<T>.Where and IEnumerable<T>.Where; these overload the existing extension methods in System.Core. The signature for the specification parameter causes the compiler to convert the specification field (e.g. IdEqual) to an expression tree. Satisfies and SatisfiedBy allow the specification to be used by assertions (for example validation of business rules).

Hopefully the comments in the method getPartiallyAppliedSpecification explain what is going on sufficiently clearly. This method is the heart of making specifications work ‘nicely’ with Linq. I’ve make a cursory attempt to make the caching of the reflection, instance activation and method call thread safe. Please correct and improve this code!

The methods CacheSize and ClearCache are there only for testing.

Putting it all together

Declaring specifications
using System;
using System.Linq;
using System.Linq.Expressions;
using VicPol.ERS.Model.Support;

namespace VicPol.ERS.Model.RunningSheets
{
    using Predicate = Expression<Func<Shift, bool>>;
    public partial class Shift
    {
        private HasSpecifications has;
        public virtual HasSpecifications Has
        {
            get
            {
                if(has == null) has = new HasSpecifications();
                return has;
            }
        }
         
        public class HasSpecifications
        {
            public readonly Func<long, Predicate> IdEqual =
                    id => shift => shift.Id == id;
         
            public readonly Func<Predicate> ARunningSheet =
                    () => shift => shift.RunningSheet != null;
         
            public readonly Func<User, Predicate> InvolvementOf =
                    user => shift =>
                            shift.Sessions.Any(
                                    session =>
                                    session.LogOn.UserId == user.UserName ||
                                    session.LogOn.User2 == user.UserName ||
                                    session.LogOn.User3 == user.UserName ||
                                    session.LogOn.User4 == user.UserName);
        }
    }
}

The specification ‘InvolvementOf’ begins to reveal the power of the specification pattern in Linq. The following snippet show the specifications in use:

Using specifications in NHibernate.Linq queries
  var shiftWithId100 =
      from shift in repos.Query<Shift>()
      where shift.Has.IdEqual(100)
      select shift;
         
  var shiftWithARunningSheet =
      from shift in repos.Query<Shift>()
      where shift.Has.ARunningSheet()
      select shift;
         
  var shiftWithIdEquals100AndWithARunningSheet =
      from shift in repos.Query<Shift>()
      where shift.Has.IdEqual(100)
      where shift.Has.ARunningSheet()
      select shift;
         
  var shiftInvolvingUser =
      from shift in repos.Query<Shift>()
      where shift.Has.InvolvementOf(loggedOnUser)
      select shift;

As can be seen in the third example, specifications can be combined in queries, however the somewhat more natural syntax of using ‘&’ between the constraints is not possible as the & operator is not defined on the Expression class (normally the & would be part of the expression tree). Other operators, like ‘!’ (not) also suffer a similar fate. Also using this style of specification as an assertion (the Satisfies method) is a little clunky. I haven’t yet tried to see how composable these specifications are, but I suspect this may be a further limitation.

Finally, I hope NHibernate.Linq will formally support specifications in the near future.

About these ads

1 Response to “Specification Pattern and LINQ to NHibernate”



Comments are currently closed.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: