如何将多个 Expression<Func<T, bool>> 组合成单个表达式以针对 DbContext 执行?

How to combine multiple Expression<Func<T, bool>> into a single expression to execute against DbContext?

我正在尝试创建一种方法来允许我的应用程序的用户创建他们自己的数据过滤视图。为此,我正在编写一个 Key-Operator-Value 对列表,然后在多个步骤之后尝试将其解析为表达式以用于 Where 调用。到目前为止,我可以创建表达式,但我不知道如何将它们合并为一个表达式。

有人可以指出正确的方向吗?通过在线阅读类似的帖子,我决定尝试使用 LINQKit PredicateBuilder 来完成它,但我得到的只是初始起始谓词。这是我到目前为止的代码,它绝不是最终的,我只是在 LINQPad 中制作原型:

void Main() {
    //  1. Create form post object
    var query = new QueryEdit {
        Filters = new List<Filter> {
            new Filter {
                Id = 1,
                Key = "Name",
                Operator = "=",
                Value = "New York"
            },
            new Filter {
                Id = 2,
                Key = "Name",
                Operator = "!=",
                Value = "Boston"
            },
            new Filter {
                Id = 3,
                Key = "Name",
                Operator = "=",
                Value = "Washington"
            }
        },
        FilterClause = "1 AND 3 OR 2 OR 4"
    };

    //  2. Compose filter groups
    var filterGroups = GetFilterGroups(query);

    //  3. Compose expression groups
    var expressionGroups = GetExpressionGroups<Region>(filterGroups);

    expressionGroups.Dump();

    //  4. Compose full expression
    var expression = GetExpression(expressionGroups);

    expression.Dump();

    //  5. Serialize expression to blob
    //  6. Deserialize blob to expression
    //  7. Execute expression against db
}

static IEnumerable<FilterGroup> GetFilterGroups(
    QueryEdit query) {
    //  The parentheses are meaningless, so they need to be removed.
    //  The groups are all conditions with ANDs, separated by ORs.

    return query.FilterClause.Replace(")", string.Empty).Replace("(", string.Empty).Split(new[] { "OR" }, StringSplitOptions.None).Select(
        fc => new FilterGroup {
            Filters = fc.Split(new[] { "AND" }, StringSplitOptions.None).Select(
                i => {
                    var id = Convert.ToByte(i);

                    return query.Filters.SingleOrDefault(
                        f => f.Id == id);
                }).Where(
                f => f != null).OrderBy(
                f => f.Id)
        }).Where(
        fg => fg.Filters.Any());
}

static IEnumerable<IEnumerable<Expression<Func<T, bool>>>> GetExpressionGroups<T>(
    IEnumerable<FilterGroup> filterGroups) {
    var type = typeof(T);
    var parameter = Expression.Parameter(type);

    return filterGroups.Select(
        fg => {
            return fg.Filters.Select(
                f => {
                    var left = Expression.Property(parameter, type.GetProperty(f.Key));
                    var right = Expression.Constant(f.Value);
                    var body = GetExpressionBody(f.Operator, left, right);

                    if (body == null) {
                        return null;
                    }

                    return Expression.Lambda<Func<T, bool>>(body, parameter);
                }).Where(
                e => e != null);
        });
}

static object GetExpression<T>(
    IEnumerable<IEnumerable<Expression<Func<T, bool>>>> expressionGroups) {
    var predicate = PredicateBuilder.True<T>();

    foreach (var expressionGroup in expressionGroups) {
        var expression = GetPredicateForGroup<T>(expressionGroup);

        predicate.Or(expression);
    }

    return predicate;
}

static Expression<Func<T, bool>> GetPredicateForGroup<T>(
    IEnumerable<Expression<Func<T, bool>>> expressionGroup) {
    var predicate = PredicateBuilder.True<T>();

    foreach (var expression in expressionGroup) {
        predicate.And(expression);
    }

    return predicate;
}

static Expression GetExpressionBody(
    string @operator,
    Expression left,
    Expression right) {
    switch (@operator) {
        case "=":
            return Expression.Equal(left, right);
        case "!=":
            return Expression.NotEqual(left, right);
        case ">":
            return Expression.GreaterThan(left, right);
        case ">=":
            return Expression.GreaterThanOrEqual(left, right);
        case "<":
            return Expression.LessThan(left, right);
        case "<=":
            return Expression.LessThanOrEqual(left, right);
        default:
            return null;
    }
}

sealed class QueryEdit {
    public IEnumerable<Filter> Filters { get; set; }
    public string FilterClause { get; set; }
    public int Id { get; set; }
    public string Name { get; set; }
}

sealed class Filter {
    public byte Id { get; set; }
    public string Key { get; set; }
    public string Operator { get; set; }
    public object Value { get; set; }
}

sealed class FilterGroup {
    public IEnumerable<Filter> Filters { get; set; }
}

我也乐于接受有关如何实现此目标或整体改进的其他建议。提前致谢!

您可以考虑使用规范模式来组合谓词。我发现这非常有帮助 - http://gurmitteotia.blogspot.co.uk/2015/06/specification-pattern-with-repository.html#more。他的博客包括链接谓词表达式和一般模式的扩展方法,它基于 EF。

尝试

predicate = predicate.Or(expression) 

predicate = predicate.And(expression)