.NET9 EFcore支持早期MSSQL数据库 ROW_NUMBER()分页功能
前言
NET程序员是很幸福的,MS在上个月发布了NET9.0
RTM,带来了不少的新特性,但是呢,还有很多同学软硬件都还没跟上时代的步伐,比如,自己的电脑还在跑Win7,公司服务器还在跑MSSQL2005-2008的!
这不就引入了我们本文要探索的问题,因为MS早在EFcore3.1
后就不再内置支持ROW_NUMBER()
了,以至于需要兼容分页的代码都需要自行处理,当然同学们如果对EFCore没有依赖度也可以使用其他的ORM选型,当然如果不想折腾EFCore也能使用万能的RawSql拼接执行也是可以的
最近自己发的Nuget包有个国外的程序员朋友提了一个Issue,以至于我马上行动起来
在EFCore9
中, 以前兼容的好好的ROW_NUMBER()
代码,升级尝鲜后发现跑不起来了,这主要是因为新版本的EFCore9做了很多破坏性更新,以至于我们不得不研究新的底层代码!
兼容实现
之前发布过一个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; 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_";//下标避免数据表存在字段 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); // 创建子查询 IListprojections = [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 replace var pm = new ProjectionMember(); var projectionMapping = new Dictionary { { pm, oldSelect.GetProjection(new ProjectionBindingExpression(null,pm,null)) } }; newSelect.ReplaceProjection(projectionMapping); return newSelect; } } } } #pragma warning restore EF1001 // Internal EF Core API usage. #endif
最后
实现上逻辑还是一致的,反正都是将Offset
转换为ROW_NUMBER()
子查询中,取行号数据
只是代码实现区别有一些,以前的EFCore底层代码很多已经不在可用比如直接使用PushdownIntoSubquery()
会报错,GenerateOuterColumn()
内部的扩展方法发生了破坏性更新导致不能再使用等!
如果你的程序需要升级到NET9
并还在使用早期数据库的话,可以引用我实现的代码部分,或者直接引用我发布的Nuget包
代码我放在了GitHub,任何问题欢迎Issue https://github.com/vipwan/Biwen.EFCore.UseRowNumberForPaging
到此这篇关于.NET9 EFcore支持早期MSSQL数据库 ROW_NUMBER()分页的文章就介绍到这了,更多相关.NET9 EFcore MSSQL数据库 ROW_NUMBER()分页内容请搜索科站长以前的文章或继续浏览下面的相关文章希望大家以后多多支持科站长!
栏 目:ASP.NET
下一篇:如何使用Microsoft.Extensions.AI简化.NET中的AI集成
本文标题:.NET9 EFcore支持早期MSSQL数据库 ROW_NUMBER()分页功能
本文地址:https://www.fushidao.cc/wangluobiancheng/3292.html
您可能感兴趣的文章
- 03-31详解如何在.NET代码中使用本地部署的Deepseek语言模型
- 02-06.net core如何使用Nacos注册中心
- 01-28使用.NET8构建一个高效的时间日期帮助类
- 01-26.NET Core GC压缩(compact_phase)底层原理解析
- 01-24在ASP.NET中读写TXT文本文件的多种方法
- 01-24在ASP.NET中读写XML数据的多种方法
- 01-24.NET轻松实现Excel转PDF的三种方法详解
- 01-23.NET9 AOT部署方案详解
- 01-23.NET NativeAOT 用法指南
- 01-23iis部署前后端分离项目全过程(Vuet前端和.NET6后端)


阅读排行
推荐教程
- 03-31详解如何在.NET代码中使用本地部署的Deepseek语言模型
- 11-23移动互联网广告有哪些模式?
- 11-22.net 应对网站访问压力的方案总结
- 11-22详解ASP.NET提取多层嵌套json数据的方法
- 11-23网站投放广告如何达到最好的效果
- 11-22.net 应对网站访问压力的方案总结
- 11-23网站打开速度慢解决办法
- 11-23草根站长为什么喜欢做门户站
- 11-22ASP.NET MVC分页问题解决
- 11-22ASP.NET编程简单实现生成静态页面的方法