I am using specification classes to encapsulate various queries that are used throughout my domain.
The specifications inherit from a Specification<T>
base class:
public abstract class Specification<T> : ISpecification<T>
where T : class
{
public abstract Expression<Func<T, bool>> ToExpression();
public virtual bool IsSatisfiedBy(T candidate)
{
var predicate = ToExpression().Compile();
return predicate(candidate);
}
public Specification<T> And(Specification<T> specification)
{
return new AndSpecification<T>(this, specification);
}
public Specification<T> Or(Specification<T> specification)
{
return new OrSpecification<T>(this, specification);
}
}
The AndSpecification<T>
is defined as:
public class AndSpecification<T> : Specification<T>
where T : class
{
private readonly Specification<T> _left;
private readonly Specification<T> _right;
public AndSpecification(Specification<T> left, Specification<T> right)
{
_right = right;
_left = left;
}
public override Expression<Func<T, bool>> ToExpression()
{
var leftExpression = _left.ToExpression();
var rightExpression = _right.ToExpression();
var andExpression = Expression.AndAlso(
leftExpression.Body, rightExpression.Body);
var param = leftExpression.Parameters.Single();
return Expression.Lambda<Func<T, bool>>(
andExpression, param);
}
}
Specification<T>
classes can then be chained together, e.g.:
firstSpecification.And(secondSpecification).IsSatisfiedBy(candidate);
An example of a real-life specification class is:
public class IsAssignmentSetForStudentOverdueSpecification : Specification<Student>
{
private readonly Assignment _assignment;
public IsAssignmentSetForStudentOverdueSpecification(Assignment assignment)
{
_assignment = assignment
}
public override Expression<Func<Student, bool>> ToExpression()
{
var isStudentSetAssignment = new IsStudentSetAssignmentSpecification(_assignment);
var hasAssignmentBeenCompletedByStudent = new HasAssignmentBeenCompletedByStudentSpecification(_assignment);
return isStudentSetAssignment.And(hasAssignmentBeenCompletedByStudent).ToExpression();
}
}
IsStudentSetAssignmentSpecification
has it's ToExpression()
method as follows (the real thing is a bit more complicated but it's been simplified for brevity):
public override Expression<Func<Student, bool>> ToExpression()
{
return x => _assignment.Students.Contains(x);
}
'HasAssignmentBeenCompletedByStudentSpecificationhas a
ToExpression()` method as follows:
public override Expression<Func<Student, bool>> ToExpression()
{
return x => _assignment.StudentSubmissions.Contains(x);
}
An example of usage is:
var overdue = new IsAssignmentSetForStudentOverdueSpecification(assignment).IsSatisfiedBy(student);
where assignment
and student
are domain classes. However, when I try an use this, I get the following System.InvalidOperationException
:
variable 'x' of type 'Student' referenced from scope '', but it is not defined
How can I resolve this?
The stack trace is:
at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression
1 node)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)\r\n at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)\r\n at Beehive.Domain.Specifications.AndSpecification
1.ToExpression() in C:\Beehive\2.0\Beehive.Domain\Specifications\AndSpecification.cs:line 35\r\n at Beehive.Domain.Planner.Assignments.Specifications.IsAssignmentSetForStudentOverdueSpecification.ToExpression() in C:\Beehive\2.0\Beehive.Domain\Planner\Assignments\Specifications\IsAssignmentSetForStudentOverdueSpecification.cs:line 28\r\n at Beehive.Domain.Specifications.Specification1.IsSatisfiedBy(T candidate) in C:\\Beehive\\2.0\\Beehive.Domain\\Specifications\\Specification.cs:line 19\r\n at Beehive.Services.Shared.Planner.Assignments.AssignmentToAssignmentListViewMapHelper.Map(Assignment assignment, Student student) in C:\\Beehive\\2.0\\Beehive.Services\\Shared\\Planner\\Assignments\\AssignmentToAssignmentListViewMapHelper.cs:line 22\r\n at Beehive.Services.QueryHandlers.Planner.Assignments.AssignmentsForUserQueryHandler.<>c__DisplayClass5_0.<CreateMap>b__0(Assignment x) in C:\\Beehive\\2.0\\Beehive.Services\\QueryHandlers\\Planner\\Assignments\\AssignmentsForUserQueryHandler.cs:line 70\r\n at System.Linq.Enumerable.WhereSelectListIterator
2.MoveNext()\r\n at System.Collections.Generic.List1..ctor(IEnumerable
1 collection)\r\n at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)\r\n at Beehive.Services.QueryHandlers.Planner.Assignments.AssignmentsForUserQueryHandler.CreateMap(AssignmentToAssignmentListViewMapHelper mapHelper, Student student, IRepositoryCollection
1 collection) in C:\Beehive\2.0\Beehive.Services\QueryHandlers\Planner\Assignments\AssignmentsForUserQueryHandler.cs:line 70\r\n at Beehive.Services.QueryHandlers.Planner.Assignments.AssignmentsForUserQueryHandler.Handle(AssignmentsForUserQuery query) in C:\Beehive\2.0\Beehive.Services\QueryHandlers\Planner\Assignments\AssignmentsForUserQueryHandler.cs:line 59\r\n at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)\r\n at Beehive.DI.QueryProcessor.Process[TResult](IQuery1 query) in C:\\Beehive\\2.0\\Beehive.DI\\QueryProcessor.cs:line 26\r\n at Beehive.API.Controllers.Planner.AssignmentsController.<>c__DisplayClass1_0.<GetAssignments>b__0() in C:\\Beehive\\2.0\\Beehive.API\\Controllers\\Planner\\AssignmentsController.cs:line 26\r\n at Beehive.API.Controllers.ControllerBase.ExecuteQuery[TResult](Func
1 action) in C:\Beehive\2.0\Beehive.API\Controllers\ControllerBase.cs:line 20\r\n at Beehive.API.Controllers.Planner.AssignmentsController.GetAssignments(Guid userId) in C:\Beehive\2.0\Beehive.API\Controllers\Planner\AssignmentsController.cs:line 26\r\n at lambda_method(Closure , Object , Object[] )\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.b__9(Object instance, Object[] methodParameters)\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ApiControllerActionInvoker.d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ExceptionFilterResult.d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Web.Http.Controllers.ExceptionFilterResult.d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()