C# 动态构建表达式树(二)——构建 Select 和 GroupBy 的表达式

前言

上篇中写了表达式的基本使用,为 Where 方法动态构建了表达式。在这篇中会写如何为 Select 和 GroupBy 动态构建(可以理解为动态表达式的其它常见形式)。

本文的操作方式似乎在实际使用中作用甚微,仅作为了解即可

准备工作

环境:.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

为 Select 方法动态构建表达式

假设我们要查出 Person 表中的 Name、Age、Gender 字段,返回类型为 List<PersonResult> 的对象:

class PersonResult
{
public string Name { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
}

常规写法:

List<PersonResult> personList
= context.Person.Select(p => new PersonResult
{
Name = p.Name,
Age = p.Age,
Gender = p.Gender
}).ToList();

动态组合写法:

ParameterExpression pe = Expression.Parameter(typeof(Person), "p");	# 创建形参 p

MemberExpression meName = Expression.MakeMemberAccess(pe, typeof(Person).GetProperty("Name"));	# 要使用 MakeMemberAccess 方法
MemberExpression meAge = Expression.MakeMemberAccess(pe, typeof(Person).GetProperty("Age"));
MemberExpression meGender = Expression.MakeMemberAccess(pe, typeof(Person).GetProperty("Gender")); Type personResultType = typeof(PersonResult);
MemberAssignment maName = Expression.Bind(personResultType.GetProperty("Name"), meName); # 使用 Bind 方法将目标类型的属性与源类型的属性值绑定
MemberAssignment maAge = Expression.Bind(personResultType.GetProperty("Age"), meAge);
MemberAssignment maGender = Expression.Bind(personResultType.GetProperty("Gender"), meGender); NewExpression ne = Expression.New(personResultType); # 相当于 new 关键字创建一个对象 MemberInitExpression mie = Expression.MemberInit(ne, maName, maAge, maGender); # 相当于初始化时赋值操作 Expression<Func<Person, PersonResult>> personSelectExpression = Expression.Lambda<Func<Person, PersonResult>>(mie, pe);
var personList1 = context.Person.Select(personSelectExpression).ToList();

与构建 Where 方法的表达式差不多,主要是创建新对象以及赋值的写法需要注意。

为 GroupBy 方法动态构建表达式

假设我们要统计出 Person 表中的男生女生数量,返回类型为 List<PersonGroupByResult> 对象

class PersonGroupByResult
{
public string Gender { get; set; }
public int Count { get; set; }
}

常规写法:

List<PersonGroupByResult> personList =
context.Person.GroupBy(p => p.Gender)
.Select(p => new PersonGroupByResult
{
Gender = p.Key,
Count = p.Count()
}).ToList();

动态组合写法:

// 动态创建 GroupBy 中的 Expression
ParameterExpression pe = Expression.Parameter(typeof(Person), "p");
MemberExpression meGender = Expression.Property(pe, "Gender");
Expression<Func<Person, string>> groupByExpression = Expression.Lambda<Func<Person, string>>(meGender, pe); // 动态创建 Select 中的 Expression
Type groupType = typeof(IGrouping<string, Person>); # 注意 GroupBy 函数返回的类型
ParameterExpression pge = Expression.Parameter(groupType, "pg");
MemberExpression meKeyGender = Expression.MakeMemberAccess(pge, groupType.GetProperty("Key")); # 获取其中的属性,与上面动态拼接 Select 相同 Type groupByResultType = typeof(PersonGroupByResult);
MemberAssignment maGender = Expression.Bind(groupByResultType.GetProperty("Gender"), meKeyGender); # 使用 Bind 方法将目标类型的属性与源类型的属性值绑定,与上面动态拼接 Select 相同
MethodInfo countMethod = typeof(Enumerable).GetMethods().Where(a => a.Name == "Count" && a.GetParameters().Length == 1)
.FirstOrDefault().MakeGenericMethod(typeof(Person)); # 获取 Count 方法
MemberAssignment maCount = Expression.Bind(groupByResultType.GetProperty("Count"), Expression.Call(countMethod, pge)); #使用 Bind 方法将目标类型的属性与源类型调用方法的返回值绑定
NewExpression ne = Expression.New(groupByResultType);
MemberInitExpression mie = Expression.MemberInit(ne, maGender, maCount); Expression<Func<IGrouping<string, Person>, PersonGroupByResult>> personSelectExpression =
Expression.Lambda<Func<IGrouping<string, Person>, PersonGroupByResult>>(mie, pge);
var personList1 = context.Person.GroupBy(groupByExpression).Select(personSelectExpression).ToList();

需要注意的是查找 Count 方法的过程。通过查看定义发现,IGrouping 类型中并没有 Count 方法,而 IGrouping 实现了 IEnumerable,因此想到获取 Enumerable 这个 IEnumerable 实现类中的 Count 方法

而 Enumerable中 的 Count 方法定义如下:

在查阅资料和多次尝试后,仍然无法直接获取到 Count(当仅传入方法名称时,提示有多个定义;当传入方法名称和参数时,一直返回为 null)。现通过参数个数来筛选,得到想要的方法。还需要注意的是,Count 方法为泛型方法,得到后还需要执行 MakeGenericMethod 以传入泛型类型

通过查看 ChangeTracer 和 SQL 执行情况,发现即使我们使用的是 Enumerable 类型,依然是只返回了我们想要的结果,没有全表查询。这其中的奥秘还需要探索啊。

其它的一点思考

之前在工作中为了方便经常使用 Select 查询出匿名类,能否使用动态创建表达式的方式创建匿名类呢(我能想到的一种使用场景是,根据某些条件返回不同的字段,但这其实可以通过冗余字段实现)。在进行了很多尝试后,发现只能先写好一个匿名类,再 Select 这个匿名类的相关字段。虽然看似达到了目的,但不符合我们动态组合的要求,因此是没有意义的。

参考

c# – 使用反射创建lambda表达式,如x => new {..}

referencing desired overloaded generic method

Expression表达式目录树动态拼接 反射获取泛型方法

How to create LINQ Expression Tree to select an anonymous type

How to use Expression to build an Anonymous Type?

C# 动态构建表达式树(二)——构建 Select 和 GroupBy 的表达式的更多相关文章

  1. C#动态构建表达式树(三)——表达式的组合

    C#动态构建表达式树(三)--表达式的组合 前言 在筛选数据的过程中,可能会有这样的情况:有一些查询条件是公共的,但是根据具体的传入参数可能需要再额外增加一个条件.对于这种问题一般有两种方法: a. ...

  2. Lambda表达式树构建(上)

    概述 Lambda是C#常用的语句,采用委托等方式,来封装真实的代码块.Lambda其实就是语法糖,是一个匿名函数,是一种高效的类似于函数式编程的表达式,Lambda简化了开发中需要编写的代码量.它可 ...

  3. c#——表达式树在LINQ动态查询

    一般如果逻辑比较简单,只是存在有的情况多一个查询条件,有的情况不需要添加该查询条件 简单方式这样操作就可以了 public IQueryable<FileImport> DynamicCh ...

  4. [.net 面向对象程序设计进阶] (6) Lamda表达式(二) 表达式树快速入门

    [.net 面向对象程序设计进阶] (6) Lamda表达式(二) 表达式树快速入门 本节导读: 认识表达式树(Expression Tree),学习使用Lambda创建表达式树,解析表达式树. 学习 ...

  5. C# 表达式树遍历(二)

    一.前言 上一篇我们对表达式树有了初步的认识,这里我们将对表达式树进行遍历,只有弄清楚了他的运行原理,我们才可以对他进行定制化修改. 表达式系列目录 C# 表达式树讲解(一) C# 表达式树遍历(二) ...

  6. WebKit Inside: DOM树的构建

    当客户端App主进程创建WKWebView对象时,会创建另外两个子进程:渲染进程与网络进程.主进程WKWebView发起请求时,先将请求转发给渲染进程,渲染进程再转发给网络进程,网络进程请求服务器.如 ...

  7. 【C#表达式树 开篇】 Expression Tree - 动态语言

    .NET 3.5中新增的表达式树(Expression Tree)特性,第一次在.NET平台中引入了"逻辑即数据"的概念.也就是说,我们可以在代码里使用高级语言的形式编写一段逻辑, ...

  8. LinQ实战学习笔记(三) 序列,查询操作符,查询表达式,表达式树

    序列 延迟查询执行 查询操作符 查询表达式 表达式树 (一) 序列 先上一段代码, 这段代码使用扩展方法实现下面的要求: 取进程列表,进行过滤(取大于10M的进程) 列表进行排序(按内存占用) 只保留 ...

  9. 不可不知的表达式树(1)Expression初探

    说起Lambda表达式,大家基本都很熟悉了,而表达式树(Expression Trees),则属于80%的工作中往往都用不到的那种技术,所以即便不是什么新技术,很多人对其理解都并不透彻.此文意图从表达 ...

随机推荐

  1. CleanArchitecture Application代码生成插件-让程序员告别CURD Ctrl+C Ctrl+V

    这是一个根据Domain项目中定义的实体对象(Entity)生成符合Clean Architecture原则的Application项目所需要的功能代码,包括常用的Commands,Queries,V ...

  2. Dart空安全的底层原理与适配

    一.在空安全推出之前,静态类型系统允许所有类型的表达式中的每一处都可以有 null. 从类型理论的角度来说,Null 类型被看作是所有类型的子类: 类型会定义一些操作对象,包括 getters.set ...

  3. 原来一条select语句在MySQL是这样执行的《死磕MySQL系列 一》

    前言 看到蒋老师的第一篇文章后就收货颇丰,真是句句戳中痛点. 令我记忆最深的就是为什么知道了一个个技术点,却还是用不好 ?不管是蒋老师所说的Redis还是本系列要展开学习的MySQL. 这是一个值得思 ...

  4. Clusternet - 新一代开源多集群管理与应用治理项目

    作者 徐迪,腾讯云容器技术专家. 汝英哲,腾讯云高级产品经理. 摘要 在过去的数年里,云计算领域经历了多次巨大的变革,当前越来越多的组织将应用部署在本地和云上的多个基础设施平台上,这些平台可能是两个公 ...

  5. 本文详细阐述如何用C#创建COM组件,并能用VC6.0等调用。

    本文详细阐述如何用C#创建COM组件,并能用VC6.0等调用. 附:本文适用任何VS系列工具. 在用C#创建COM组件时,一定要记住以下几点: 1.所要导出的类必须为公有: 2.所有属性.方法也必须为 ...

  6. C# KeyValuePair<TKey,TValue> 与 Dictionary<TKey,TValue> 区别

    KeyValuePair<TKey,TValue> 可以设置.查询的一对键值 是struct Dictionary<TKey,TValue> 可以设置.查询的多对键值的集合 总 ...

  7. 10.SpringMVC之格式化、校验

    数据格式化 数据格式化的注解: 数据校验JSR303 Hibernate Validator扩展注解 启动 springMVC数据校验 转换.格式化.校验出错处理:

  8. 对集合使用Comparator

    1 import java.util.Comparator; 2 import java.util.PriorityQueue; 3 4 /** 5 * 对集合使用Comparator,不改变对象的自 ...

  9. Linux centos 安装 jenkins & 本地构建jar & 远程构建jar

    一.部署 jenkins 需要的前奏 1.安装 JDK:https://www.cnblogs.com/chuyi-/p/10644440.html 2.安装tomcat:https://www.cn ...

  10. 刷题-力扣-230. 二叉搜索树中第K小的元素

    230. 二叉搜索树中第K小的元素 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/kth-smallest-element-in-a ...