Skip to content

Commit 16b6da0

Browse files
author
ps.sergeev
committed
merge branch
2 parents 2659e4c + dde9d1b commit 16b6da0

File tree

2 files changed

+61
-78
lines changed

2 files changed

+61
-78
lines changed

EntityFrameworkCore.UseRowNumberForPaging/Offset2RowNumberConvertVisitor.EfCore8.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ static Offset2RowNumberConvertVisitor()
1919
var method = typeof(SelectExpression).GetMethod("GenerateOuterColumn", BindingFlags.NonPublic | BindingFlags.Instance);
2020
if (!typeof(ColumnExpression).IsAssignableFrom(method?.ReturnType))
2121
{
22-
throw new InvalidOperationException("SelectExpression.GenerateOuterColum() was not found");
22+
throw new InvalidOperationException("SelectExpression.GenerateOuterColumn() was not found");
2323
}
2424

2525
TableReferenceExpressionType = method.GetParameters().First().ParameterType;
@@ -43,7 +43,7 @@ protected override Expression VisitExtension(Expression node)
4343
}
4444
if (node is SelectExpression se)
4545
{
46-
return VisitSelect(se);
46+
node = VisitSelect(se);
4747
}
4848
return base.VisitExtension(node);
4949
}
Lines changed: 59 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#if USE_EF_CORE_9
2-
using System.Collections.Generic;
2+
using System;
33
using System.Linq;
44
using System.Linq.Expressions;
55
using System.Reflection;
@@ -14,105 +14,88 @@ internal class Offset2RowNumberConvertVisitor(
1414
SqlAliasManager sqlAliasManager
1515
) : ExpressionVisitor
1616
{
17+
private static readonly MethodInfo GenerateOuterColumnAccessor;
18+
19+
static Offset2RowNumberConvertVisitor()
20+
{
21+
var method = typeof(SelectExpression).GetMethod("GenerateOuterColumn", BindingFlags.NonPublic | BindingFlags.Instance);
22+
if (!typeof(ColumnExpression).IsAssignableFrom(method?.ReturnType))
23+
{
24+
throw new InvalidOperationException("SelectExpression.GenerateOuterColumn() was not found");
25+
}
26+
GenerateOuterColumnAccessor = method;
27+
}
28+
29+
1730
private readonly Expression root = root;
1831
private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory;
1932
private readonly SqlAliasManager sqlAliasManager = sqlAliasManager;
2033

21-
protected override Expression VisitExtension(Expression node) => node switch
22-
{
23-
ShapedQueryExpression shapedQueryExpression => shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), Visit(shapedQueryExpression.ShaperExpression)),
24-
SelectExpression se => VisitSelect(se),
25-
_ => base.VisitExtension(node),
26-
};
27-
28-
private SelectExpression VisitSelect(SelectExpression selectExpression)
34+
protected override Expression VisitExtension(Expression node)
2935
{
30-
// if we have no offset, we do not need to use ROW_NUMBER for offset calculations
31-
if (selectExpression.Offset == null)
36+
if (node is ShapedQueryExpression shapedQueryExpression)
3237
{
33-
return selectExpression;
38+
return shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), Visit(shapedQueryExpression.ShaperExpression));
3439
}
35-
var isRootQuery = selectExpression == root;
40+
if (node is SelectExpression se)
41+
{
42+
node = VisitSelect(se);
43+
}
44+
return base.VisitExtension(node);
45+
}
3646

37-
// store offset, limit and orderings
47+
private Expression VisitSelect(SelectExpression selectExpression)
48+
{
3849
var oldOffset = selectExpression.Offset;
50+
if (oldOffset == null)
51+
return selectExpression;
3952
var oldLimit = selectExpression.Limit;
4053
var oldOrderings = selectExpression.Orderings;
54+
var newOrderings = oldOrderings.Count > 0 && (oldLimit != null || selectExpression == root)
55+
? oldOrderings.ToList()
56+
: [];
57+
// Change SelectExpression
58+
selectExpression = selectExpression.Update(projections: selectExpression.Projection.ToList(),
59+
tables: selectExpression.Tables.ToList(),
60+
predicate: selectExpression.Predicate,
61+
groupBy: selectExpression.GroupBy.ToList(),
62+
having: selectExpression.Having,
63+
orderings: newOrderings,
64+
limit: null,
65+
offset: null);
66+
var rowOrderings = oldOrderings.Count != 0 ? oldOrderings
67+
: [new OrderingExpression(new SqlFragmentExpression("(SELECT 1)"), true)];
4168

42-
// remove offset and limit by creating new select expression from old one
43-
// we can't use SelectExpression.Update because that breaks PushDownIntoSubquery
44-
var enhancedSelect = new SelectExpression(
45-
alias: null,
46-
tables: new(selectExpression.Tables),
47-
predicate: selectExpression.Predicate,
48-
groupBy: new(selectExpression.GroupBy),
49-
having: selectExpression.Having,
50-
projections: new(selectExpression.Projection),
51-
distinct: selectExpression.IsDistinct,
52-
orderings: isRootQuery ? [] : new(selectExpression.Orderings),
53-
offset: null,
54-
limit: null,
55-
tags: selectExpression.Tags,
56-
annotations: null,
57-
sqlAliasManager: sqlAliasManager,
58-
isMutable: true
59-
);
60-
// set up row_number expression
61-
var rowNumber = new RowNumberExpression([], isRootQuery ? [ new(new SqlFragmentExpression("(SELECT 1)"), true) ] : oldOrderings, oldOffset.TypeMapping);
62-
enhancedSelect.AddToProjection(rowNumber);
63-
enhancedSelect.PushdownIntoSubquery();
69+
// restore sql alias manager in updated expression
70+
typeof(SelectExpression)
71+
.GetField("_sqlAliasManager", BindingFlags.Instance | BindingFlags.NonPublic)
72+
.SetValue(selectExpression, sqlAliasManager);
6473

65-
// restore ordering to outer select after earlier removal
66-
if (isRootQuery)
67-
{
68-
foreach (var orderingClause in oldOrderings)
69-
{
70-
selectExpression.AppendOrdering(orderingClause);
71-
}
72-
}
74+
selectExpression.PushdownIntoSubquery();
7375

74-
// generate subselect rownumber access expression
75-
var innerTable = enhancedSelect.Tables[0];
76-
var rowNumberColname = enhancedSelect.Projection[enhancedSelect.Projection.Count - 1].Alias;
77-
var rowNumberAlias = enhancedSelect.CreateColumnExpression(innerTable, rowNumberColname, typeof(int), null, false);
76+
var subQuery = (SelectExpression)selectExpression.Tables[0];
77+
var projection = new RowNumberExpression([], rowOrderings, oldOffset.TypeMapping);
78+
var left = GenerateOuterColumnAccessor.Invoke(
79+
subQuery,
80+
[
81+
subQuery.Alias,
82+
projection,
83+
sqlAliasManager.GenerateTableAlias("row"),
84+
]) as ColumnExpression;
85+
selectExpression.ApplyPredicate(sqlExpressionFactory.GreaterThan(left!, oldOffset));
7886

79-
// apply offset and limit
80-
var rowNumberGtOffset = sqlExpressionFactory.GreaterThan(rowNumberAlias, oldOffset);
81-
enhancedSelect.ApplyPredicate(rowNumberGtOffset);
8287
if (oldLimit != null)
8388
{
8489
if (oldOrderings.Count == 0)
8590
{
86-
var rowNumberLimiting = sqlExpressionFactory.LessThanOrEqual(rowNumberAlias, sqlExpressionFactory.Add(oldOffset, oldLimit));
87-
enhancedSelect.ApplyPredicate(rowNumberLimiting);
91+
selectExpression.ApplyPredicate(sqlExpressionFactory.LessThanOrEqual(left, sqlExpressionFactory.Add(oldOffset, oldLimit)));
8892
}
8993
else
9094
{
91-
enhancedSelect.ApplyLimit(oldLimit);
95+
selectExpression.ApplyLimit(oldLimit);
9296
}
9397
}
94-
95-
enhancedSelect.ApplyProjection(); // to make immutable
96-
var restoredProjections = enhancedSelect.Projection
97-
.Where(p => p.Alias != rowNumberColname)
98-
.ToList();
99-
var result = enhancedSelect.Update(
100-
enhancedSelect.Tables,
101-
enhancedSelect.Predicate,
102-
enhancedSelect.GroupBy,
103-
enhancedSelect.Having,
104-
restoredProjections,
105-
enhancedSelect.Orderings,
106-
enhancedSelect.Offset,
107-
enhancedSelect.Limit
108-
);
109-
110-
// restore projection member binding lookup capabilities via reflection magic
111-
var clientProjections = typeof(SelectExpression).GetField("_clientProjections", BindingFlags.NonPublic | BindingFlags.Instance);
112-
clientProjections.SetValue(result, clientProjections.GetValue(selectExpression));
113-
var projectionMapping = typeof(SelectExpression).GetField("_projectionMapping", BindingFlags.NonPublic | BindingFlags.Instance);
114-
projectionMapping.SetValue(result, projectionMapping.GetValue(selectExpression));
115-
return result;
98+
return selectExpression;
11699
}
117100
}
118101
#endif

0 commit comments

Comments
 (0)