When Linq was initially introduced, I thought it was one of the most useful additions that have ever been made to .NET. I still think that now but after working with Linq for a while I’ve come to the conclusion that it can introduce several problems:
- Queries are scattered all over the code base
- There is no common reusable query mechanism
- Numerous identical or similar queries are repeated (violating DRY – don’t repeat yourself)
- A change to an entity (e.g. renaming a property) can affect many classes which contain queries that use that property
- It is very easy to introduce bugs in Linq queries as it is impossible to just unit test the code which is querying a collection in isolation
This led me to initially conclude that the Specification pattern might be the answer as this enables the ability to define encapsulated, reusable and testable queries. Shown below is the OrderProcess class which initially makes use of simple Linq queries, which I shall use to demonstrate how to refactor to use the Specification pattern.
public class OrderProcess
{
public List<Order> GetCompletedOrders(List<Order> orders)
{
List<Order> completedOrders = orders.Where(order => order.IsCompleted
&& order.Products.Count > 0).ToList();
return completedOrders;
}
public List<Order> GetCustomerOrders(List<Order> orders, Customer customer)
{
List<Order> customerOrders = orders.Where(order => order.Customer.Id.Equals(customer.Id)).ToList();
return customerOrders;
}
public List<Order> GetCustomerCompletedOrders(List<Order> orders, Customer customer)
{
List<Order> customerCompletedOrders = orders.Where(order => order.Customer.Id.Equals(customer.Id)
&& order.IsCompleted
&& order.Products.Count > 0).ToList();
return customerCompletedOrders;
}
}
Shown below is the Specification base class, which all specifications derive from.
public abstract class Specification<Entity>
{
public abstract bool IsSatisfiedBy(Entity candidate);
public Specification<Entity> And(Specification<Entity> specification)
{
return new AndSpecification<Entity>(this, specification);
}
public Specification<Entity> Or(Specification<Entity> specification)
{
return new OrSpecification<Entity>(this, specification);
}
public Specification<Entity> Not(Specification<Entity> specification)
{
return new NotSpecification<Entity>(this);
}
}
To enable the ability to chain specifications using And/Or logic I have implemented specifications which enable this functionality, below is the AndSpecification class.
public class AndSpecification<Entity> : Specification<Entity>
{
private readonly Specification<Entity> leftSide;
private readonly Specification<Entity> rightSide;
public AndSpecification(Specification<Entity> leftSide, Specification<Entity> rightSide)
{
this.leftSide = leftSide;
this.rightSide = rightSide;
}
public override bool IsSatisfiedBy(Entity candidate)
{
return leftSide.IsSatisfiedBy(candidate) && rightSide.IsSatisfiedBy(candidate);
}
}
Next I needed to implement the specifications that encapsulate the different queries that can be performed against the Order entity. Shown below is the OrderProductCountGreaterThanSpecification class which is responsible for ensuring that Orders have more than the specified number of products.
public class OrderProductCountGreaterThanSpecification : Specification<Order>
{
private readonly int productCount;
public OrderProductCountGreaterThanSpecification(int productCount)
{
this.productCount = productCount;
}
public override bool IsSatisfiedBy(Order candidate)
{
return candidate.Products.Count > productCount;
}
}
Below is the OrderProcess class which has been refactored to make use of the new specification classes.
public class OrderProcess
{
public List<Order> GetCompletedOrders(List<Order> orders)
{
List<Order> completedOrders = orders.Where(order => new OrderProductCountGreaterThanSpecification(0)
.And(new OrderIsCompletedSpecification())
.IsSatisfiedBy(order)).ToList();
return completedOrders;
}
public List<Order> GetCustomerOrders(List<Order> orders, Customer customer)
{
List<Order> customerOrders = orders.Where(order => new OrderCustomerSpecification(customer)
.IsSatisfiedBy(order)).ToList();
return customerOrders;
}
public List<Order> GetCustomerCompletedOrders(List<Order> orders, Customer customer)
{
List<Order> customerCompletedOrders = orders.Where(order => new OrderProductCountGreaterThanSpecification(0)
.And(new OrderIsCompletedSpecification())
.And(new OrderCustomerSpecification(customer))
.IsSatisfiedBy(order)).ToList();
return customerCompletedOrders;
}
}
Once I had done this I was still not 100% satisfied, as I found the code hard to read, bloated and the intent of the queries is not obvious without understanding exactly what each specification is doing. What I really wanted was something that had a fluent API and therefore is very easy to see what the intent of the query is, but still had the features offered by the Specification pattern (e.g. encapsulation/reusable and the ability to chain specifications using And/Or).
So after much reading/surfing I came to the conclusion that expression trees offered the functionality I required, as they have the ability to create queries on the fly. So I created a specification which used expression trees to build up and execute the required query this class OrderSpecification is shown below.
public class OrderSpecification : Specification<Order>
{
private Expression<Func<Order, bool>> expression;
public OrderSpecification WithIsCompleted()
{
addExpression(order => order.IsCompleted);
return this;
}
public OrderSpecification WithMoreProductsThan(int count)
{
addExpression(order => order.Products.Count > count);
return this;
}
public OrderSpecification WithCustomer(Customer customer)
{
addExpression(order => order.Customer.Id.Equals(customer.Id));
return this;
}
private void addExpression(Expression<Func<Order, bool>> expression)
{
if (this.expression == null)
{
this.expression = expression;
}
else
{
this.expression = ConcatenateExpressions(expression);
}
}
private Expression<Func<Order, bool>> ConcatenateExpressions(Expression<Func<Order, bool>> rightSide)
{
// Create an invocation expression (expression that applies a lambda to a list of expressions argument)
InvocationExpression rightSideInvoke = Expression.Invoke(rightSide, expression.Parameters.Cast<Expression>());
// Create a binary operation between the two expressions
BinaryExpression binaryExpression = Expression.MakeBinary(ExpressionType.AndAlso, expression.Body, rightSideInvoke);
// Create the lambda
return Expression.Lambda<Func<Order, bool>>(binaryExpression, expression.Parameters);
}
public override bool IsSatisfiedBy(Order candidate)
{
return expression.Compile().Invoke(candidate);
}
}
The final version of the OrderProcess class which makes use of this new specification is shown below.
public class OrderProcess
{
public List<Order> GetCompletedOrders(List<Order> orders)
{
List<Order> completedOrders = orders.Where(order => new OrderSpecification()
.WithIsCompleted()
.WithMoreProductsThan(0)
.IsSatisfiedBy(order)).ToList();
return completedOrders;
}
public List<Order> GetCustomerOrders(List<Order> orders, Customer customer)
{
List<Order> customerOrders = orders.Where(order => new OrderSpecification()
.WithCustomer(customer)
.IsSatisfiedBy(order)).ToList();
return customerOrders;
}
public List<Order> GetCustomerCompletedOrders(List<Order> orders, Customer customer)
{
List<Order> customerCompletedOrders = orders.Where(order => new OrderSpecification()
.WithIsCompleted()
.WithCustomer(customer)
.WithMoreProductsThan(0)
.IsSatisfiedBy(order)).ToList();
return customerCompletedOrders;
}
}
As you can see the OrderProcess class is now much more readable and the intention of the queries is now clear. Additionally as the OrderSpecification inherits from the Specification base class it can be used in conjunction with other specifications for example.
orders.Where(order => new OrderSpecification()
.WithCustomer(customer)
.And(new OrderProductCountGreaterThanSpecification(10))
.IsSatisfiedBy(order)).ToList();