0

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 aToExpression()` 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](Expression1 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.AndSpecification1.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.WhereSelectListIterator2.MoveNext()\r\n at System.Collections.Generic.List1..ctor(IEnumerable1 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, IRepositoryCollection1 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](Func1 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()

Graham
  • 1,497
  • 2
  • 16
  • 36
  • 1
    Take a look at https://stackoverflow.com/questions/6736505/how-to-combine-two-lambdas/6736589#6736589 – Raphaël Althaus Nov 29 '17 at 10:37
  • Is it an error or an exception? Which line of code causes it? Also a tip - if possible, *Compile* should be called only once. – IS4 Nov 29 '17 at 10:37
  • Sorry, it's a `System.InvalidOperationException`. Question updated. – Graham Nov 29 '17 at 10:51
  • 1
    In your `AndSpecification.ToExpression()` both left and right expression have their own parameters. When you combine them, you are using parameter from left expression in final lambda, so parameter from right expression is dangling and you have this exception. Link in first comment has all information you need to fix that. – Evk Nov 29 '17 at 11:04
  • Should you return _assignment.Students.Contains(x); in your expression? – Steve Ford Nov 29 '17 at 14:03

2 Answers2

0

Using the code here https://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/ you can create a AndSpecification that correctly combines two Expressions

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();

        return PredicateBuilder.And(leftExpression, rightExpression);
    }
}

This will fix them so the bodies of the expressions will use the same parameter and should avoid you error.

Mant101
  • 2,705
  • 1
  • 23
  • 27
0

Like @Raphaël and @Evk suggested:

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 paramExpr = Expression.Parameter(typeof(T));

        var andExpression = Expression.AndAlso(
            leftExpression.Body, rightExpression.Body);

        andExpression = (BinaryExpression) new ParameterReplacer(paramExpr).Visit(andExpression);

        return Expression.Lambda<Func<T, bool>>(andExpression, paramExpr);
    }
}
public class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression _parameter;

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return base.VisitParameter(_parameter);
    }

    internal ParameterReplacer(ParameterExpression parameter)
    {
        _parameter = parameter;
    }
}
TheRock
  • 1,513
  • 1
  • 18
  • 19