Specification Pattern: Implementation with Linq

Description and code samples
Table of Contents
Summary

Upgrade classical Specification objects to perform selection operations over in-memory and persisted business objects.

 


Description

Using the classical Specification objects to filter records stored in a data store is inefficient for most applications because all records must be selected and reconstitued into in-memory objects and only then the specification object can be used for specialized queries.

Adoppted solution

The Specification interface is expanded to use a LINQ expression tree to represent the predicate that the specification object encapsulates. The expression tree is then exposed as a public property.

Why to to it

To filter records stored in a data store the Specification pattern is used in conjuntion with the Repository pattern.

As the number of queries to perform over an entity increases, the number of specialized “Get/Find” methods will also increase which implies recompilation of the Repository object that implements those query methods.

To avoid this scenario the Specification pattern is used where the selection criteria or logic is extracted from the “Get/Find” method into a Specification class that will be used as a parameter of the Repository object.

How it works

In case of a in-memory collection of business objects the predicate parameterized as lambda expressions will be compiled as Func<TSource, bool> delegates and applied to the extension methods defined for the IEnumerable<TSource> colection type selecting the elegible business objects.

In case of a persisted collection of objects the predicate parameterized as lambda expressions will be compiled as Expressions<Func<TSource, bool>> expression trees and applied to the extension methods defined for the IQueryable<TSource> type:

  1. The “Where<TSource>(IQueryable<TSource>, Expression<Func<TSource, Boolean>>)” extension method generates a MethodCallExpression that represents calling “Where<TSource>(IQueryable<TSource>, Expression<Func<TSource, Boolean>>) itself as a constructed generic method;
  2. It then passes the MethodCallExpression to the “CreateQuery(Expression)” method of the IQueryProvider represented by the “Provider” property of the source parameter.

The original lambda expression that defined a selector function will be applied on the data source and retrieve only the elegible items.

Requirements

The System.Linq.Expressions namespace is required.

Linq to NHibernate

The Query<T> method included in NHibernate 3.0 has expanded support for expression trees.

Operations
Using Specification pattern for selection of a in-memory collection of business objects
How to do it

namespace Training.Domain.Specifications
{
    public class OrderWithDiscountAppliedSpecification : Specification<Order>
    {
        private float _minimumDiscounted;
        private short _minimumQuantity;

        public OrderWithDiscountAppliedSpecification(float pMinimumDiscounted, short pMinimumQuantity)
        {
            _minimumDiscounted = pMinimumDiscounted;
            _minimumQuantity = pMinimumQuantity;
        }

        public override string Description { get { return string.Format("orders with minimum discount of {0}€ and minimum quantity of {1} units ordered", _minimumDiscounted, _minimumQuantity); } }

        public override Expression<Func<Order, bool>> predicate
        {
            get
            {
                return (o => o.Order_Details.Discount >= _minimumDiscounted && o.Order_Details.Quantity >= _minimumQuantity);
            }
        }
    }    

    public class OrderReceivedOnPeriodSpecification : Specification<Order>
    {
        private DateTime _startDate;
        private DateTime _endDate;

        public OrderReceivedOnPeriodSpecification(DateTime pStartDate, DateTime pEndDate)
        {
            _startDate= pStartDate;
            _endDate = pEndDate;
        }

        public override string Description { get { return string.Format("Orders ordered between {0} and {1}", _startDate.ToString("yyyy/MM/dd"), _endDate.ToString("yyyy/MM/dd")); } }

        public override Expression<Func<Order, bool>> predicate
        {
            get
            {
                return o => o.OrderDate >= _startDate && o.OrderDate <= _endDate;
            }
        }
    }

}
How to use it
var _orders = new List<Training.Data.Order>() {
                              new Training.Data.Order() {
                                 Id=1,  CustomerID ="CUST1", OrderDate= new DateTime(1998,1,1),
                                 Order_Details = new Training.Data.Order_Detail() { Discount=10, ProductID=10, Quantity=10, UnitPrice=120}
                              },
                              new Training.Data.Order() {
                                 Id=2, CustomerID ="CUST2", OrderDate = new DateTime(1998,5,1),
                                 Order_Details = new Training.L2SQL.Data.Order_Detail() { Discount=10, ProductID=11, Quantity=20, UnitPrice=50}
                              }

              };

var _ordersValid = _orders.Where(o => new Training.Domain.Specifications.OrderWithDiscountAppliedSpecification((float)10, (short)1)
                                                            .And(new Training.Domain.Specifications.OrderReceivedOnPeriodSpecification(new DateTime(1998, 4, 22), new DateTime(1998, 5, 22))).IsSatisfiedBy(o));

foreach (Training.L2SQL.Data.Order item in _ordersValid)
{
   Console.WriteLine("Order ID {0}", item.Id);
}
Using Specification pattern for selection of business objects stored in a data source through Linq To NHibernate
How to do it

 

namespace Training.Domain.Specifications
{
    public class OrderReceivedOnPeriodSpecification : Specification<Order>
    {
        private DateTime _startDate;
        private DateTime _endDate;

        public OrderReceivedOnPeriodSpecification(DateTime pStartDate, DateTime pEndDate)
        {
            _startDate= pStartDate;
            _endDate = pEndDate;
        }

        public override string Description { get { return string.Format("Specification of orders ordered between {0} and {1}", _startDate.ToString("yyyy/MM/dd"), _endDate.ToString("yyyy/MM/dd")); } }

        public override Expression<Func<Order, bool>> predicate
        {
            get
            {
                return o => o.Date >= _startDate && o.Date <= _endDate;
            }
        }
    }

    public class CustomerWithTypedOrdersSpecification : Specification<Customer>
    {
        private Int16 _type;

        public CustomerWithTypedOrdersSpecification(Int16 pType)
        {
            _type= pType;
        }

        public override string Description { get { return string.Format("Specification of customers with orders of type {0}", _type); } }

        public override Expression<Func<Customer, bool>> predicate
        {
            get
            {
                return c => c.Orders.Any(o => o.Type==1);
            }
        }
    }

    public class CustomerTotalOrdersSpecification : Specification<Customer>
    {
        private Int32 _totalOrders;

        public CustomerTotalOrdersSpecification(Int32 pTotalOrders)
        {
            _totalOrders = pTotalOrders;
        }

        public override string Description { get { return string.Format("Specification of customers with minimum number of orders of {0}", _totalOrders); } }

        public override Expression<Func<Customer, bool>> predicate
        {
            get
            {
                return c => c.Orders.Count() >_totalOrders;
            }
        }
    }
}
How to use it
var  _orders = session.Query<Training.Data.Order>().Where(
                        new Training.Domain.Specifications.OrderReceivedOnPeriodSpecification(new DateTime(2013, 5, 1), new DateTime(2013, 5, 8)).predicate);

foreach (var item in _orders)
{
   Console.WriteLine("Order Id: {0}, Name: {1}", item.Id, item.Number);
}

var _customers = session.Query<Training.Data.Customer>().Where(
                        new Training.Domain.Specifications.CustomerWithTypedOrdersSpecification((Int16) 1).predicate);

foreach (var item in _customers)
{
   Console.WriteLine("Customer Id: {0}, Name: {1}", item.Id, item.Name);
}

_customers = session.Query<Training.Data.Customer>().Where(
                        new Training.Domain.Specifications.CustomerTotalOrdersSpecification((Int32)1).predicate);

foreach (var item in _customers)
{
   Console.WriteLine("Customer Id: {0}, Name: {1}", item.Id, item.Name);
}

_customers = session.Query<Training.Data.Customer>().Where(
                        new Training.Domain.Specifications.CustomerTotalOrdersSpecification((Int32)2)
                            .And(new Training.Domain.Specifications.CustomerWithTypedOrdersSpecification((Int16)1)).predicate);

foreach (var item in _customers)
{
   Console.WriteLine("Customer Id: {0}, Name: {1}", item.Id, item.Name);
}
Using Specification pattern for selection of business objects stored in a data source through Linq To SQL
How to do it

namespace Training.Domain.Specifications
{
    public class CustomerWithDiscountOnOrdersSpecification: Specification<Customer>  {

        private float _minimumDiscounted;
        private short _minimumQuantity;

        public override string Description { get { return string.Format("Specification of customers with minimum discount of {0}€ and minimim number of orders of {0} and minimum quantity of {1} units ordered", _minimumDiscounted, _minimumQuantity); } }

        public CustomerWithDiscountOnOrdersSpecification(float pMinimumDiscounted, short pMinimumQuantity)
        {
            _minimumDiscounted = pMinimumDiscounted;
            _minimumQuantity = pMinimumQuantity;
        }

        public override Expression<Func<Customer, bool>> predicate
        {
            get {
                return c => c.Orders.Any(o => o.Order_Details.Discount >= _minimumDiscounted && o.Order_Details.Quantity >= _minimumQuantity);
                //alternative option: return c => c.Orders.Any(new OrderWithDiscountAppliedSpecification((float)10,(short)1).predicate);

            }
        }
    }

    public class OrderReceivedOnPeriodSpecification : Specification<Order>
    {
        private DateTime _startDate;
        private DateTime _endDate;

        public OrderReceivedOnPeriodSpecification(DateTime pStartDate, DateTime pEndDate)
        {
            _startDate= pStartDate;
            _endDate = pEndDate;
        }

        public override string Description { get { return string.Format("Orders ordered between {0} and {1}", _startDate.ToString("yyyy/MM/dd"), _endDate.ToString("yyyy/MM/dd")); } }

        public override Expression<Func<Order, bool>> predicate
        {
            get
            {
                return o => o.OrderDate >= _startDate && o.OrderDate <= _endDate;
            }
        }
    }
    public class OrderWithDiscountAppliedSpecification : Specification<Order>
    {
        private float _minimumDiscounted;
        private short _minimumQuantity;

        public OrderWithDiscountAppliedSpecification(float pMinimumDiscounted, short pMinimumQuantity)
        {
            _minimumDiscounted = pMinimumDiscounted;
            _minimumQuantity = pMinimumQuantity;
        }

        public override string Description { get { return string.Format("orders with minimum discount of {0}€ and minimum quantity of {1} units ordered", _minimumDiscounted, _minimumQuantity); } }

        public override Expression<Func<Order, bool>> predicate
        {
            get
            {
                return (o => o.Order_Details.Discount >= _minimumDiscounted && o.Order_Details.Quantity >= _minimumQuantity);
            }
        }
    }
}
References

[1] Refer to this article  for custom API implementation.