11#if USE_EF_CORE_9
2- using System . Collections . Generic ;
2+ using System ;
33using System . Linq ;
44using System . Linq . Expressions ;
55using 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