'How can I build an Expression with dynamic comparison operators for an unknown number of filters?

I'm new to expression building, so bear with me a little bit.

I'm building off of this answer, using PredicateBuilder for building a predicate with an unknown number of filters. Say I have these classes:

public FilterTerm
{
    public string ComparisonOperatorA { get; set; }
    public decimal ValueA { get; set; }
    public string ComparisonOperatorB { get; set; }
    public decimal ValueB { get; set; }
    public string ComparisonOperatorC { get; set; }
    public decimal ValueC { get; set; }
}

public Item
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal SkillLevelA { get; set; }
    public decimal SkillLevelB { get; set; }
    public decimal SkillLevelC { get; set; }
}

where FilterTerm.ComparisonOperator[A|B|C] can be any of >, >=, <, <=, ==, which a user submits N FilterTerms to filter results from the db (via EF context) Items. My main query is set up this way:

[...]

List<FilterTerm> filterTerms = GetFilterTerms();

var itemsQuery = context.GetAll<Item>(); // reutrns IQueryable<Item>
itemsQuery = itemsQuery.ApplyFilters(filterTerms);

var items = await itemsQuery.ToListAsync();

[...]

I'm building the base predicate like this, with missing knowledge (I'm using assigned variables in the loop so values aren't evaluated at execution time):

private IQueryable<Item> ApplyFilters(IQueryable<Item> query, FilterTerms filterTerms)
{ 
    Expression<Func<Item, bool>> superPredicate = PredicateBuilder.False<Item>()
    foreach (var filterTerm in filterTerms)
    {
        Expression<Func<Item, bool>> subPredicate = PredicateBuilder.True<Item>();

        subPredicate = subPredicate.And<Item>(GetDynamicPredicate(i => i.SkillLevelA, filterTerm.ValueA, filterTerm.ComparisonOperatorA));
        subPredicate = subPredicate.And<Item>(GetDynamicPredicate(i => i.SkillLevelB, filterTerm.ValueB, filterTerm.ComparisonOperatorB));
        subPredicate = subPredicate.And<Item>(GetDynamicPredicate(i => i.SkillLevelC, filterTerm.ValueC, filterTerm.ComparisonOperatorC));

        superPredicate = superPredicate.Or(subPredicate);
    }
    
    return query.Where(superPredicate);
}

private Expression<Func<Item, bool>> GetDynamicPredicate(XXXX xxxx, decimal value, string comparisonOperator)
{
    Expression<Func<Item, bool>> predicate;

    switch (comparisonOperator)
    {
        case "<":
            predicate = Expression.LessThan(xxxx, value);
            break;
        [...]
    }

    return predicate;
}

where I don't know what to have for XXXX xxxx, which I prefer to have as a term selector like i => i.[Property] (is it possible to do this in a way that maintains Query building before sending it to the DB for execution?), and I know that the Expression.LessThan requires Expression left, Expression right for the signature, but I'm stuck with how to complete this.

I've seen questions like this one, but I just I can't seem to figure out how to apply the answers to my use case. Can anyone help provide the missing knowledge? It's been a few hours of trial-and-error, and I may just need something spoon-fed to me to connect the dots... I think I'm missing some steps with BinaryExpression and Lambda building, but I just can't piece it together.



Solution 1:[1]

It can sometimes get a bit daunting when working with expression trees, but your use-case is not too hard. The key bit you need is how to fill in the arguments to LessThan. The short version is that it should look like this:

predicate = Expression.Lambda<Func<Item, bool>>(
    Expression.LessThan(property.Body, Expression.Constant(value)), property.Parameters[0]);

That requires you to change your parameter to match the incoming lambda which specifies a property, so the complete solution is for your method to look like:

private Expression<Func<Item, bool>> GetDynamicPredicate<TValue>(
    Expression<Func<Item, TValue>> property, decimal value, string comparisonOperator)
{
    Expression<Func<Item, bool>> predicate = null;

    switch (comparisonOperator)
    {
        case "<":
            predicate = Expression.Lambda<Func<Item, bool>>(
                Expression.LessThan(property.Body, Expression.Constant(value)),
                property.Parameters[0]);
            break;
    }

    return predicate;
}

Now for a breakdown of the changes we made.

First we changed the GetDynamicPredicate method so that the type of the first parameter is Expression<Func<Item, TValue>>, since a lambda requires an input and output type. That required adding the generic parameter TValue to your method.

Next we know that the incoming lambda is of the form x => x.Property. What we actually want is x => x.Property < value. Fortunately that's a really simple transformation. We know that the body is x.Property so we simply have to use that when calling LessThan, as that is what we're comparing against.

The value is easy: Expression.Constant will work just fine for most values.

Finally, we need the predicate variable to contain a lambda expression, so we need to call Expression.Lambda, grabbing the existing parameter from the incoming lambda for convenience.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kirk Woll