将 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<Expression<Func<Person, bool>>>
成为 || 的列表某些情况下的条款。因此,在此示例中,如果您同时选择了男性和女性以及年龄范围,那么我希望
(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<Person> MyWhere(IEnumerable<Person> dataSource, Func<Person, bool> predicate)
现在,您可以通过这种方式传递参数:var qry = MyWhere(Persons, p => p.FirstName.Contains("ee") || p.LastName.Contains("ic") || p.Age > 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
,而不是将它们包装在Expression
s 中(在这种情况下,也不再需要将数据转换为可查询)。我已经改变了我的例子以考虑到这一点。如果您仍想使用Expressions
,则必须首先遍历所有过滤器类别,将它们转换为每个类别的一个表达式(因此将所有被或的事物分组到一个表达式中),然后采用以前的方法.以上是关于将 List<Expression<Func<T, bool>>> 组合到 OR 子句 LINQ [重复]的主要内容,如果未能解决你的问题,请参考以下文章
Codeforces Round #451 (Div. 2) F Restoring the Expression
C# 如何将 Expression<Func<SomeType>> 转换为 Expression<Func<OtherType>>
java 泛型报错 Type safety: The expression of type List needs unchecked conversion to conform to Lis