本文旨在指导如何配置Entity Framework Core(6或7版本),以便在不迁移到EF Core 8的情况下,利用OpenJson的性能优势来处理大量的SQL IN子句值。如果项目需要扩展并使用缓存值查询更多数据,但又无法迁移到EF Core 8,因为迁移将需要团队进行更多的测试和开发,那么可以通过NuGet包或直接将类集成到代码库中,使用OpenJson。
本文的范围是传达开始使用OpenJson与EF Core 6/7所需的配置步骤。
在开始之前,需要了解以下内容:
设置测试数据库 执行上述SQL Server脚本以:
基准测试(秒)
使用带有大型IN子句和小型NOT IN子句的简单LINQ语句:
context.LargeTables.Where(itm => chunkData.Contains(itm.CodeName) && !_listNotIn.Contains(itm.ProjectRef)).ToListAsync();
IN子句 - 批量大小 标准EF设置 OpenJson EF拦截器 20K 151 204 30K 396 157 40K Timeout 132 100K Timeout 123 150K Timeout 99 200K Timeout 98 300k Timeout 91 500k Timeout 83 700K Timeout 86 1M Timeout 105 在40K时,标准EF在第一个IN子句上超时失败: 注意:Entity Framework SQL查询生成器通常会创建复杂且复杂的SQL语句,特别是当涉及到IN/NOT IN子句时。它会将子句与需要匹配的任何联接表重复,这通常会使得查询失败,因为SQL Server由于其复杂性而无法执行。但是通过OpenJson,可以使用更大的值数量在分块方法中,并使用并发包来维护结果(下面有示例代码)。
使用NuGet包或直接将类添加到项目中添加拦截器 使用NuGet包添加拦截器类 NuGet包位于Nuget.Org上,Readme.md文件中说明了如何在项目中编辑EF设置 - 选择最新版本安装。 可以像在Visual Studio中添加普通包一样,将NuGet包添加到项目中 - 搜索Linq.OpenJson.InClause.Middleware并点击安装。
直接添加拦截器类 如果打开附加的Visual Studio解决方案,将看到三个项目,名为DemoLinqContainsOpenJson的项目用于演示和测试新的EF拦截器。 第二个名为LinqOpenJsonInClauseMiddleware的项目是NuGet包的内容,包含拦截器覆盖方法(ReaderExecutingAsync和ReaderExecuting)。 如果想自定义代码,例如,增加使用OpenJson而不是正常IN/NOT IN子句的阈值,可以在SqlServerReaderExecutingOpenJson类中设置常量MINIMUM_ITEM_SIZE。在这段代码片段中,当拦截器找到包含超过1000个值的IN/NOT IN子句时,它将转换SQL以使用OpenJson语法。
要将SqlServerReaderExecutingOpenJson类集成到项目中,只需从LinqOpenJsonInClauseMiddleware项目中复制该类,将类的命名空间重命名为与项目命名空间匹配。然后,可以编辑生成的SQL以满足需求。
编辑Entity FrameworkCore设置以调用自定义拦截器中间件
可以配置SqlServerReaderExecutingOpenJson类以两种方式包含在项目中 - 选择最适合项目的方式。
Program.cs类
通过向项目添加命名空间
using LinqOpenJsonInClauseMiddleware.Classes;
添加代码行
options.AddInterceptors(new SqlServerReaderExecutingOpenJson
到每个与dbContext相关的选项(标准和工厂上下文如下)。
dbContext类
通过向项目添加命名空间
using LinqOpenJsonInClauseMiddleware.Classes;
添加代码行
options.AddInterceptors(new SqlServerReaderExecutingOpenJson
到OnConfiguring方法的options。
拦截器代码解释 ReaderExecutingAsync | ReaderExecuting 在下面的覆盖方法中,可以看到拦截了LINQ(通过EF管道)生成的SQL,并用OpenJson替代SQL语句替换它。调用辅助方法ConvertInClauseToOpenJson将原始EF SQL转换为自定义的OpenJson定向SQL语句。
辅助类 ConvertInClauseToOpenJson ConvertInClauseToOpenJson方法将解析IN/NOT IN子句,然后将值传递给ConvertToOpenJson方法,该方法将用OpenJson SQL替换现有的SQL。
ConvertToOpenJson ConvertToOpenJson方法将确定IN/NOT IN子句中的值是整数/小数还是varchar相关,并构建与此相关的新OpenJson SQL。
最终可以看到OpenJson语句被插入到SQL语句中,并返回给父IN/NOT IN子句。
原始EF SQL和生成的OpenJson SQL的示例 在原始SQL(为了简洁起见,值已精简)中,可以看到标准的IN子句及其值。 然后在生成的OpenJson SQL中,可以看到值是字符串相关的,并且正确使用了相关的NVARCHAR数据类型。 注意:NOT IN子句没有转换为OpenJson,因为它的值计数小于1k。
使用多线程和ConcurrentBag汇总结果 通过使用OpenJson,可以让SQL Server执行带有较大IN/NOT IN子句的SQL语句。默认情况下,EF Core 6/7将执行带有小于20K值计数的IN子句,这将是整个语句的总值(因为IN子句可以在单个EF生成的SQL语句中多次复制)。 在以前的项目中,采取了基于每个查询的复杂性执行OpenJson查询的方法。通过限制块的大小来创建值列表。 然后循环这个列表,将值插入到OpenJson查询中并执行它,使用并行循环来利用服务器可用的多个核心,然后简单地将结果添加到(线程安全的)ConcurrentBag中。