如何为未知数量的过滤器构建带有动态比较运算符的表达式?

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

我是表达构建的新手,所以请多多包涵。

我正在 this answer, using PredicateBuilder 的基础上构建一个具有未知数量过滤器的谓词。假设我有这些 类:

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

其中 FilterTerm.ComparisonOperator[A|B|C] 可以是 >, >=, <, <=, == 中的任何一个,用户提交 N FilterTerms 以从数据库中过滤结果(通过 EF 上下文)Items。我的主要查询是这样设置的:

[...]

List<FilterTerm> filterTerms = GetFilterTerms();

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

var items = await itemsQuery.ToListAsync();

[...]

我正在像这样构建基本谓词,但缺少知识(我在循环中使用分配的变量,因此不会在执行时评估值):

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

我不知道 XXXX xxxx 有什么,我更喜欢像 i => i.[Property] 这样的术语选择器(是否可以通过维护查询构建的方式来做到这一点在将它发送到数据库执行之前?),而且我知道 Expression.LessThan 需要 Expression left, Expression right 作为签名,但我不知道如何完成它。

我见过 这样的问题,但我似乎无法弄清楚如何将答案应用到我的用例中。任何人都可以帮助提供缺失的知识吗?经过几个小时的反复试验,我可能只需要用勺子喂我一些东西来连接点……我想我错过了 BinaryExpressionLambda 的一些步骤建筑,但我无法将其拼凑起来。

使用表达式树时有时会让人望而生畏,但您的 use-case 并不太难。您需要的关键是如何填写 LessThan 的参数。简短的版本应该是这样的:

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

这需要您更改参数以匹配指定 属性 的传入 lambda,因此您的方法的完整解决方案如下所示:

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

现在详细介绍我们所做的更改。

首先,我们更改了 GetDynamicPredicate 方法,使第一个参数的类型为 Expression<Func<Item, TValue>>,因为 lambda 需要输入和输出类型。这需要将通用参数 TValue 添加到您的方法中。

接下来我们知道传入的lambda是x => x.Property的形式。我们真正想要的是x => x.Property < value。幸运的是,这是一个非常简单的转换。我们知道 body 是 x.Property 所以我们只需要在调用 LessThan 时使用它,因为这就是我们要比较的对象。

该值很简单:Expression.Constant 对大多数值都适用。

最后,我们需要 predicate 变量来包含一个 lambda 表达式,所以我们需要调用 Expression.Lambda,为了方便从传入的 lambda 中抓取现有参数。