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 这两个我比较常用的方法了,敬请期待吧。
参考
C# 动态构建表达式树(一)—— 构建 Where 的 Lambda 表达式的更多相关文章
- Java 终于有 Lambda 表达式啦~Java 8 语言变化——Lambda 表达式和接口类更改【转载】
原文地址 en cn 下载 Demo Java™ 8 包含一些重要的新的语言功能,为您提供了构建程序的更简单方式.Lambda 表达式 为内联代码块定义一种新语法,其灵活性与匿名内部类一样,但样板文件 ...
- 【C#表达式树 五】工厂模式创建表达式树节点
常量 1.值常量 (p)=>100+88+p ParameterExpression par = Expression.Parameter(typeof(int), "p" ...
- Lambda表达式的语法与如何使用Lambda表达式
Lambda表达式是对象,是一个函数式接口的实例 如何来写Lambda表达式? 看参数 看返回值 代码实例1: package day2; import jdk.nashorn.internal.co ...
- 黑马Lambda表达式学习 Stream流 函数式接口 Lambda表达式 方法引用
- 转载:C#特性-表达式树
原文地址:http://www.cnblogs.com/tianfan/ 表达式树基础 刚接触LINQ的人往往觉得表达式树很不容易理解.通过这篇文章我希望大家看到它其实并不像想象中那么难.您只要有普通 ...
- C#特性-表达式树
表达式树ExpressionTree 表达式树基础 转载需注明出处:http://www.cnblogs.com/tianfan/ 刚接触LINQ的人往往觉得表达式树很不容易理解.通过这篇文章我希 ...
- .NET Core表达式树的梳理
最近要重写公司自己开发的ORM框架:其中有一部分就是查询的动态表达式:于是对这方面的东西做了一个简单的梳理 官网的解释: 表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 ...
- 追根溯源之Linq与表达式树
一.什么是表达式树? 首先来看下官方定义(以下摘录自巨硬官方文档) 表达式树表示树状数据结构中的代码,其中每个节点都是表达式,例如,方法调用或诸如的二进制操作x < y. 您可以编译 ...
- LinqToDB 源码分析——处理表达式树
处理表达式树可以说是所有要实现Linq To SQL的重点,同时他也是难点.笔者看完作者在LinqToDB框架里面对于这一部分的设计之后,心里有一点不知所然.由于很多代码没有文字注解.所以笔者只能接合 ...
- [C#] C# 知识回顾 - 表达式树 Expression Trees
C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...
随机推荐
- 自动化可用到的Java读取Excel文件指定的行列数据
前言 在做接口自动化的时候,通常会遇到数据取用及存放的问题,一般有三种方式可选择 1.数据库存取 2.表格存取 3.项目配置文件存取 这里仅展示下第二种方式表格取数据的 示例 import org.a ...
- 新手安装eclipse或idea后进行配置、快捷键、插件总结
.personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...
- Mybatis源码解析2—— 实例搭建
大家好,我是可乐. 上篇文章给大家撸了一遍用 JDBC 直接操作数据库的实例,还只是简单写了一个查询的接口,其代码量就已经很大了,并且可乐还给大家分析了直接使用 JDBC 带来的一些问题,总之是一种反 ...
- nat转换技术,且用且珍惜
一.NAT转换技术 1.1.NAT技术概述 随着Internet的发展和网络应用的增多,IPv4地址枯竭已经成为制约网络发展的瓶颈.尽管IPv6可以从根本上解决IPv4地址空间不足的问题,但目前众多的 ...
- Python语言系列-07-面向对象2
重构父类__init__方法 #!/usr/bin/env python3 # author:Alnk(李成果) # 需求:Dog类要新增一个实例属性,但是Cat类不需要 class Animal(o ...
- 清理docker常用命令
0.查看docker占用空间docker system df1.一键删除所有已经停止的容器 docker container prune 2.删除所有容器(包含停止的 正在运行的) docker rm ...
- weblogicSSRF漏洞复现
一.关于SSRF 1.1 简介: SSRF(Server-Side Request Forgery)服务端请求伪造,是一种由攻击者构造形成由服务器端发起请求的一个漏洞,一般情况下,SSRF 攻击的目标 ...
- Python 可变数据类型与不可变数据类型
浅拷贝和深拷贝 Python数据都是存放到内存中的,Python的数据又分为可变和不可以变 可变数据(修改了值后,不会改变内存地址,修改的值还是指向相同的内存地址) 字典 # 列表是可变 x = [1 ...
- 题解 Star Way To Heaven
传送门 这整场都不会--这题想二分不会check 其实check很好写,考虑一个mid的实际意义 即为check在不靠近每个star及边界mid距离内的前提下,能不能到达\((n,m)\) 其实可以转 ...
- LuoguP3167通配符匹配
题意 本题的意思就是给出一段带有 $ ? $ 与 \(*\) 的字符串 (在下面称为\(s\)), $ ? $ 必须占据一个字符位置, \(*\) 可以占据任意位置, 求下面给出几段(在下面称为\(s ...