将 List<Expression<Func<T, bool>>> 组合到 OR 子句 LINQ [重复]

Posted

技术标签:

【中文标题】将 List<Expression<Func<T, bool>>> 组合到 OR 子句 LINQ [重复]【英文标题】:Combine List<Expression<Func<T, bool>>> to an OR clause LINQ [duplicate] 【发布时间】:2015-06-16 09:43:30 【问题描述】:

在此示例中,我有一个人员列表,其中包含一些随机数据,这些数据被许多选项过滤。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public class Program

    public static void Main()
    
        var people = GetPeople();       
        ConsolePeople(GetPeopleFiltered(GetFilters(new FilterRequest Male = true), people));
        ConsolePeople(GetPeopleFiltered(GetFilters(new FilterRequest Female= true), people));
        ConsolePeople(GetPeopleFiltered(GetFilters(new FilterRequest Male = true, TwentyToThirty = true),people));
        ConsolePeople(GetPeopleFiltered(GetFilters(new FilterRequest Male = true, Female=true, TwentyToThirty = true),people));
    

    public static void ConsolePeople(List<Person> people)
    
        if(people.Count == 0)
            Console.WriteLine("No people found");
        foreach(var person in people)
        
            Console.WriteLine(string.Format("FirstName: 0, LastName: 1, Age: 2, Gender: 3", person.FirstName, person.LastName, person.Age, person.Gender.ToString()));
        
        Console.WriteLine(string.Empty);
    

    public static List<Person> GetPeople()
    
        var people = new List<Person>();
        people.Add(new Person  FirstName = "Philip", LastName = "Smith", Age = 29, Gender = GenderEnum.Male);
        people.Add(new Person  FirstName = "Joe", LastName = "Blogs", Age = 40, Gender = GenderEnum.Male);        
        people.Add(new Person  FirstName = "Mary", LastName = "Ann", Age = 10, Gender = GenderEnum.Female);
        people.Add(new Person  FirstName = "Lisa", LastName = "Dunn", Age = 60, Gender = GenderEnum.Male);
        people.Add(new Person  FirstName = "Terry", LastName = "Banks", Age = 89, Gender = GenderEnum.Male);
        people.Add(new Person  FirstName = "John", LastName = "Doe", Age = 32, Gender = GenderEnum.Male);
        people.Add(new Person  FirstName = "Sally", LastName = "Shields", Age = 19, Gender = GenderEnum.Female);
        return people;
    

    public static List<Expression<Func<Person, bool>>> GetFilters(FilterRequest request)
    
        var filters = new List<Expression<Func<Person, bool>>>();
        if(request.Male)
            filters.Add(x=>x.Gender == GenderEnum.Male);
        if(request.Female)
            filters.Add(x=>x.Gender == GenderEnum.Female);
        if(request.TentoTwenty)
            filters.Add(x=>x.Age >= 10 && x.Age < 20);
        if(request.TwentyToThirty)
            filters.Add(x=>x.Age >= 20 && x.Age < 30);
        if(request.ThirtyToFourty)
            filters.Add(x=>x.Age >= 30 && x.Age < 40);
        if(request.FourtyPlus)
            filters.Add(x=>x.Age >= 40);
        return filters;
    

    public static List<Person> GetPeopleFiltered(List<Expression<Func<Person,bool>>> filters, List<Person> people)
    
        var query = people.AsQueryable();
        foreach(var filter in filters)
        
            query = query.Where(filter);
        
        return query.ToList();
    


public class FilterRequest

    public bool Male get;set;
    public bool Female get;set;
    public bool TentoTwenty get;set;
    public bool TwentyToThirty get;set;
    public bool ThirtyToFourty get;set;
    public bool FourtyPlus get;set;


public class Person

    public string FirstName get;set;
    public string LastName get;set;
    public int Age get;set;
    public GenderEnum Gender get;set;


public enum GenderEnum

    Male,
    Female

你可以在DotNetFiddle看到这个

我希望我的List&lt;Expression&lt;Func&lt;Person, bool&gt;&gt;&gt; 成为 || 的列表某些情况下的条款。因此,在此示例中,如果您同时选择了男性和女性以及年龄范围,那么我希望

(x.Gender == GenderEnum.Male || x.Gender == GenderEnum.Female) 
&& ((x.Age > 10 && x.Age < 20) || (x.Age >= 20 && x.Age < 30))

我如何实现这一目标?我知道这个例子可以以不同的方式重新设计,但这只是一个例子。

注意:真正的代码将处理数百万行信息,因此应该进行相当优化。

【问题讨论】:

你应该看看谓词生成器这样的事情:albahari.com/nutshell/predicatebuilder.aspx 我建议编写这样的函数:static IEnumerable&lt;Person&gt; MyWhere(IEnumerable&lt;Person&gt; dataSource, Func&lt;Person, bool&gt; predicate) 现在,您可以通过这种方式传递参数:var qry = MyWhere(Persons, p =&gt; p.FirstName.Contains("ee") || p.LastName.Contains("ic") || p.Age &gt; 21) 这个例子并没有说得很清楚,但是这样做会导致大量代码计算出||的所有可能性。 .作为参考,这应该将 MVC 视图中的 SearchRequest 转换为数据返回。搜索的结构不是很友好(客户决定这样做,再多的抱怨也不会改变他们),这导致了许多代表多个搜索词的布尔值。 这是重复的,另一篇文章中给出的答案确实有效。我给出的工作示例是这个dotnetfiddle.net/4mumPD,它显然可以整理得更通用。答案来自albahari.com/nutshell/predicatebuilder.aspx 【参考方案1】:

这是一个PredicateBuilder 的实现,它能够将Or 两个表达式组合在一起:

public static class PredicateBuilder

    public static Expression<Func<T, bool>> True<T>()  return f => true; 
    public static Expression<Func<T, bool>> False<T>()  return f => false; 

    public static Expression<Func<T, bool>> Or<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    
        var secondBody = expr2.Body.Replace(
            expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
    

    public static Expression<Func<T, bool>> And<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    
        var secondBody = expr2.Body.Replace(
            expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
    


public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)

    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);

internal class ReplaceVisitor : ExpressionVisitor

    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    
        this.from = from;
        this.to = to;
    
    public override Expression Visit(Expression node)
    
        return node == from ? to : base.Visit(node);
    

这允许你写:

var predicate = listOfPredicateExpressions.Aggregate(PredicateBuilder.Or);

【讨论】:

像魅力一样工作【参考方案2】:

您需要做的是对同一类别的过滤器进行分组。例如,两个性别过滤器应该是 or'ed,而一个性别过滤器和一个年龄过滤器应该是 and'ed。因此,您将需要替换返回过滤器的方法以返回可枚举的过滤器枚举。每个可枚举表示您可以拥有的一类过滤器。

在你上面给出的例子中,你会得到一个看起来像这样的对象:


    
        x => x.Gender == GenderEnum.Male,
        x => x.Gender == GenderEnum.Female
    ,
    
        x => x.Age >= 10 && x.Age < 20,
        x => x.Age >= 20 && x.Age < 30
    

然后您的查询将变为以下方法:

public static List<Person> GetPeopleFiltered(IEnumerable<IEnumerable<Func<Person,bool>>> filterCategories, List<Person> people)

    var query = people;
    foreach(var filterCat in filterCategories)
    
        query = query.Where(x => filterCat.Any(f => f(x)));
    
    return query.ToList();

或者,也摆脱外部的 foreach 循环:

public static List<Person> GetPeopleFiltered(IEnumerable<IEnumerable<Func<Person,bool>>> filterCategories, List<Person> people)

    return people.Where(x => filterCategories.All(cat => cat.Any(f => f(x)))).ToList();

Any 方法遍历集合的所有元素,如果遇到返回 true 的元素,则返回 true。 All 做同样的事情,但如果它遇到一个产生 false 的元素,则返回 false。

【讨论】:

我已将其添加到示例 dotnetfiddle.net/Z1UqBo 中,但我似乎无法让它工作。只是抱怨 f 像方法一样使用 我很抱歉。如果您采用我建议的方法,您将不得不使用裸Func,而不是将它们包装在Expressions 中(在这种情况下,也不再需要将数据转换为可查询)。我已经改变了我的例子以考虑到这一点。如果您仍想使用Expressions,则必须首先遍历所有过滤器类别,将它们转换为每个类别的一个表达式(因此将所有被或的事物分组到一个表达式中),然后采用以前的方法.

以上是关于将 List<Expression<Func<T, bool>>> 组合到 OR 子句 LINQ [重复]的主要内容,如果未能解决你的问题,请参考以下文章

Expression 转化为sql --自定义函数

Codeforces Round #451 (Div. 2) F Restoring the Expression

C# 如何将 Expression<Func<SomeType>> 转换为 Expression<Func<OtherType>>

Media Queries Module媒体查询

java 泛型报错 Type safety: The expression of type List needs unchecked conversion to conform to Lis

CSS3及JS媒体查询教程