简体   繁体   中英

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; }
}

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?

Based on JonSkeet's Answer to this post using an amazing method PredicateBuilder JonSkeet suggested from the LINQKit library I came up with the following simple solution:

public static IQueryable<FileColumnRecords> DynamicColumnQuery
                                (this IQueryable<FileColumnRecords> query, 
                                List<QueryColumn> querycolumns)
{
    var predicate = PredicateBuilder.False<FileColumnRecords>();
    foreach (QueryColumn columnQuery in querycolumns)
    {
        string temp = columnQuery.queryTerm.ToLower();
        predicate = predicate.Or(d => d.fcv.Any(f => (f.value != null ?
                                 f.value.ToLower().Contains(temp) : false));
    }

    return query.AsExpandable().Where(predicate);
}

If you know a little bit about writing expression trees then you're immediately going to appreciate how incredibly AWESOME this is. Plus, note how I have embedded parameters in my predicate.

I am in no way affiliated with the developers of LINQKit nor Joseph or Ben Albahari nor O'Reilly publisher. Just an incredibly satisfied developer who thought I should share the awesomeness of what they built.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM