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

C# 动态构建表达式树(一)—— 构建 Where 的 Lambda 表达式的更多相关文章

  1. Java 终于有 Lambda 表达式啦~Java 8 语言变化——Lambda 表达式和接口类更改【转载】

    原文地址 en cn 下载 Demo Java™ 8 包含一些重要的新的语言功能,为您提供了构建程序的更简单方式.Lambda 表达式 为内联代码块定义一种新语法,其灵活性与匿名内部类一样,但样板文件 ...

  2. 【C#表达式树 五】工厂模式创建表达式树节点

    常量 1.值常量  (p)=>100+88+p ParameterExpression par = Expression.Parameter(typeof(int), "p" ...

  3. Lambda表达式的语法与如何使用Lambda表达式

    Lambda表达式是对象,是一个函数式接口的实例 如何来写Lambda表达式? 看参数 看返回值 代码实例1: package day2; import jdk.nashorn.internal.co ...

  4. 黑马Lambda表达式学习 Stream流 函数式接口 Lambda表达式 方法引用

  5. 转载:C#特性-表达式树

    原文地址:http://www.cnblogs.com/tianfan/ 表达式树基础 刚接触LINQ的人往往觉得表达式树很不容易理解.通过这篇文章我希望大家看到它其实并不像想象中那么难.您只要有普通 ...

  6. C#特性-表达式树

    表达式树ExpressionTree   表达式树基础 转载需注明出处:http://www.cnblogs.com/tianfan/ 刚接触LINQ的人往往觉得表达式树很不容易理解.通过这篇文章我希 ...

  7. .NET Core表达式树的梳理

    最近要重写公司自己开发的ORM框架:其中有一部分就是查询的动态表达式:于是对这方面的东西做了一个简单的梳理 官网的解释: 表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和  ...

  8. 追根溯源之Linq与表达式树

    一.什么是表达式树?   首先来看下官方定义(以下摘录自巨硬官方文档)   表达式树表示树状数据结构中的代码,其中每个节点都是表达式,例如,方法调用或诸如的二进制操作x < y.   您可以编译 ...

  9. LinqToDB 源码分析——处理表达式树

    处理表达式树可以说是所有要实现Linq To SQL的重点,同时他也是难点.笔者看完作者在LinqToDB框架里面对于这一部分的设计之后,心里有一点不知所然.由于很多代码没有文字注解.所以笔者只能接合 ...

  10. [C#] C# 知识回顾 - 表达式树 Expression Trees

    C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...

随机推荐

  1. 天梯赛 L1-058 6翻了

    传送门:https://pintia.cn/problem-sets/994805046380707840/problems/1111914599408664577 这道字符串题,只是天梯赛L1的题, ...

  2. java常见面试题总结2

    JAVA必背面试题和项目面试通关要点 简单来讲,equals方法主要是用来判断从表面上看或者从内容上看,2个对象是不是相等. 一 数据库 1.常问数据库查询.修改(SQL查询包含筛选查询.聚合查询和链 ...

  3. A Python Environment Detector

    The user provide the method to get result(command on remote host), the check standard(a callback fun ...

  4. LinuxDHCP配置

    目录 一.DHCP服务 1.1.了解DHCP服务 1.2.使用DHCP的好处 1.3.DHCP的分配方式 1.4.DHCP的租约过程 客户机请求IP地址 重新登录 更新租约 1.5.使用DHCP动态配 ...

  5. 1056 Mice and Rice (25分)队列

    1.27刷题2 Mice and Rice is the name of a programming contest in which each programmer must write a pie ...

  6. 【原创】Dubbo 2.7.8多个远程代码执行漏洞

    马上年底了,发现年初定的几个漏洞的KPI还没来得及完成,趁着最近有空赶紧突击一波,之前业务部门被爆过Dubbo的漏洞,干脆就把Dubbo拖过来挖一把.之前没用过Dubbo,既然要挖它就先大体了解了一下 ...

  7. 07.SpringMVC之静态资源

    如何你的DispatcherServlet拦截 *.do这样的URL,就不存在访问不到静态资源的问题.如果你的DispatcherServlet拦截"/",拦截了所有的请求,同时对 ...

  8. 【VLC开发】libvlc_new函数参数

    项目中有视频监控的需求,找了vlc这个开源视频工具,在获取实例参数时遇到了问题, 要得到VLC的全部参数有两种方法, 1 只要在创建时加上"--longhelp"和"-- ...

  9. [SWMM]出现问题及解决

    1,节点顺序 [错误]:如下图,在SWMM软件中普通节点到出水口的连接线不能正常连接,提示找不到出水口节点,但在inp文件中是存在的! [解决]:需要先写入点节点再写入线节点,即先写入[JUNCTIO ...

  10. Photoshop 批量修改图像大小