• C# 动态构建表达式树(一)—— 构建 Where 的 Lambda 表达式


    C# 动态构建表达式树(一)—— 构建 Where 的 Lambda 表达式

    前言

    记得之前同事在做筛选功能的时候提出过一个问题:如果用户传入的条件数量不确定,条件的内容也不确定(大于、小于和等于),能否能够动态拼接成 Linq 后在数据库筛选,当时也没有好的思路。最近看的教程上提到了“动态构建表达式树”,刚好可以解决此类问题。

    准备工作

    环境:.NET Framework 4.5,SQLServer 2017

    建表脚本如下(由 SSMS 导出):

    USE [default]
    GO
    /****** Object:  Table [dbo].[Person]    Script Date: 2021/6/9 12:06:43 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    CREATE TABLE [dbo].[Person](
    	[Id] [varchar](100) NOT NULL,
    	[Name] [nvarchar](50) NOT NULL,
    	[Age] [int] NOT NULL,
    	[Gender] [nvarchar](5) NOT NULL,
    	[Point] [int] NOT NULL,
    	[CreateTime] [datetime] NOT NULL,
     CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED 
    (
    	[Id] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO
    

    表中数据如下:

    动态构建“属性值”比较的表达式

    要查询的为满足:Gender 为“男”的,且 Point 小于 10000的数据

    按照常规写法:

    List<Person> personList = context.Person.Where(p => p.Gender == "男" && p.Point < 10000).ToList();
    

    动态组合的写法:

    ParameterExpression pe = Expression.Parameter(typeof(Person), "p");	# 创建形参 p
    
    MemberExpression meGender = Expression.Property(pe, "Gender");	# 获取 p 的属性 Gender
    BinaryExpression beGenderCondition = Expression.Equal(meGender, Expression.Constant("男")); # 比较
    
    MemberExpression mePoint = Expression.Property(pe, "Point");	# 获取 p 的属性 Point
    BinaryExpression bePointCondition = Expression.LessThan(mePoint, Expression.Constant(10000)); # 比较
    
    BinaryExpression resultCondition = Expression.AndAlso(bePointCondition, beGenderCondition);	# 组合两个条件
    
    Expression<Func<Person, bool>> personFilterExpression = 
    	Expression.Lambda<Func<Person, bool>>(resultCondition, pe);	# 创建最终 lambda 表达式
    List<Person> personList1 = context.Person.Where(personFilterExpression).ToList();	# 执行查询
    

    从上面的代码中可以看出,Expression 类包含了所有有可能的操作。所谓动态组合,就是使用 Expression 类的各种方法,改写原始写法,最终组合形成表达式。如:获取属性时我们使用的“.”(点号),可以通过 Expression.Property 方法来实现,“小于”操作符可以通过 Expression.LessThan 方法来实现。

    动态构建“属性方法”比较的表达式

    要查询的为满足:Gender 是以 “男” 开头的数据(别问为什么有这么奇怪的需求,我一时想不到好的例子了XD)

    按照常规写法:

    List<Person> personList = context.Person.Where(p => p.Gender.StartsWith("男")).ToList();
    

    动态组合的写法:

    ParameterExpression pe = Expression.Parameter(typeof(Person), "p");	# 创建形参 p
    MemberExpression meGender = Expression.Property(pe, "Gender");	# 获取 p 的属性 Gender
    MethodCallExpression mceGender = 
    	Expression.Call(meGender, "StartsWith", null, Expression.Constant("男"));	# 调用 StartsWith 方法
    
    Expression<Func<Person, bool>> personFilterExpression 
    	= Expression.Lambda<Func<Person, bool>>(mceGender, pe);	# 创建最终 lambda 表达式
    List<Person> personList1 = context.Person.Where(personFilterExpression).ToList(); # 执行查询
    

    注意,调用方法时,也需要使用 Expression.Property 获取属性,再参与操作!

    LINQ to Entities 中不能识别的方法(如 DateTime 类型的 ToString 方法)依然不能通过这种方式调用!

    全表查询的问题

    先说结论:向 IQueryable 类型传入 Expression 类型,会通过数据库查询出符合条件的内容并返回;向 IQueryable 类型传入 Func 类型,会查出全表,在程序中过滤后返回。(可以通过 ChangeTracer 中的内容或 SQL 执行情况判断是否全表查询)

    一种表达式原则上应该只有一种类型才对,但这一点似乎对 lambda 表达式不适用。

    # 写法1 数据库全表查询
    Func<Person, bool> func = p => p.Point < 10000 && p.Gender == "男";
    List<Person> funcPersonList = context.Person.Where(func).ToList();
    
    #写法2 数据库按需查询
    Expression<Func<Person, bool>> expression = p => p.Point < 10000 && p.Gender == "男";
    List<Person> expressionPersonList = context.Person.Where(expression).ToList();
    

    这两种写法均不会报错。但注意观察,“写法1” 中的 context.Person 类型已经变为了 IEnumerable,而正常应该是 IQueryable

    至于原因其实也很简单,因为 IQueryable 继承自 IEnumerable,IQueryable.Where 只支持 Expression 作为参数,而 IEnumerable 只支持 Func 作为参数

    如果要将Expression 转换为 Func,可以调用 Expression.Compile 方法。

    后记

    最近在听朝夕教育的体验课,本文的主要的内容也是其中讲动态表达式的内容。其中有个听课的同学提出,传入参数类型为 Func 和 Expression 会有不同的效果,这也给了我很大启发(准确地说是帮我避开了一个大坑),在这里表示感谢。

    因为能力有限,一口实在吃不下太多,因此本文写的主要是向 Where 方法传递 lambda 表达式参数,也算是一个入门了。在后面的内容打算涉及 Select 和 Group 这两个我比较常用的方法了,敬请期待吧。

    参考

    动态构建Expression表达式树

    Func转Expression的方法(C#)

    动态生成C# Lambda表达式

    Linq To EF 用泛型时生成的Sql会查询全表的问题

    Expression<Func>和Func

  • 相关阅读:
    Webform服务器控件调用JS
    Webfrom基础知识
    Webform用户控件
    数组练习
    整理
    SVN分支与合并
    根据经纬度,获取两点间的距离
    简单Bat文件编写
    Maven Android使用一
    Maven环境配置
  • 原文地址:https://www.cnblogs.com/battor/p/expression_where.html
Copyright © 2020-2023  润新知