.NET9 EFcore支持早期MSSQL数据库 ROW_NUMBER()分页
前言
NET程序员是很幸福的,MS在上个月发布了NET9.0
RTM,带来了不少的新特性,但是呢,我们是不是还有很多同学软硬件都还没更上,比如,自己的电脑还在跑Win7,公司服务器还在跑MSSQL2005-2008的!
这不就引入了我们本文要探索的问题,因为MS早在EFcore3.1
后就不再内置支持ROW_NUMBER()
了,以至于需要兼容分页的代码都需要自行处理!
最近自己发的nuget包有个国外的程序员朋友提了一个Issue,以至于我马上行动起来
在EFCore9
中, 以前兼容的好好的ROW_NUMBER()
代码,升级尝鲜后发现跑不起来了,这主要是因为新版本的EFcore做了很多破坏性更新,以至于我们不得不研究新的底层代码!
兼容实现
之前发布过一个nuget包,代码主要是基于以前程序员兼容EFCore7
适配到EFCore8
的兼容,代码也不多变化也不大,不过呢,升级到EFCore9
后发现底层的API全变了,不得不重新再实现一遍!
以下是兼容EFCore9的代码部分:
#if NET9_0_OR_GREATER
#pragma warning disable EF1001 // Internal EF Core API usage.
namespace Biwen.EFCore.UseRowNumberForPaging;
using Microsoft.EntityFrameworkCore.Query;
using System.Collections.Generic;
using System.Reflection;
public class SqlServer2008QueryTranslationPostprocessorFactory(
QueryTranslationPostprocessorDependencies dependencies,
RelationalQueryTranslationPostprocessorDependencies relationalDependencies) : IQueryTranslationPostprocessorFactory
{
private readonly QueryTranslationPostprocessorDependencies _dependencies = dependencies;
private readonly RelationalQueryTranslationPostprocessorDependencies _relationalDependencies = relationalDependencies;
public virtual QueryTranslationPostprocessor Create(QueryCompilationContext queryCompilationContext)
=> new SqlServer2008QueryTranslationPostprocessor(
_dependencies,
_relationalDependencies,
queryCompilationContext);
public class SqlServer2008QueryTranslationPostprocessor(QueryTranslationPostprocessorDependencies dependencies, RelationalQueryTranslationPostprocessorDependencies relationalDependencies, QueryCompilationContext queryCompilationContext) :
RelationalQueryTranslationPostprocessor(dependencies, relationalDependencies, (RelationalQueryCompilationContext)queryCompilationContext)
{
public override Expression Process(Expression query)
{
query = base.Process(query);
query = new Offset2RowNumberConvertVisitor(query, RelationalDependencies.SqlExpressionFactory).Visit(query);
return query;
}
internal class Offset2RowNumberConvertVisitor(
Expression root,
ISqlExpressionFactory sqlExpressionFactory) : ExpressionVisitor
{
private readonly Expression root = root;
private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory;
private const string SubTableName = "subTbl";
private const string RowColumnName = "_Row_";//下标避免数据表存在字段
private const string _mp = "_projectionMapping";
private static readonly FieldInfo ProjectionMapping = typeof(SelectExpression).GetField(_mp, BindingFlags.NonPublic | BindingFlags.Instance);
protected override Expression VisitExtension(Expression node) => node switch
{
ShapedQueryExpression shapedQueryExpression => shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression),
SelectExpression se => VisitSelect(se),
_ => base.VisitExtension(node)
};
private SelectExpression VisitSelect(SelectExpression selectExpression)
{
var oldOffset = selectExpression.Offset;
if (oldOffset == null)
return selectExpression;
var oldLimit = selectExpression.Limit;
var oldOrderings = selectExpression.Orderings;
var newOrderings = oldOrderings switch
{
{ Count: > 0 } when oldLimit != null || selectExpression == root => oldOrderings.ToList(),
_ => []
};
var rowOrderings = oldOrderings.Any() switch
{
true => oldOrderings,
false => [new OrderingExpression(new SqlFragmentExpression("(SELECT 1)"), true)]
};
var oldSelect = selectExpression;
var rowNumberExpression = new RowNumberExpression([], rowOrderings, oldOffset.TypeMapping);
// 创建子查询
IList<ProjectionExpression> projections = [new ProjectionExpression(rowNumberExpression, RowColumnName),];
var subquery = new SelectExpression(
SubTableName,
oldSelect.Tables,
oldSelect.Predicate,
oldSelect.GroupBy,
oldSelect.Having,
[.. oldSelect.Projection, .. projections],
oldSelect.IsDistinct,
[],//排序已经在rowNumber中了
null,
null,
null,
null);
//构造新的条件:
var and1 = sqlExpressionFactory.GreaterThan(
new ColumnExpression(RowColumnName, SubTableName, typeof(int), null, true),
oldOffset);
var and2 = sqlExpressionFactory.LessThanOrEqual(
new ColumnExpression(RowColumnName, SubTableName, typeof(int), null, true),
sqlExpressionFactory.Add(oldOffset, oldLimit));
var newPredicate = sqlExpressionFactory.AndAlso(and1, and2);
//新的Projection:
var newProjections = oldSelect.Projection.Select(e =>
{
if (e is { Expression: ColumnExpression col })
{
var newCol = new ColumnExpression(col.Name, SubTableName, col.Type, col.TypeMapping, col.IsNullable);
return new ProjectionExpression(newCol, e.Alias);
}
return e;
});
// 创建新的 SelectExpression,将子查询作为来源
var newSelect = new SelectExpression(
oldSelect.Alias,
[subquery],
newPredicate,
oldSelect.GroupBy,
oldSelect.Having,
[.. newProjections],
oldSelect.IsDistinct,
[],
null,
null,
null,
null);
//使用反射替换_projectionMapping变量:
ProjectionMapping.SetValue(newSelect, ProjectionMapping.GetValue(oldSelect));
return newSelect;
}
}
}
}
#pragma warning restore EF1001 // Internal EF Core API usage.
#endif
最后
实现上逻辑还是一致的,反正都是将Offset
转换为ROW_NUMBER()
子查询中,取行号数据
只是代码实现区别有一些,以前的EFCore底层代码很多已经不在可用比如直接使用PushdownIntoSubquery()
会报错,GenerateOuterColumn()
内部的扩展方法发生了破坏性更新导致不能再使用等!
如果你的程序需要升级到NET9
并还在使用早期数据库的话,可以引用我实现的代码部分,或者直接引用我发布的Nuget包
代码我放在了,任何问题欢迎Issue https://github.com/vipwan/Biwen.EFCore.UseRowNumberForPaging
.NET9 EFcore支持早期MSSQL数据库 ROW_NUMBER()分页的更多相关文章
- Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库
前言 在 .Net Core 2.2中 Microsoft.AspNetCore.App 默认内置了EntityFramework Core 包,所以在使用过程中,我们无需再从 NuGet 仓 ...
- 如何从40亿整数中找到不存在的一个 webservice Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库 WPF实战案例-打印 RabbitMQ与.net core(五) topic类型 与 headers类型 的Exchange
如何从40亿整数中找到不存在的一个 前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况 ...
- jDialects:一个从Hibernate抽取的支持70多种数据库方言的原生SQL分页工具
jDialects(https://git.oschina.net/drinkjava2/jdialects) 是一个收集了大多数已知数据库方言的Java小项目,通常可用来创建分页SQL和建表DDL语 ...
- mssql Row_Number() 分页 DISTINCT 问题
转载原文地址http://www.cnblogs.com/pumaboyd/archive/2008/04/20/1162376.html 这周碰到了很多奇怪的问题,有些是莫名的低级错误,有些这是一直 ...
- DBHelper (支持事务与数据库变更)
1 概述 更新内容:添加 "支持数据分页" 这个数据库操作类的主要特色有 1> 事务操作更加的方便 2> 变更数据库更加的容易 3> 支持数据分 ...
- 记一次SQLServer的分页优化兼谈谈使用Row_Number()分页存在的问题
最近有项目反应,在服务器CPU使用较高的时候,我们的事件查询页面非常的慢,查询几条记录竟然要4分钟甚至更长,而且在翻第二页的时候也是要这么多的时间,这肯定是不能接受的,也是让现场用SQLServerP ...
- SQL Server数据库ROW_NUMBER()函数使用详解
SQL Server数据库ROW_NUMBER()函数使用详解 摘自:http://database.51cto.com/art/201108/283399.htm SQL Server数据库ROW_ ...
- 使用Row_Number()分页优化
记一次SQLServer的分页优化兼谈谈使用Row_Number()分页存在的问题 最近有项目反应,在服务器CPU使用较高的时候,我们的事件查询页面非常的慢,查询几条记录竟然要4分钟甚至更长,而且 ...
- MSSQL数据库迁移到Oracle
MSSQL数据库迁移到Oracle 最近要把一个MSSQL数据库迁移到Oracle上面,打算借助PowerDesigner这个软件来实现;今天简单研究一下这个软件的运用;把一步简单的操作步骤记录下来: ...
- 通过 DynamicLinq 简单实现 N-Tier 部署下的服务端数据库通用分页
通过 DynamicLinq 简单实现 N-Tier 部署下的服务端数据库通用分页 YbSoftwareFactory 的 YbRapidSolution for WinForm 插件使用CSLA.N ...
随机推荐
- SciPy从入门到放弃
目录 SciPy简介 拟合与优化模块 求最小值 曲线拟合 线性代数模块 统计模块 直方图和概率密度函数 统计检验 SciPy简介 SciPy是一种以NumPy为基础,用于数学.工程及许多其他的科学任务 ...
- NumPy从入门到放弃
看前建议: 本文以jupyter notebook为编辑器进行示例,建议有一定python基础后再进行学习. python的安装:https://www.cnblogs.com/scfssq/p/17 ...
- SpringMVC:SpringMVC处理Ajax请求
目录 @RequestBody @RequestBody获取json格式的请求参数 @ResponseBody @ResponseBody响应浏览器json数据 @RestController注解 @ ...
- 《Effective TypeScript》条款21 - 类型扩展
本文主要通过一些实际的代码示例,来帮助大家理解什么是类型扩展,本文主要内容如下: 什么是类型扩展 代码示例 总结 什么是类型扩展? TypeScript 需要从你指定的单一值中决定一组可能的值,这个过 ...
- C# Dynamic 转换成 Dictionary,Dynamic 转换成 DataTable
部分软件开发的时候用到了 dynamic 类型,这个类型的数据不需要做其他处理的时候非常好用,但是需要对其中的数据调整的时候就不是那么好用了,这里提供两个常见的转换方式 Dynamic To Dict ...
- JavaScript——基础语法
书写语法 输出语句 变量 数据类型 运算符 == 与 === 区别: ==: 1.判断类型是否一样,如果不一样,则进行类型转换 ...
- jQuery父子页面之间元素、方法获取、调用
资源来自:https://www.cnblogs.com/it-xcn/p/5896231.html 一.jquery 父.子页面之间页面元素的获取,方法的调用: 1. 父页面获取子页面元素: 格式: ...
- SuperMap iServer8C证书过期如何解决
说明:该问题是SuperMap iServer8.0.2和8.1.0版本云许可模块问题,需要手动更新云许可HTTPS证书,可以升级到官网8.1.1/9D/10i等方式进行解决 针对无法升级或者老项目维 ...
- 第17天:信息打点-语言框架&开发组件&FastJson&Shiro&Log4j&SpringBoot等
框架:简单代码的一个整合库,如果使用框架就只需要学习使用框架调用即可 如:文件上传功能是需要很多代码来实现的,框架把这个代码进行封封装,调用即可 影响:如果采用框架开发,代码的安全性是取决于框架的过滤 ...
- C# ASP.NET Core Web API 框架 实现向手机发送验证码短信
本文章主要是在C# ASP.NET Core Web API框架实现向手机发送验证码短信功能.这里我选择是一个互亿无线短信验证码平台,其实像阿里云,腾讯云上面也可以. 首先我们先去 互亿无线 http ...