引子

之前发现了一款叫 EFCore.BulkExtensions 的 nuget 包。里面提供了大量的 BulkInsertOrUpdateOrDelete 和 BatchUpdate 的拓展,可以很方便的解决批量更新和删除的问题,不用让 EFCore 一条一条的删除和更新。

其中几个比较有用的函数签名是

Task<int> BatchDeleteAsync(this IQueryable<T> queryable);
Task<int> BatchUpdateAsync(this IQueryable<T> queryable, Expression<Func<T, T>> updateExpression);

但是在升级到 ASP.NET Core 3.1 的时候,所有 Where 中的 someArray.Contains(i.Key) 全部挂掉了。而我的程序里用这一语句比较多,遂下载了其源代码并合并了当时作者几个月都没合并的一个PR。

研究代码,总结了该程序的基本运行过程:

  1. 通过反射获取各种私有变量来访问到 DbContext

  2. updateExpression 由这个包自己访问表达式树获得

  3. 让 IQueryable 执行 GetEnumerator 让 EFCore 生成对应的 Select 语句,进行字符串拼接

  4. 由 DbContext.Database.ExecuteSqlRaw 来完成语句执行

但是这过程有几个问题:

  1. 有几种句式 updateExpression 会翻译不了

  2. 由其原来实现的 updateExpression 翻译后的某些参数的 SQL 类型不对

  3. 我需要一个 INSERT INTO SELECT FROM 的句式,它不支持

  4. 我需要一个 upsert 功能,但是原来的 BulkInsertOrUpdate 不能在原表基础上操作

遂研究 IQueryable.Provider.Execute<T> 是什么执行流程。

语句生成过程

我觉得在翻代码的过程中,有这么一首歌比较符合我的心情:如果你愿意一层一层一层一层的拨开我的心,你会发现,你会讶异,你是我最压抑最深处的秘密。

  1. 调用 QueryCompiler.ExtractParameters,将其中的闭包捕捉变量参数化

  2. 检查是否已经缓存了这个查询表达式,如果没有则转入 QueryCompilationContext 处理,否则转到8

  3. QueryTranslationPreprocessor 处理,在原来的表达式树上先跳舞

  4. QueryableMethodTranslatingExpressionVisitor 将原来的表达式树翻译成一个 ShapedQueryExpression,而这一个表达式则包含了几个部分:SelectExpressionShaperExpression 和 ResultCardinality。其中前者是可以翻译成 SQL 语句的表达式,中间的是将查询出来的元组映射到实体类型,最后一个是查询的维度(Enumerable、Single、SingleOrDefault)

  5. QueryTranslationPostprocessor 处理,其中比较重要的是将查询的字段加入 SELECT 的 Projection 列表

  6. ShapedQueryCompilingExpressionVisitor 将 ShapedQueryExpression 缓存,并转换成为 IRelationcalCommandCache,然后构造一个 QueryableEnumerable 的 NewExpression。前者包含了该查询语句需要的参数、查询语法树、查询字符串,后者是进行语句执行的类

  7. 将上述 NewExpression 和将 QueryCompilationContext 中的查询参数加到 QueryContext 中的语句合并成为一个代码块,然后 Lambda Compile

  8. 生成 DbCommand 由 IRelationcalCommandCache 获取字符串并加入各种参数进行查询

翻译结束了,查询到这里也就可以开始了。

支持批量操作?

IRelationalCommandCache 是怎么生成字符串的呢?没错,就是 QuerySqlGenerator 啦。

那么,也就是说,我们能过拿到 Select Expression 的话,一切都好说。

上述过程中,最后的 IRelationalCommandCache 中会包含这个 SelectExpression。我们可以魔改这个啊!

DELETE 语句的生成比较简单。我们构建一个 DeleteExpression 类,将要删除的 Table、删除中的 Predicate、删除个数限制 Limit、原来的一些 Join 全部获取出来,就好了。然后在我们自己继承的 SqlServerQuerySqlGenerator 中实现这个部分。

INSERT INTO SELECT 也比较简单,只要构建一个 InsertIntoSelectExpression 类,将要插入的表 Table 和 SelectExpression 保存起来,就好了。

UPDATE SET 可能比较麻烦。但是我们可以骚操作啊!将那个 updateExpression 变成 Select 的字段,然后再读取 SelectExpression 中的 ProjectionExpression 不就好了吗~我真是个小天才。

MERGE INTO 是最烦的,因为结构过于复杂,涉及到 Target、Source、JoinPredicate、Limit、Matched、NotMatchedByTarget、NotMatchedBySource。过程中还要实现一些表的更名之类的。目前我只是实现了这些,但是想做出 Matched When 功能以后再发布到 nuget 上,这个实现实在是过于复杂,不知道有没有人帮帮我啊 TAT。

由于翻译 SqlExpression 最方便还是基于 QuerySqlGenerator 操作,所以就写一个 EnhancedQuerySqlGenerator 类来满足我们的需求,并在 DbContextOptionsBuilder 那边将这个 Factory 替换掉。

实现了这些,GitHub 地址:Microsoft.EntityFrameworkCore.Bulk,可以在 github packages 上下载目前版本的 nuget 包。

另外 src/Internal/TranslationGoThrough.cs 中有上述语句生成过程的一个缩影,和系统版本几乎一致,唯一不同的是修改了 ExtractParameters 函数。

因为原来的 Extract 过程有一个事情很诡异:在生成参数的时候,我们可以进行一些本地执行,但是如果不阻止某些本地执行程的话,可能会导致 UPDATE 语句的字段全部空。例如 updateExpression 中没有利用到原表的参数并且不捕捉闭包变量的时候,那么不会被本地执行,但是如果没有利用到原表的参数还捕捉闭包变量的时候,它就会被直接本地执行,字段空啦~(确实不懂他们这段代码逻辑怎么写的,你生成查询的时候优化这个的话,怎么不把前面一个也优化掉啊……

原文地址:https://www.90yang.com/efcore-query-sql-generation/


EFCore查询语句生成流程、让EFCore支持批量Update/Delete/MergeInto相关推荐

  1. mysql 查询语句_MySQL相关(一)- 一条查询语句是如何执行的

    前言 学习一个新知识最好的方式就是上官网,所以我先把官网贴出来 MySQL官网 (点击查阅),如果大家有想了解我没有说到的东西可以直接上官网看哈~目前 MySQL 最新大版本为8.0,但是鉴于目前应用 ...

  2. 实践案例丨ACL2020 KBQA 基于查询图生成回答多跳复杂问题

    摘要:目前复杂问题包括两种:含约束的问题和多跳关系问题.本文对ACL2020 KBQA 基于查询图生成的方法来回答多跳复杂问题这一论文工作进行了解读,并对相关实验进行了复现. 1.摘要 1.1 复杂问 ...

  3. MySql 学习笔记-Mysql架构介绍与查询sql执行流程

    最近花了99元大洋在极客时间 买了 MySQL 实战45讲.学习的同时留下点笔记.该内容仅仅是个人总结笔记,如有涉及版权还请告知. MySql 的简介 MySQL是一个关系型数据库管理系统,由瑞典My ...

  4. Mysql(一)一条查询语句的执行流程

    MySQL 的内部模块简介 1. Connector:用来支持各种语言和 SQL 的交互,比如 PHP,Python,Java 的JDBC: 2. Management Serveices & ...

  5. 讲mysql执行流程书籍_MySQL 基础架构 1. 一条SQL查询语句的执行过程(个人学习笔记)...

    MySQL的逻辑架构图: MySQL 大体分为 "server 层" 和 "存储引擎层" 两部分: Server 层 包括 连接器.查询缓存.分析器.优化器.执 ...

  6. go mysql 查询语句_01 MySQL-初识MySQL-查询语句的执行流程-Go语言中文社区

    MySQL的基础架构 我们通过一条查询语句来看看MySQL是如何执行的,同时通过这条语句的执行,了解MySQL的整体架构体系.mysql> select * from T where ID=1: ...

  7. 使用DbContextPool提高EfCore查询性能

    长话短说 上个月公司上线了一个物联网数据科学项目,我主要负责前端接收设备Event,并提供模型参数下载(数据科学团队会优化参数).WebApp部署在Azure,模型参数使用Azure SQL Serv ...

  8. tp5循环查询语句_如何用Excel快速生成SQL语句,用过的人都说好

    Excel的公式自动生成想必大家都知道了,就是写好一个公式后直接往下拖,就可以将后面数据的公式自动生成. 今天我们就用这个功能来快速生成SQL语句. 导入Excel数据 Excel的数据有多种方式,这 ...

  9. PHP资格证书查询系统源码 自动生成二维码 支持导入导出功能

    PHP资格证书查询系统源码 自动生成二维码 支持导入导出功能 程序说明: PHP资格证书查询系统源码 证书管理? 自动生成二维码 支持导入和导出功能 采用fastadmin框架开发而成 PHP资格证书 ...

最新文章

  1. 【技术趋势】2020 五大技术趋势:无人驾驶发展、机器视觉崛起、区块链实用化、人类增强技术、超自动化...
  2. 多功能选择列表(左右选择)
  3. ajax spring mvc 接收json数据,easyui ajax请求获取SpringMVC @ResponseBody返回的Json数据为什么非得eval才能通过对象获取值?...
  4. kafka的消费隔离级别(持续更新中)
  5. mysql优化之连接优化
  6. To disable deprecation,,use _CRT_SECURE_NO_WARNINGS
  7. 阿里工程师是如何系统化地总结缓存相关知识的
  8. 普通函数被类引用为友元函数
  9. 前端性能优化之重排和重绘
  10. Linus 怒批 GitHub:制造了毫无用处的垃圾合并信息!
  11. 用JavaScript获取输入的特殊字符
  12. 3. (5.18~5.25)2022年自动化保研信息+分析汇总(夏令营)
  13. 测试图片色域软件,显示器色域检测
  14. HDU 2415 Bribing FIPA(树形背包)
  15. 人民币的符号的正确表示法?一杠?两杠?¥还是¥呢?
  16. Halcon 算子 complement
  17. 《计算机网络自顶向下》笔记
  18. 人生不该有如此压力,来吃下这口缓解焦虑的良药[50P]
  19. Mac (M1) 官网安装 Tomcat,XAMPP,MySQL
  20. idea中的jar包在哪里导入?

热门文章

  1. 【BZOJ】【4145】【AMPPZ2014】The Prices
  2. HDFS HA与QJM(Quorum Journal Manager)介绍及官网内容整理
  3. oracle基于时间恢复整个数据库
  4. minecraft服务器_如何启动自己的Minecraft服务器进行多人游戏
  5. 如何更改您的iPhone铃声
  6. os x 启动引导_什么是OS X的启动板以及它如何工作?
  7. mysql索引三个字段查询两个字段_mysql中关于关联索引的问题——对a,b,c三个字段建立联合索引,那么查询时使用其中的2个作为查询条件,是否还会走索引?...
  8. uAdmin the Golang Web framework
  9. React Native在Android当中实践(五)——常见问题
  10. redis 安装错误 jemalloc.h: No such file or directory