Search code examples
c#.netentity-frameworklinqlinq-to-entities

Created predicate doesn't work in Linq to Entity Framework Where clause


I'd like to move predicate building logic into a base class and am getting the error “The LINQ expression node type 'Invoke' is not supported in LINQ to Entities”. I want to be able to concatenate or conditionally chain the expressions in the predicate.

I want to be able to pass in the part of the predicate that is unique to the caller which is the property names used (the GetFilterPredicate will become a generic routine which will operate on types that will have different property names holding the relevant values).

        protected Expression<Func<MyEntity, bool>> GetFilterPredicate(
        PagingParameters pagingParameters,
        Func<MyEntity, DateTime?> terminationDate,
        Func<MyEntity, string> entityID,
        Func<MyEntity, string> name
        )
    {
        Expression<Func<MyEntity, bool>> validFilter = x => true;

        if (pagingParameters.Active == true)
        {
            validFilter = x => terminationDate(x) > DateTime.Now;
        }

        ///more conditions added here

        return validFilter;
    }

    protected List<MyEntity> Query(IQueryable<MyEntity> source)
    {
        var filters = GetFilterPredicate(
            new PagingParameters() { Active = true }
            , i => i.TerminationDate
            , i => i.EntityID
            , i => i.Name
            );

        return source.Where(filters).AsNoTracking().ToList<MyEntity>();
    }

Solution

  • You can't construct your new expression using the delegate terminationDate you need to change terminationDate to be an Expression and use it to build a new expression manually.

    protected static  Expression<Func<MyEntity, bool>> GetFilterPredicate(
        PagingParameters pagingParameters,
        Expression<Func<MyEntity, DateTime?>> terminationDate,
        Expression<Func<MyEntity, string>> entityID,
        Expression<Func<MyEntity, string>> name
    )
    {
        Expression<Func<MyEntity, bool>> validFilter = x => true;
        // We need to replace the parameter for all expressions with 
        // a common single parameter. I used the parameter for the default
        // filter but a new Parameter expression would have worked as well.
        // If you don't do this you will get an error (unbound parameter or something like that ) because the parameter  
        // used in the expressions (terminationDate, entityID) will be 
        // different then the parameter used for the new validFilter expression
        var parameterReplacer = new ReplaceVisitor
        {
            NewParameter = validFilter.Parameters.First()
        };
    
        if (pagingParameters.Active == true)
        {
            validFilter = Expression.Lambda<Func<MyEntity, bool>>(
                Expression.GreaterThan
                (
                    parameterReplacer.Visit(terminationDate.Body),
                    Expression.Convert(Expression.Property(null, typeof(DateTime), "Now"), typeof(DateTime?))
                ),
                parameterReplacer.NewParameter
            );
        }
    
        // existing filter && x.EntityId != "A"
        validFilter = Expression.Lambda<Func<MyEntity, bool>>(
                Expression.And(
                    validFilter.Body,
                    Expression.NotEqual
                    (
                        parameterReplacer.Visit(entityID.Body),
                        Expression.Constant("A")
                    )
                ),
                parameterReplacer.NewParameter
            );
    
        return validFilter;
    }
    /// <summary>
    /// Simple Parameter Replacer, will replace the any parameter with the new
    /// parameter. You will need to refine this if your expressions have nested 
    /// lambda, in that you will need to only replace the top most lambda 
    /// parameter, but for simple expressions it will work fine.
    /// </summary>
    class ReplaceVisitor : ExpressionVisitor
    {
        public ParameterExpression NewParameter { get; set; }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return this.NewParameter;
        }
    }