.NET9?EFcore支持早期MSSQL數(shù)據(jù)庫(kù)?ROW_NUMBER()分頁(yè)功能
前言
NET程序員是很幸福的,MS在上個(gè)月發(fā)布了NET9.0RTM,帶來(lái)了不少的新特性,但是呢,還有很多同學(xué)軟硬件都還沒(méi)跟上時(shí)代的步伐,比如,自己的電腦還在跑Win7,公司服務(wù)器還在跑MSSQL2005-2008的!
這不就引入了我們本文要探索的問(wèn)題,因?yàn)镸S早在EFcore3.1后就不再內(nèi)置支持ROW_NUMBER()了,以至于需要兼容分頁(yè)的代碼都需要自行處理,當(dāng)然同學(xué)們?nèi)绻麑?duì)EFCore沒(méi)有依賴(lài)度也可以使用其他的ORM選型,當(dāng)然如果不想折騰EFCore也能使用萬(wàn)能的RawSql拼接執(zhí)行也是可以的 ??
最近自己發(fā)的Nuget包有個(gè)國(guó)外的程序員朋友提了一個(gè)Issue,以至于我馬上行動(dòng)起來(lái)

在EFCore9中, 以前兼容的好好的ROW_NUMBER()代碼,升級(jí)嘗鮮后發(fā)現(xiàn)跑不起來(lái)了,這主要是因?yàn)樾掳姹镜腅FCore9做了很多破壞性更新,以至于我們不得不研究新的底層代碼!
兼容實(shí)現(xiàn)
之前發(fā)布過(guò)一個(gè)Nuget包,代碼主要是基于以前道友兼容EFCore7適配到EFCore8的兼容,代碼也不多變化也不大,不過(guò)呢,升級(jí)到EFCore9后發(fā)現(xiàn)底層的API全變了,不得不重新再實(shí)現(xiàn)一遍!
以下是兼容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_";//下標(biāo)避免數(shù)據(jù)表存在字段
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);
// 創(chuàng)建子查詢(xún)
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,
[],//排序已經(jīng)在rowNumber中了
null,
null,
null,
null);
//構(gòu)造新的條件:
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;
});
// 創(chuàng)建新的 SelectExpression,將子查詢(xún)作為來(lái)源
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<ProjectionMember, Expression>
{
{
pm,
oldSelect.GetProjection(new ProjectionBindingExpression(null,pm,null))
}
};
newSelect.ReplaceProjection(projectionMapping);
return newSelect;
}
}
}
}
#pragma warning restore EF1001 // Internal EF Core API usage.
#endif最后
實(shí)現(xiàn)上邏輯還是一致的,反正都是將Offset轉(zhuǎn)換為ROW_NUMBER()子查詢(xún)中,取行號(hào)數(shù)據(jù)
只是代碼實(shí)現(xiàn)區(qū)別有一些,以前的EFCore底層代碼很多已經(jīng)不在可用比如直接使用PushdownIntoSubquery()會(huì)報(bào)錯(cuò),GenerateOuterColumn()內(nèi)部的擴(kuò)展方法發(fā)生了破壞性更新導(dǎo)致不能再使用等!
如果你的程序需要升級(jí)到NET9并還在使用早期數(shù)據(jù)庫(kù)的話(huà),可以引用我實(shí)現(xiàn)的代碼部分,或者直接引用我發(fā)布的Nuget包
<PackageReference Include="Biwen.EFCore.UseRowNumberForPaging" Version="2.1.1" />
代碼我放在了GitHub,任何問(wèn)題歡迎Issue https://github.com/vipwan/Biwen.EFCore.UseRowNumberForPaging
到此這篇關(guān)于.NET9 EFcore支持早期MSSQL數(shù)據(jù)庫(kù) ROW_NUMBER()分頁(yè)的文章就介紹到這了,更多相關(guān).NET9 EFcore MSSQL數(shù)據(jù)庫(kù) ROW_NUMBER()分頁(yè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
.Net Core配置與自動(dòng)更新的實(shí)現(xiàn)方法
下面小編就為大家分享一篇.Net Core配置與自動(dòng)更新的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
ASP.NET Core中使用LazyCache的全過(guò)程
這篇文章主要給大家介紹了關(guān)于ASP.NET Core中使用LazyCache的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
asp.net slickupload 使用方法(文件上傳)
asp.net下使用slickupload上傳文件的代碼2009-05-05
ASP.NET?MVC實(shí)現(xiàn)樹(shù)形導(dǎo)航菜單
這篇文章介紹了ASP.NET?MVC實(shí)現(xiàn)樹(shù)形導(dǎo)航菜單的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09
Asp.Net Core MVC項(xiàng)目實(shí)現(xiàn)多語(yǔ)言實(shí)例(Globalization/Localization)
本篇文章主要介紹了Asp.Net Core MVC項(xiàng)目實(shí)現(xiàn)多語(yǔ)言實(shí)例(Globalization/Localization) ,具有一定的參考價(jià)值,有興趣的可以了解一下2017-06-06
ASP.NET.4.5.1+MVC5.0設(shè)置系統(tǒng)角色與權(quán)限(一)
這篇文章主要介紹了ASP.NET.4.5.1+MVC5.0設(shè)置系統(tǒng)角色與權(quán)限的部分內(nèi)容,后續(xù)我們將繼續(xù)討論這個(gè)話(huà)題,希望小伙伴們喜歡。2015-01-01
DataGridView自動(dòng)調(diào)整行高和行寬
根據(jù)數(shù)據(jù)內(nèi)容自動(dòng)調(diào)整列寬,根據(jù)數(shù)據(jù)內(nèi)容自動(dòng)調(diào)整行高2009-04-04
c# NameValueCollection類(lèi)讀取配置信息
c#中的NameValueCollection類(lèi)讀取配置信息,大家可以參考下。2009-04-04

