Search code examples
c#linqexpression-treesexpressionvisitor

Parameter Replacement when the parameter is an complex object


I'm trying to create a dynamic AndAlso filter that will be used in a Where method to a LINQ-to-EF query:

query.Where(filterExpression)

where filterExpression is a compiled lambda

So far I've implemented a loop with some fancy usage to check for cases where there is only one, two, or more queries.

Expression selectLeft = null;
Expression selectRight = null;
Expression filterExpression = null;

foreach (QueryColumn columnQuery in querycolumns)
{
    Expression<Func<FileColumnRecords, bool>> 
                columnPredicate = d => d.fcv.Any(f => (f.value != null ? 
                                  f.value.ToLower().Contains(columnQuery.queryTerm.ToLower()) :
                                  false));

    if (selectLeft == null)
    {
        selectLeft = columnPredicate.Body;
        filterExpression = selectLeft;
        continue;
    }
    if (selectRight == null)
    {
        selectRight = columnPredicate.Body;
        filterExpression =
        Expression.AndAlso(selectLeft, selectRight);
        continue;
    }

    filterExpression =
            Expression.AndAlso(filterExpression, columnPredicate.Body);
}

Then I set up a ParameterReplacer to ensure all iterations of my expression parameter get the same reference:

ParameterExpression param = Expression.Parameter(typeof(FileColumnRecords), "p");
ParameterReplacer replacer = new ParameterReplacer(param);
filterExpression = replacer.Visit(filterExpression);

which is built from:

class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression parameter;

    internal ParameterReplacer(ParameterExpression parameter)
    {
        this.parameter = parameter;
    }

    protected override Expression VisitParameter
        (ParameterExpression node)
    {
        return parameter;
    }
}

(ParameterReplacer class courtesy of JonSkeet)

When run the ParameterReplacer I get the following error:

"Property 'System.String value' is not defined for type 'Models.FileColumnRecords'"

where FileColumnRecords is defined as:

public class FileColumnRecords
{       
    public Documents doc;

    public IEnumerable<FileColumnValues> fcv;
}

and FileColumnValues as:

public partial class FileColumnValues
{
    public long ID { get; set; }
    public long CNID { get; set; }
    public long fileID { get; set; }
    public string value { get; set; }
}

Solution

  • You're running into difficulties due to there being parameters embedded within your expression tree, I suspect. The replacer should only replace the top-level parameters.

    You could pass the top-level expressions into ParameterReplacer:

    class ParameterReplacer : ExpressionVisitor
    {
        private readonly ParameterExpression target;
        private readonly ISet<ParameterExpression> sources;
    
        internal ParameterReplacer(ParameterExpression target,
                                   IEnumerable<LambdaExpression> expressions)
        {
            this.target = target;
            sources = new HashSet<ParameterExpression>(
                expressions.SelectMany(e => e.Parameters));
        }
    
        protected override Expression VisitParameter
            (ParameterExpression node)
        {
            return sources.Contains(node) ? target : node;
        }
    }
    

    You'd then need to change the rest of your code to build up the list of expression trees you've been combining. There are definitely more elegant ways of doing it, but I think that should work. That's just off the top of my head though - I haven't tried it.

    Having said all this, are you sure you can't just use PredicateBuilder instead?