NHibernate and the specification pattern

Home / Programming / NHibernate and the specification pattern
Share...Tweet about this on TwitterShare on Facebook1Share on Google+3Share on StumbleUpon1Share on LinkedIn0Flattr the authorPin on Pinterest0Share on Reddit0Share on Tumblr0Digg this

This is my first guest post at Agile-Code! I wish to thank my friend Zoran Maksimovic (@zoranmax) for the invitation! Hope the blog’s readers like it! 🙂

The Specification pattern is well known to .NET developers and has gained significant momentum with the introduction of LINQ expressions. There are lots of implementations, but, for one reason or the other, I wasn’t really happy with any of them, so I decided to write my own, specifically for working with NHibernate.

Using my implementation of the specification pattern, you can write detached LINQ queries (something that NHibernate does not have at the moment) which can then be applied to NHibernate for obtaining results. As an appetizer, here are some examples:

var specification = Specification.Create(p => p.Name != "");

var isSatisfied = specification.IsSatisfiedBy(new Product { Name = "" });

var filteredResults = session.QueryBySpecification(specification).ToList();

var pagedSpecification = specification.Take(3).Skip(1);

var pagedResults = session.QueryBySpecification(pagedSpecification).ToList();

var orderedSpecification = specification.OrderBy(x => x.Name);

var orderedResults = session.QueryBySpecification(orderedSpecification).ToList();

var orderedDescendingSpecification = specification.OrderByDescending(x => x.Name);

var orderedDescendingResults = session
.QueryBySpecification(orderedDescendingSpecification)
.ToList();

var thenSpecification = orderedSpecification.ThenBy(x => x.Price);

var thenResults = session.QueryBySpecification(thenSpecification).ToList();

var thenDescendingSpecification = orderedSpecification.ThenByDescending(x => x.Price);

var thenDescendingResults = session
.QueryBySpecification(thenDescendingSpecification)
.ToList();

var orSpecification = specification.Or(p => p.Price < 1000); 
var orResults = session.QueryBySpecification(orSpecification).ToList(); 
var andSpecification = specification.And(p => p.Price > 1000);

var andResults = session.QueryBySpecification(andSpecification).ToList();

var notSpecification = specification.Not();

var notResults = session.QueryBySpecification(notSpecification).ToList();

var fetchSpecification = specification.Fetch(p => p.OrderDetails);

var fetchResults = session.QueryBySpecification(fetchSpecification).ToList();

var allTogetherSpecification = specification.And(p => p.Name.Length > 3)
.OrderBy(x => x.Name)
.ThenBy(x => x.Price)
.Skip(1)
.Take(2).Fetch(p => p.OrderDetails);

var allTogetherResults = session.QueryBySpecification(allTogetherSpecification).ToList();

isSatisfied = allTogetherSpecification.IsSatisfiedBy(new Product { Name = "" });

We build a specification with an arbitrarily complex condition and we can apply paging, ordering and eager fetching to it. This does not come from the Specification pattern, but it is something that NHibernate can use. In the end, we ask NHibernate to retrieve the results for the specification.

Implementation

First, we have an interface definition (well, two), which only differ to other well-known implementations because of the Expression property:

public interface ISpecification
{
    Expression Expression { get; }
}

public interface ISpecification<T> : ISpecification where T : class
{
    Boolean IsSatisfiedBy(T item);
}

So, basically we have a root interface and a generic one that inherits from it. This is what represents our specification contract:

  • A class can be checked against it;
  • It has an expression that represents queries for it.

Being an interface, you can implement it in any way you like, in any class, but I wanted to have some helper class to make this easier. So, here’s a specification class that does just that:

public class Specification : ISpecification where T : class
{
    private Func<T, Boolean> compiled;

    protected internal Specification(Expression expression)
    {
        this.Expression = expression;
    }

    public static ISpecification Create(Expression<Func<T, Boolean>> expression)
    {
        return (new Specification(expression));
    }

    public static IQueryable All()
    {
        return (SpecificationExtensions.All());
    }

    public static IQueryable Where(Expression<Func<T, Boolean>> condition)
    {
        return (SpecificationExtensions.Where(condition));
    }

#region ISpecification Members

    public virtual Boolean IsSatisfiedBy(T item)
    {
        if (this.compiled == null)
        {
            this.compiled = SpecificationExtensions.ExtractCondition(this.Expression).Compile();
        }

        return (this.compiled(item));
    }

#endregion

#region ISpecification Members

    public virtual Expression Expression { get; protected set; }

#endregion

#region Public override methods

    public override Boolean Equals(Object obj)
    {
        var other = obj as Specification;

        if ((other == null) || (other.GetType() != this.GetType()))
        {
            return (false);
        }

        if (Object.ReferenceEquals(this, obj) == true)
        {
            return (true);
        }

        return (this.Expression.Equals(other.Expression));
    }

    public override Int32 GetHashCode()
    {
        return (this.Expression.GetHashCode());
    }

    public override String ToString()
    {
        return (this.Expression.ToString());
    }

#endregion
}

You do not need to inherit from this base class unless you want to add custom behavior – properties and having the expression reacting to it, for example.

The Specification class references a SpecificationExtensions class, which is where most of the magic actually is:

public static class SpecificationExtensions
{<
    #region Private static helper methods

    private static IQueryable CreateQueryable()
    {
        return (new NhQueryable(null));
    }

    internal static Expression<Func<T, Boolean>> ExtractCondition(Expression expression)
    {
        if (expression is Expression & lt; Func & lt; T, Boolean & gt; >)
        {
            return (expression as Expression & lt; Func & lt; T, Boolean & gt; >);
        }
        else if (expression is MethodCallExpression)
        {
            foreach (var argument in (expression as MethodCallExpression).Arguments)
            {
                var condition = ExtractCondition(argument);

                if (condition != null)
                {
                    return (condition);
                }
            }
        }
        else if (expression is UnaryExpression)
        {
            return (ExtractCondition((expression as UnaryExpression).Operand));
        }
        return (null);
    }

    private static MethodCallExpression FindMethodCallExpression(Expression expression, Type type, String name)
    {
        if (expression is MethodCallExpression)
        {
            var methodCallExp = (expression as MethodCallExpression);

            if ((methodCallExp.Method.DeclaringType == type) & amp; & (methodCallExp.Method.Name == name))
            {
                return (methodCallExp);
            }

            foreach (var argument in methodCallExp.Arguments)
            {
                methodCallExp = FindMethodCallExpression(argument, type, name);

                if (methodCallExp != null)
                {
                    return (methodCallExp);
                }
            }
        }

        return (null);
    }

    private static Expression<Func<T, Object>> ExtractOrder(Expression expression, String orderKind)
    {
        var methodCallExp = FindMethodCallExpression(expression, typeof(Queryable), orderKind);

        if (methodCallExp != null)
        {
            var lambda = (methodCallExp.Arguments.Last() as UnaryExpression).Operand as LambdaExpression;

            lambda = Expression.Lambda & lt; Func & lt; T, Object & gt; > (Expression.Convert(lambda.Body, typeof(Object)), lambda.Name, lambda.TailCall, lambda.Parameters);

            return (lambda as Expression & lt; Func & lt; T, Object & gt; >);
        }

        return (null);
    }

    private static Expression<Func<T, Object>> ExtractOrderBy(Expression expression)
    {
        return (ExtractOrder(expression, "OrderBy"));
    }

    private static Expression<Func<T, Object>> ExtractOrderByDescending(Expression expression)
    {
        return (ExtractOrder(expression, "OrderByDescending"));
    }

    public static Expression<Func<T, Object>> ExtractThenBy(Expression expression)
    {
        return (ExtractOrder(expression, "ThenBy"));
    }

    public static Expression<Func<T, Object>> ExtractThenByDescending(Expression expression)
    {
        return (ExtractOrder(expression, "ThenByDescending"));
    }

    private static Int32 ExtractPaging(Expression expression, String pagingKind)
    {
        var methodCallExp = FindMethodCallExpression(expression, typeof(Queryable), pagingKind);

        if (methodCallExp != null)
        {
            return ((Int32)(methodCallExp.Arguments.Last() as ConstantExpression).Value);
        }

        return (0);
    }

    private static Int32 ExtractTake(Expression expression)
    {
        return (ExtractPaging(expression, "Take"));
    }

    private static Int32 ExtractSkip(Expression expression)
    {
        return (ExtractPaging(expression, "Skip"));
    }

    public static Expression<Func<T, Object>> ExtractFetch(Expression expression)
    {
        var methodCallExp = FindMethodCallExpression(expression, typeof(EagerFetchingExtensionMethods), "Fetch");

        if (methodCallExp != null)
        {
            return ((methodCallExp.Arguments.Last() as UnaryExpression).Operand as Expression & lt; Func & lt; T, Object & gt; >);
        }

        return (null);
    }

    private static IQueryable AddFetching(IQueryable queryable, Expression source, Boolean skipOrdering, Boolean skipPaging)
    {
        if (skipOrdering == false)
        {
            queryable = AddOrdering(queryable, source, true);
        }

        if (skipPaging == false)
        {
            queryable = AddPaging(queryable, source, false);
        }

        var fetch = ExtractFetch(source);

        if (fetch != null)
        {
            queryable = queryable.Fetch(fetch);
        }

        return (queryable);
    }

    private static IQueryable AddPaging(IQueryable queryable, Expression source, Boolean skipOrdering)
    {
        var take = ExtractTake(source);
        var skip = ExtractSkip(source);

        if (skipOrdering == false)
        {
            queryable = AddOrdering(queryable, source, true);
        }

        if (skip != 0)
        {
            queryable = queryable.Skip(skip);
        }

        if (take != 0)
        {
            queryable = queryable.Take(take);
        }

        return (queryable);
    }

    private static IQueryable AddOrdering(IQueryable queryable, Expression source, Boolean skipPaging)
    {
        var orderBy = ExtractOrderBy(source);
        var orderByDescending = ExtractOrderByDescending(source);
        var thenBy = ExtractThenBy(source);
        var thenByDescending = ExtractThenByDescending(source);

        if (orderBy != null)
        {
            queryable = queryable.OrderBy(orderBy);
        }

        if (orderByDescending != null)
        {
            queryable = queryable.OrderByDescending(orderByDescending);
        }

        if (thenBy != null)
        {
            queryable = (queryable as IOrderedQueryable).ThenBy(thenBy);
        }

        if (thenByDescending != null)
        {
            queryable = (queryable as IOrderedQueryable).ThenByDescending(thenByDescending);
        }

        if (skipPaging == false)
        {
            queryable = AddPaging(queryable, source, true);
        }

        return (queryable);
    }

    #endregion

    #region Public extension methods

    public static IQueryable All(this Type type)
    {
        return (typeof(SpecificationExtensions).GetMethod("All", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(type).Invoke(null, null) as IQueryable);
    }

    public static IQueryable All() where T : class
    {
        return (Where(x => true));
    }

    public static IQueryable Where(Expression< Func<T, Boolean>> condition) where T : class
     {
        return (CreateQueryable().Where(condition));
}

public static IQueryable QueryBySpecification(this ISession session, ISpecification specification) where T : class
{
    var queryable = session.Query().Where(ExtractCondition(specification.Expression));
    queryable = AddOrdering(queryable, specification.Expression, false);
    //queryable = AddPaging(queryable, specification.Expression, true);
    queryable = AddFetching(queryable, specification.Expression, true, true);

    return (queryable);
}

public static ISpecification And(this ISpecification specification, Expression< Func<T, Boolean>> condition) where T : class
 {
var current = ExtractCondition(specification.Expression);
var and = Expression.AndAlso(current.Body, condition.Body);
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.AndAlso(Expression.Invoke(current, param), Expression.Invoke(condition, param));
var lambda = Expression.Lambda & lt; Func<T, Boolean>>(body, param);

var queryable = CreateQueryable().Where(lambda);
queryable = AddOrdering(queryable, specification.Expression, false);
queryable = AddPaging(queryable, specification.Expression, false);

return (new Specification(queryable.Expression));
}

public static ISpecification And(this ISpecification specification, ISpecification other) where T : class
{
    return (And(specification, ExtractCondition(other.Expression)));
}

public static ISpecification Or(this ISpecification specification, Expression< Func<T, Boolean>> condition) where T : class
 {
var current = ExtractCondition(specification.Expression);
var or = Expression.OrElse(current.Body, condition.Body);
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.OrElse(Expression.Invoke(current, param), Expression.Invoke(condition, param));
var lambda = Expression.Lambda & lt; Func<T, Boolean>>(body, param);

var queryable = CreateQueryable().Where(lambda);
queryable = AddOrdering(queryable, specification.Expression, false);
queryable = AddPaging(queryable, specification.Expression, false);

return (new Specification(queryable.Expression));
}

public static ISpecification Or(this ISpecification specification, ISpecification other) where T : class
{
    return (Or(specification, ExtractCondition(other.Expression)));
}

public static ISpecification Not(this ISpecification specification) where T : class
{
    var not = Expression.Not(ExtractCondition(specification.Expression).Body);

    var queryable = CreateQueryable().Where(Expression.Lambda & lt; Func & lt; T, Boolean & gt; > (not, ExtractCondition(specification.Expression).Parameters));
    queryable = AddOrdering(queryable, specification.Expression, false);
    queryable = AddPaging(queryable, specification.Expression, false);

    return (new Specification(queryable.Expression));
}

public static ISpecification AsSpecification(this Expression< Func<T, Boolean>> expression) where T : class
 {
return (Specification.Create(expression));
}

public static Expression<Func<T, Boolean>> AsCondition(this ISpecification specification) where T : class
{
    return (ExtractCondition(specification.Expression));
}

public static ISpecification Take(this ISpecification specification, Int32 count) where T : class
{
    var queryable = CreateQueryable();
    queryable = queryable.Where(ExtractCondition(specification.Expression)).Take(count);

    var skip = ExtractSkip(specification.Expression);

    if (skip != 0)
    {
        queryable = queryable.Skip(skip);
    }

    queryable = AddOrdering(queryable, specification.Expression, true);

    return (new Specification(queryable.Expression));
}

public static ISpecification Skip(this ISpecification specification, Int32 count) where T : class
{
    var queryable = CreateQueryable();
    queryable = queryable.Where(ExtractCondition(specification.Expression)).Skip(count);

    var take = ExtractTake(specification.Expression);

    if (take != 0)
    {
        queryable = queryable.Take(take);
    }

    queryable = AddOrdering(queryable, specification.Expression, true);

    return (new Specification(queryable.Expression));
}

public static ISpecification OrderBy(this ISpecification specification, Expression< Func<T, Object>> orderBy) where T : class
 {
var queryable = CreateQueryable();
queryable = queryable.OrderBy(orderBy).Where(ExtractCondition(specification.Expression));
queryable = AddPaging(queryable, specification.Expression, true);

return (new Specification(queryable.Expression));
}

public static ISpecification OrderByDescending(this ISpecification specification, Expression< Func<T, Object>> orderByDescending) where T : class
 {
var queryable = CreateQueryable();
queryable = queryable.OrderByDescending(orderByDescending).Where(ExtractCondition(specification.Expression));
queryable = AddPaging(queryable, specification.Expression, true);

return (new Specification(queryable.Expression));
}

public static ISpecification ThenBy(this ISpecification specification, Expression< Func<T, Object>> orderBy) where T : class
 {
var queryable = CreateQueryable();
queryable = AddOrdering(queryable, specification.Expression, true);
queryable = (queryable as IOrderedQueryable).ThenBy(orderBy).Where(ExtractCondition(specification.Expression));
queryable = AddPaging(queryable, specification.Expression, true);

return (new Specification(queryable.Expression));
}

public static ISpecification ThenByDescending(this ISpecification specification, Expression< Func<T, Object>> orderBy) where T : class
 {
var queryable = CreateQueryable();
queryable = AddOrdering(queryable, specification.Expression, true);
queryable = (queryable as IOrderedQueryable).ThenByDescending(orderBy).Where(ExtractCondition(specification.Expression));
queryable = AddPaging(queryable, specification.Expression, true);

return (new Specification(queryable.Expression));
}

public static ISpecification Fetch(this ISpecification specification, Expression< Func<T, Object>> path) where T : class
 {
var queryable = CreateQueryable().Where(ExtractCondition(specification.Expression));
queryable = queryable.Fetch(path);
queryable = AddOrdering(queryable, specification.Expression, false);
queryable = AddPaging(queryable, specification.Expression, false);

return (new Specification(queryable.Expression));
}

#endregion
}


This is a static class with some extension methods and some internal helper ones. It takes care of composing specifications together in a fluent interface (And and Or), reversing the condition (Not), applying paging (Take and Skip), fetching related associations (Fetch) and building initial specifications (All and Where). For the integration with NHibernate, there is also an extension method over ISession, QueryBySpecification.

Example

Finally, an example of a custom specification might be:

public class Last3SentOrdersSpecification : ISpecification
{
    public Last3SentOrdersSpecification()
    {
        this.Expression = Specification
        .Where(x => x.State == OrderState.Sent)
        .OrderByDescending(x => x.Date)
        .Take(3)
        .Expression;
    }

#region ISpecification Members

    public Boolean IsSatisfiedBy(Order item)
    {
        return (item.State == OrderState.Sent);
    }

#endregion

#region ISpecification Members

    public Expression Expression { get; private set; }

#endregion
}

As for running it, it’s very simple:

var last3OrdersSpecification = new Last3SentOrdersSpecification();

var last3OrdersResults = session
.QueryBySpecification(last3OrdersSpecification)
.ToList();

Assert.IsTrue(last3OrdersResults.All(p => p.State == OrderState.Sent));
Assert.IsTrue(last3OrdersResults.All(p => last3OrdersSpecification.IsSatisfiedBy(p)));

Conclusion

Some things noteworthy:

  • The SpecificationExtensions class applies some “magic” to extract the conditions, paging and ordering parts of the expression. It could be done differently, maybe with an ExpressionVisitor implementation; as it is now, it requires some structure in place, so it really only works well with specifications built with the supplied methods;
  • The IsSatisfiedBy method, of course, does not care about the paging, ordering and fetching, only the condition;
  • The Expression property – and the ISpecification instance itself – should be immutable, hence the fluent methods return new instances;
  • Unlike other implementations, I do not have OrCondition, AndCondition, NotCondition and the likes; instead of that, I had to use some slightly complex expression manipulations – see the And, Or and Not methods for some examples;
  • Yes, it probably could be improved! Smile Feel free to send me your suggestions, I’ll have a look and give credit to whom deserves it!
    Share...Tweet about this on TwitterShare on Facebook1Share on Google+3Share on StumbleUpon1Share on LinkedIn0Flattr the authorPin on Pinterest0Share on Reddit0Share on Tumblr0Digg this

    I write about development in general, with a focus in .NET, at Development With A Dot. You can also follow me on Twitter, as @rjperes75.

    Leave a Reply

    moon_rolf@mailxu.com