EFCore对于SqlServer2008版本及以下版本,不能使用Skip().Take()来分页查询的问题。我们可以通过重写IQueryTranslationPostprocessorFactory来实现。涉及的相应代码如下。
using Microsoft.EntityFrameworkCore.Query;
namespace OpenDeepSpace.EntityFrameworkCore;
/// <summary>
/// SqlServer 查询转换工厂(处理 SqlServer 2008 分页问题不支持Offset的问题)
/// 通过dbContextOptions.Replace<IQueryTranslationPostprocessorFactory,SqlServer2008QueryTranslationPostprocessorFactory>即可
/// </summary>
public class SqlServer2008QueryTranslationPostprocessorFactory : IQueryTranslationPostprocessorFactory
{
/// <summary>
/// 查询转换依赖集合
/// </summary>
private readonly QueryTranslationPostprocessorDependencies _dependencies;
/// <summary>
/// 关系查询转换依赖集合
/// </summary>
private readonly RelationalQueryTranslationPostprocessorDependencies _relationalDependencies;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="dependencies"></param>
/// <param name="relationalDependencies"></param>
public SqlServer2008QueryTranslationPostprocessorFactory(QueryTranslationPostprocessorDependencies dependencies, RelationalQueryTranslationPostprocessorDependencies relationalDependencies)
{
_dependencies = dependencies;
_relationalDependencies = relationalDependencies;
}
/// <summary>
/// 创建查询转换实例工厂
/// </summary>
/// <param name="queryCompilationContext"></param>
/// <returns></returns>
public virtual QueryTranslationPostprocessor Create(QueryCompilationContext queryCompilationContext)
{
return new SqlServer2008QueryTranslationPostprocessor(
_dependencies,
_relationalDependencies,
queryCompilationContext);
}
}
using Microsoft.EntityFrameworkCore.Query;
using System.Linq.Expressions;
namespace OpenDeepSpace.EntityFrameworkCore;
/// <summary>
/// SqlServer 查询转换器
/// </summary>
public class SqlServer2008QueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="dependencies"></param>
/// <param name="relationalDependencies"></param>
/// <param name="queryCompilationContext"></param>
public SqlServer2008QueryTranslationPostprocessor(QueryTranslationPostprocessorDependencies dependencies, RelationalQueryTranslationPostprocessorDependencies relationalDependencies, QueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
}
/// <summary>
/// 替换分页语句
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
public override Expression Process(Expression query)
{
query = base.Process(query);
query = new SqlServer2008OffsetToRowNumberConvertVisitor(query, RelationalDependencies.SqlExpressionFactory).Visit(query);
return query;
}
}
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using System.Linq.Expressions;
using System.Reflection;
namespace OpenDeepSpace.EntityFrameworkCore;
/// <summary>
/// 处理 .Skip().Take() 表达式问题
/// </summary>
public class SqlServer2008OffsetToRowNumberConvertVisitor : ExpressionVisitor
{
/// <summary>
/// 筛选列访问器
/// </summary>
private static readonly MethodInfo GenerateOuterColumnAccessor;
/// <summary>
/// 引用 TableReferenceExpression 类型
/// </summary>
private static readonly Type TableReferenceExpressionType;
/// <summary>
/// 表达式根节点
/// </summary>
private readonly Expression root;
/// <summary>
/// Sql 表达式工厂
/// </summary>
private readonly ISqlExpressionFactory sqlExpressionFactory;
/// <summary>
/// 静态构造函数
/// </summary>
static SqlServer2008OffsetToRowNumberConvertVisitor()
{
var method = typeof(SelectExpression).GetMethod("GenerateOuterColumn", BindingFlags.NonPublic | BindingFlags.Instance);
if (!typeof(ColumnExpression).IsAssignableFrom(method?.ReturnType))
throw new InvalidOperationException("SelectExpression.GenerateOuterColumn() is not found.");
TableReferenceExpressionType = method.GetParameters().First().ParameterType;
GenerateOuterColumnAccessor = method;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="root"></param>
/// <param name="sqlExpressionFactory"></param>
public SqlServer2008OffsetToRowNumberConvertVisitor(Expression root, ISqlExpressionFactory sqlExpressionFactory)
{
this.root = root;
this.sqlExpressionFactory = sqlExpressionFactory;
}
/// <summary>
/// 替换表达式
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitExtension(Expression node)
{
if (node is ShapedQueryExpression shapedQueryExpression)
{
return shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression);
}
if (node is SelectExpression se)
node = VisitSelect(se);
return base.VisitExtension(node);
}
/// <summary>
/// 更新 Select 语句
/// </summary>
/// <param name="selectExpression"></param>
/// <returns></returns>
private Expression VisitSelect(SelectExpression selectExpression)
{
var oldOffset = selectExpression.Offset;
if (oldOffset == null)
return selectExpression;
var oldLimit = selectExpression.Limit;
var oldOrderings = selectExpression.Orderings;
// 在子查询中 OrderBy 必须写 Top 数量
var newOrderings = oldOrderings.Count > 0 && (oldLimit != null || selectExpression == root)
? oldOrderings.ToList()
: new List<OrderingExpression>();
// 更新表达式
selectExpression = selectExpression.Update(selectExpression.Projection.ToList(),
selectExpression.Tables.ToList(),
selectExpression.Predicate,
selectExpression.GroupBy.ToList(),
selectExpression.Having,
orderings: newOrderings,
limit: null,
offset: null);
var rowOrderings = oldOrderings.Count != 0 ? oldOrderings
: new[] { new OrderingExpression(new SqlFragmentExpression("(SELECT 1)"), true) };
selectExpression.PushdownIntoSubquery();
var subQuery = (SelectExpression)selectExpression.Tables[0];
var projection = new RowNumberExpression(Array.Empty<SqlExpression>(), rowOrderings, oldOffset.TypeMapping);
var left = GenerateOuterColumnAccessor.Invoke(subQuery
, new object[]
{
Activator.CreateInstance(TableReferenceExpressionType, new object[] { subQuery,subQuery.Alias }),
projection,
"row",
true
}) as ColumnExpression;
selectExpression.ApplyPredicate(sqlExpressionFactory.GreaterThan(left, oldOffset));
if (oldLimit != null)
{
if (oldOrderings.Count == 0)
{
selectExpression.ApplyPredicate(sqlExpressionFactory.LessThanOrEqual(left, sqlExpressionFactory.Add(oldOffset, oldLimit)));
}
else
{
// 这里不支持子查询的 OrderBy 操作
selectExpression.ApplyLimit(oldLimit);
}
}
return selectExpression;
}
}