c# - 将 List<Expression<Func<T, bool>>> 组合到 OR 子句 LINQ

标签 c# linq linq-to-entities

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

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))

我该如何实现这一目标?我知道这个例子可以进行不同的修改,但这只是一个例子。

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

最佳答案

下面是一个 PredicateBuilder 的实现,它能够对两个表达式进行运算:

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

关于c# - 将 List<Expression<Func<T, bool>>> 组合到 OR 子句 LINQ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29565373/

相关文章:

c# - 使用 AutoMapper 将 ICollection<Model> 映射到 List<ViewModel>

c# - 将 SQL 查询转换为 LINQ

c# - 在EF中动态设置表名

c# - 查找行中任何单词的出现次数

c# - 类似于 Entity Framework 中的 "Coalesce"的表达式

c# - 从数据表中删除与 List<string> 匹配的行

linq-to-entities - 如何制作 "Except"LINQ to Entities查询?

c# - 防止在 Entity Framework 上的 TPT 继承中加载子类表行

c# - 如何在没有代码隐藏的情况下处理我的 ViewModel 中的 WPF 路由命令?

c# - xamarin.android 接收器出现 BOOT_COMPLETED 错误