前言

NET程序员是很幸福的,MS在上个月发布了NET9.0RTM,带来了不少的新特性,但是呢,我们是不是还有很多同学软硬件都还没更上,比如,自己的电脑还在跑Win7,公司服务器还在跑MSSQL2005-2008的!

这不就引入了我们本文要探索的问题,因为MS早在EFcore3.1后就不再内置支持ROW_NUMBER()了,以至于需要兼容分页的代码都需要自行处理!

最近自己发的nuget包有个国外的程序员朋友提了一个Issue,以至于我马上行动起来

EFCore9中, 以前兼容的好好的ROW_NUMBER()代码,升级尝鲜后发现跑不起来了,这主要是因为新版本的EFcore做了很多破坏性更新,以至于我们不得不研究新的底层代码!

兼容实现

之前发布过一个nuget包,代码主要是基于以前程序员兼容EFCore7适配到EFCore8的兼容,代码也不多变化也不大,不过呢,升级到EFCore9后发现底层的API全变了,不得不重新再实现一遍!

以下是兼容EFCore9的代码部分:

  1. #if NET9_0_OR_GREATER
  2. #pragma warning disable EF1001 // Internal EF Core API usage.
  3. namespace Biwen.EFCore.UseRowNumberForPaging;
  4. using Microsoft.EntityFrameworkCore.Query;
  5. using System.Collections.Generic;
  6. using System.Reflection;
  7. public class SqlServer2008QueryTranslationPostprocessorFactory(
  8. QueryTranslationPostprocessorDependencies dependencies,
  9. RelationalQueryTranslationPostprocessorDependencies relationalDependencies) : IQueryTranslationPostprocessorFactory
  10. {
  11. private readonly QueryTranslationPostprocessorDependencies _dependencies = dependencies;
  12. private readonly RelationalQueryTranslationPostprocessorDependencies _relationalDependencies = relationalDependencies;
  13. public virtual QueryTranslationPostprocessor Create(QueryCompilationContext queryCompilationContext)
  14. => new SqlServer2008QueryTranslationPostprocessor(
  15. _dependencies,
  16. _relationalDependencies,
  17. queryCompilationContext);
  18. public class SqlServer2008QueryTranslationPostprocessor(QueryTranslationPostprocessorDependencies dependencies, RelationalQueryTranslationPostprocessorDependencies relationalDependencies, QueryCompilationContext queryCompilationContext) :
  19. RelationalQueryTranslationPostprocessor(dependencies, relationalDependencies, (RelationalQueryCompilationContext)queryCompilationContext)
  20. {
  21. public override Expression Process(Expression query)
  22. {
  23. query = base.Process(query);
  24. query = new Offset2RowNumberConvertVisitor(query, RelationalDependencies.SqlExpressionFactory).Visit(query);
  25. return query;
  26. }
  27. internal class Offset2RowNumberConvertVisitor(
  28. Expression root,
  29. ISqlExpressionFactory sqlExpressionFactory) : ExpressionVisitor
  30. {
  31. private readonly Expression root = root;
  32. private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory;
  33. private const string SubTableName = "subTbl";
  34. private const string RowColumnName = "_Row_";//下标避免数据表存在字段
  35. private const string _mp = "_projectionMapping";
  36. private static readonly FieldInfo ProjectionMapping = typeof(SelectExpression).GetField(_mp, BindingFlags.NonPublic | BindingFlags.Instance);
  37. protected override Expression VisitExtension(Expression node) => node switch
  38. {
  39. ShapedQueryExpression shapedQueryExpression => shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression),
  40. SelectExpression se => VisitSelect(se),
  41. _ => base.VisitExtension(node)
  42. };
  43. private SelectExpression VisitSelect(SelectExpression selectExpression)
  44. {
  45. var oldOffset = selectExpression.Offset;
  46. if (oldOffset == null)
  47. return selectExpression;
  48. var oldLimit = selectExpression.Limit;
  49. var oldOrderings = selectExpression.Orderings;
  50. var newOrderings = oldOrderings switch
  51. {
  52. { Count: > 0 } when oldLimit != null || selectExpression == root => oldOrderings.ToList(),
  53. _ => []
  54. };
  55. var rowOrderings = oldOrderings.Any() switch
  56. {
  57. true => oldOrderings,
  58. false => [new OrderingExpression(new SqlFragmentExpression("(SELECT 1)"), true)]
  59. };
  60. var oldSelect = selectExpression;
  61. var rowNumberExpression = new RowNumberExpression([], rowOrderings, oldOffset.TypeMapping);
  62. // 创建子查询
  63. IList<ProjectionExpression> projections = [new ProjectionExpression(rowNumberExpression, RowColumnName),];
  64. var subquery = new SelectExpression(
  65. SubTableName,
  66. oldSelect.Tables,
  67. oldSelect.Predicate,
  68. oldSelect.GroupBy,
  69. oldSelect.Having,
  70. [.. oldSelect.Projection, .. projections],
  71. oldSelect.IsDistinct,
  72. [],//排序已经在rowNumber中了
  73. null,
  74. null,
  75. null,
  76. null);
  77. //构造新的条件:
  78. var and1 = sqlExpressionFactory.GreaterThan(
  79. new ColumnExpression(RowColumnName, SubTableName, typeof(int), null, true),
  80. oldOffset);
  81. var and2 = sqlExpressionFactory.LessThanOrEqual(
  82. new ColumnExpression(RowColumnName, SubTableName, typeof(int), null, true),
  83. sqlExpressionFactory.Add(oldOffset, oldLimit));
  84. var newPredicate = sqlExpressionFactory.AndAlso(and1, and2);
  85. //新的Projection:
  86. var newProjections = oldSelect.Projection.Select(e =>
  87. {
  88. if (e is { Expression: ColumnExpression col })
  89. {
  90. var newCol = new ColumnExpression(col.Name, SubTableName, col.Type, col.TypeMapping, col.IsNullable);
  91. return new ProjectionExpression(newCol, e.Alias);
  92. }
  93. return e;
  94. });
  95. // 创建新的 SelectExpression,将子查询作为来源
  96. var newSelect = new SelectExpression(
  97. oldSelect.Alias,
  98. [subquery],
  99. newPredicate,
  100. oldSelect.GroupBy,
  101. oldSelect.Having,
  102. [.. newProjections],
  103. oldSelect.IsDistinct,
  104. [],
  105. null,
  106. null,
  107. null,
  108. null);
  109. //使用反射替换_projectionMapping变量:
  110. ProjectionMapping.SetValue(newSelect, ProjectionMapping.GetValue(oldSelect));
  111. return newSelect;
  112. }
  113. }
  114. }
  115. }
  116. #pragma warning restore EF1001 // Internal EF Core API usage.
  117. #endif

最后

实现上逻辑还是一致的,反正都是将Offset转换为ROW_NUMBER()子查询中,取行号数据

只是代码实现区别有一些,以前的EFCore底层代码很多已经不在可用比如直接使用PushdownIntoSubquery()会报错,GenerateOuterColumn()内部的扩展方法发生了破坏性更新导致不能再使用等!

如果你的程序需要升级到NET9并还在使用早期数据库的话,可以引用我实现的代码部分,或者直接引用我发布的Nuget包

代码我放在了,任何问题欢迎Issue https://github.com/vipwan/Biwen.EFCore.UseRowNumberForPaging

.NET9 EFcore支持早期MSSQL数据库 ROW_NUMBER()分页的更多相关文章

  1. Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库

    前言     在 .Net Core 2.2中 Microsoft.AspNetCore.App 默认内置了EntityFramework Core 包,所以在使用过程中,我们无需再从 NuGet 仓 ...

  2. 如何从40亿整数中找到不存在的一个 webservice Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库 WPF实战案例-打印 RabbitMQ与.net core(五) topic类型 与 headers类型 的Exchange

    如何从40亿整数中找到不存在的一个 前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况 ...

  3. jDialects:一个从Hibernate抽取的支持70多种数据库方言的原生SQL分页工具

    jDialects(https://git.oschina.net/drinkjava2/jdialects) 是一个收集了大多数已知数据库方言的Java小项目,通常可用来创建分页SQL和建表DDL语 ...

  4. mssql Row_Number() 分页 DISTINCT 问题

    转载原文地址http://www.cnblogs.com/pumaboyd/archive/2008/04/20/1162376.html 这周碰到了很多奇怪的问题,有些是莫名的低级错误,有些这是一直 ...

  5. DBHelper (支持事务与数据库变更)

    1 概述 更新内容:添加 "支持数据分页" 这个数据库操作类的主要特色有 1>     事务操作更加的方便 2>     变更数据库更加的容易 3>  支持数据分 ...

  6. 记一次SQLServer的分页优化兼谈谈使用Row_Number()分页存在的问题

    最近有项目反应,在服务器CPU使用较高的时候,我们的事件查询页面非常的慢,查询几条记录竟然要4分钟甚至更长,而且在翻第二页的时候也是要这么多的时间,这肯定是不能接受的,也是让现场用SQLServerP ...

  7. SQL Server数据库ROW_NUMBER()函数使用详解

    SQL Server数据库ROW_NUMBER()函数使用详解 摘自:http://database.51cto.com/art/201108/283399.htm SQL Server数据库ROW_ ...

  8. 使用Row_Number()分页优化

    记一次SQLServer的分页优化兼谈谈使用Row_Number()分页存在的问题   最近有项目反应,在服务器CPU使用较高的时候,我们的事件查询页面非常的慢,查询几条记录竟然要4分钟甚至更长,而且 ...

  9. MSSQL数据库迁移到Oracle

    MSSQL数据库迁移到Oracle 最近要把一个MSSQL数据库迁移到Oracle上面,打算借助PowerDesigner这个软件来实现;今天简单研究一下这个软件的运用;把一步简单的操作步骤记录下来: ...

  10. 通过 DynamicLinq 简单实现 N-Tier 部署下的服务端数据库通用分页

    通过 DynamicLinq 简单实现 N-Tier 部署下的服务端数据库通用分页 YbSoftwareFactory 的 YbRapidSolution for WinForm 插件使用CSLA.N ...

随机推荐

  1. SciPy从入门到放弃

    目录 SciPy简介 拟合与优化模块 求最小值 曲线拟合 线性代数模块 统计模块 直方图和概率密度函数 统计检验 SciPy简介 SciPy是一种以NumPy为基础,用于数学.工程及许多其他的科学任务 ...

  2. NumPy从入门到放弃

    看前建议: 本文以jupyter notebook为编辑器进行示例,建议有一定python基础后再进行学习. python的安装:https://www.cnblogs.com/scfssq/p/17 ...

  3. SpringMVC:SpringMVC处理Ajax请求

    目录 @RequestBody @RequestBody获取json格式的请求参数 @ResponseBody @ResponseBody响应浏览器json数据 @RestController注解 @ ...

  4. 《Effective TypeScript》条款21 - 类型扩展

    本文主要通过一些实际的代码示例,来帮助大家理解什么是类型扩展,本文主要内容如下: 什么是类型扩展 代码示例 总结 什么是类型扩展? TypeScript 需要从你指定的单一值中决定一组可能的值,这个过 ...

  5. C# Dynamic 转换成 Dictionary,Dynamic 转换成 DataTable

    部分软件开发的时候用到了 dynamic 类型,这个类型的数据不需要做其他处理的时候非常好用,但是需要对其中的数据调整的时候就不是那么好用了,这里提供两个常见的转换方式 Dynamic To Dict ...

  6. JavaScript——基础语法

    书写语法    输出语句    变量    数据类型    运算符        == 与 === 区别:       ==:         1.判断类型是否一样,如果不一样,则进行类型转换     ...

  7. jQuery父子页面之间元素、方法获取、调用

    资源来自:https://www.cnblogs.com/it-xcn/p/5896231.html 一.jquery 父.子页面之间页面元素的获取,方法的调用: 1. 父页面获取子页面元素: 格式: ...

  8. SuperMap iServer8C证书过期如何解决

    说明:该问题是SuperMap iServer8.0.2和8.1.0版本云许可模块问题,需要手动更新云许可HTTPS证书,可以升级到官网8.1.1/9D/10i等方式进行解决 针对无法升级或者老项目维 ...

  9. 第17天:信息打点-语言框架&开发组件&FastJson&Shiro&Log4j&SpringBoot等

    框架:简单代码的一个整合库,如果使用框架就只需要学习使用框架调用即可 如:文件上传功能是需要很多代码来实现的,框架把这个代码进行封封装,调用即可 影响:如果采用框架开发,代码的安全性是取决于框架的过滤 ...

  10. C# ASP.NET Core Web API 框架 实现向手机发送验证码短信

    本文章主要是在C# ASP.NET Core Web API框架实现向手机发送验证码短信功能.这里我选择是一个互亿无线短信验证码平台,其实像阿里云,腾讯云上面也可以. 首先我们先去 互亿无线 http ...