代码之家  ›  专栏  ›  技术社区  ›  Gup3rSuR4c

如何将多个表达式组合到单个表达式中,以针对DbContext执行?

  •  0
  • Gup3rSuR4c  · 技术社区  · 7 年前

    我正在尝试创建一种方法,允许我的应用程序的用户创建自己的数据过滤视图。为此,我正在编写一个键-运算符-值对列表,然后在多个步骤后,尝试将其解析为一个表达式,以用于 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; }
    }
    

    我也愿意接受关于如何实现这一目标或整体改进的其他建议。提前感谢!

    2 回复  |  直到 7 年前
        1
  •  1
  •   user3411327    7 年前

    尝试

    predicate = predicate.Or(expression) 
    

    predicate = predicate.And(expression)
    
        2
  •  0
  •   thisextendsthat    7 年前

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