@@ -40,7 +40,7 @@ internal static class ExpressionHelpers
40
40
/// <exception cref="ArgumentException">
41
41
/// If the number of keys does not match the number of values.
42
42
/// </exception>
43
- public static ( Expression < Func < T , bool > > WhereExpression , int Offset , bool ReverseOrder ) BuildWhereExpression < T > (
43
+ public static ( Expression < Func < T , bool > > WhereExpression , int Offset ) BuildWhereExpression < T > (
44
44
ReadOnlySpan < CursorKey > keys ,
45
45
Cursor cursor ,
46
46
bool forward )
@@ -77,53 +77,34 @@ public static (Expression<Func<T, bool>> WhereExpression, int Offset, bool Rever
77
77
{
78
78
var handledKey = handled [ j ] ;
79
79
80
- keyExpr =
81
- Expression . Equal (
82
- Expression . Call (
83
- ReplaceParameter ( handledKey . Expression , parameter ) ,
84
- handledKey . CompareMethod ,
85
- cursorExpr [ j ] ) ,
86
- zero ) ;
87
-
88
- current = current is null
89
- ? keyExpr
90
- : Expression . AndAlso ( current , keyExpr ) ;
80
+ keyExpr = Expression . Equal (
81
+ Expression . Call ( ReplaceParameter ( handledKey . Expression , parameter ) , handledKey . CompareMethod ,
82
+ cursorExpr [ j ] ) , zero ) ;
83
+
84
+ current = current is null ? keyExpr : Expression . AndAlso ( current , keyExpr ) ;
91
85
}
92
86
93
87
var greaterThan = forward
94
- ? key . Direction is CursorKeyDirection . Ascending
95
- : key . Direction is CursorKeyDirection . Descending ;
96
-
97
- keyExpr =
98
- greaterThan
99
- ? Expression . GreaterThan (
100
- Expression . Call (
101
- ReplaceParameter ( key . Expression , parameter ) ,
102
- key . CompareMethod ,
103
- cursorExpr [ i ] ) ,
104
- zero )
105
- : Expression . LessThan (
106
- Expression . Call (
107
- ReplaceParameter ( key . Expression , parameter ) ,
108
- key . CompareMethod ,
109
- cursorExpr [ i ] ) ,
110
- zero ) ;
111
-
112
- current = current is null
113
- ? keyExpr
114
- : Expression . AndAlso ( current , keyExpr ) ;
115
- expression = expression is null
116
- ? current
117
- : Expression . OrElse ( expression , current ) ;
88
+ ? key . Direction == CursorKeyDirection . Ascending
89
+ : key . Direction == CursorKeyDirection . Descending ;
90
+
91
+ keyExpr = greaterThan
92
+ ? Expression . GreaterThan (
93
+ Expression . Call ( ReplaceParameter ( key . Expression , parameter ) , key . CompareMethod , cursorExpr [ i ] ) ,
94
+ zero )
95
+ : Expression . LessThan (
96
+ Expression . Call ( ReplaceParameter ( key . Expression , parameter ) , key . CompareMethod , cursorExpr [ i ] ) ,
97
+ zero ) ;
98
+
99
+ current = current is null ? keyExpr : Expression . AndAlso ( current , keyExpr ) ;
100
+ expression = expression is null ? current : Expression . OrElse ( expression , current ) ;
118
101
handled . Add ( key ) ;
119
102
}
120
103
121
- var offset = cursor . Offset ?? 0 ;
122
- var reverseOrder = offset < 0 ; // Reverse order if offset is negative
123
-
124
- return ( Expression . Lambda < Func < T , bool > > ( expression ! , parameter ) , Math . Abs ( offset ) , reverseOrder ) ;
104
+ return ( Expression . Lambda < Func < T , bool > > ( expression ! , parameter ) , cursor . Offset ?? 0 ) ;
125
105
}
126
106
107
+
127
108
/// <summary>
128
109
/// Build the select expression for a batch paging expression that uses grouping.
129
110
/// </summary>
@@ -151,12 +132,11 @@ public static (Expression<Func<T, bool>> WhereExpression, int Offset, bool Rever
151
132
/// <typeparam name="TV">
152
133
/// The value type.
153
134
/// </typeparam>
154
- /// <returns></returns>
155
135
/// <exception cref="ArgumentException">
156
136
/// If the number of keys is less than one or
157
137
/// the number of order expressions does not match the number of order methods.
158
138
/// </exception>
159
- public static ( Expression < Func < IGrouping < TK , TV > , Group < TK , TV > > > SelectExpression , bool ReverseOrder ) BuildBatchSelectExpression < TK , TV > (
139
+ public static BatchExpression < TK , TV > BuildBatchExpression < TK , TV > (
160
140
PagingArguments arguments ,
161
141
ReadOnlySpan < CursorKey > keys ,
162
142
ReadOnlySpan < LambdaExpression > orderExpressions ,
@@ -187,7 +167,8 @@ public static (Expression<Func<IGrouping<TK, TV>, Group<TK, TV>>> SelectExpressi
187
167
var methodName = forward ? orderMethods [ i ] : ReverseOrder ( orderMethods [ i ] ) ;
188
168
var orderExpression = orderExpressions [ i ] ;
189
169
var delegateType = typeof ( Func < , > ) . MakeGenericType ( typeof ( TV ) , orderExpression . Body . Type ) ;
190
- var typedOrderExpression = Expression . Lambda ( delegateType , orderExpression . Body , orderExpression . Parameters ) ;
170
+ var typedOrderExpression =
171
+ Expression . Lambda ( delegateType , orderExpression . Body , orderExpression . Parameters ) ;
191
172
192
173
var method = GetEnumerableMethod ( methodName , typeof ( TV ) , typedOrderExpression ) ;
193
174
@@ -197,44 +178,68 @@ public static (Expression<Func<IGrouping<TK, TV>, Group<TK, TV>>> SelectExpressi
197
178
typedOrderExpression ) ;
198
179
}
199
180
200
- var reverseOrder = false ;
201
181
var offset = 0 ;
182
+ var usesRelativeCursors = false ;
183
+ Cursor ? cursor = null ;
202
184
203
185
if ( arguments . After is not null )
204
186
{
205
- var cursor = CursorParser . Parse ( arguments . After , keys ) ;
206
- var ( whereExpr , cursorOffset , reverse ) = BuildWhereExpression < TV > ( keys , cursor , forward : true ) ;
187
+ cursor = CursorParser . Parse ( arguments . After , keys ) ;
188
+ var ( whereExpr , cursorOffset ) = BuildWhereExpression < TV > ( keys , cursor , forward : true ) ;
207
189
source = Expression . Call ( typeof ( Enumerable ) , "Where" , [ typeof ( TV ) ] , source , whereExpr ) ;
208
190
offset = cursorOffset ;
209
- reverseOrder = reverse ;
191
+
192
+ if ( cursor . IsRelative )
193
+ {
194
+ usesRelativeCursors = true ;
195
+ }
210
196
}
211
197
212
198
if ( arguments . Before is not null )
213
199
{
214
- var cursor = CursorParser . Parse ( arguments . Before , keys ) ;
215
- var ( whereExpr , cursorOffset , reverse ) = BuildWhereExpression < TV > ( keys , cursor , forward : false ) ;
200
+ if ( usesRelativeCursors )
201
+ {
202
+ throw new ArgumentException (
203
+ "You cannot use `before` and `after` with relative cursors at the same time." ,
204
+ nameof ( arguments ) ) ;
205
+ }
206
+
207
+ cursor = CursorParser . Parse ( arguments . Before , keys ) ;
208
+ var ( whereExpr , cursorOffset ) = BuildWhereExpression < TV > ( keys , cursor , forward : false ) ;
216
209
source = Expression . Call ( typeof ( Enumerable ) , "Where" , [ typeof ( TV ) ] , source , whereExpr ) ;
217
210
offset = cursorOffset ;
218
- reverseOrder = reverse ;
219
211
}
220
212
221
- if ( reverseOrder )
213
+ if ( arguments . First is not null )
222
214
{
223
- source = Expression . Call (
224
- typeof ( Enumerable ) ,
225
- "Reverse" ,
226
- [ typeof ( TV ) ] ,
227
- source ) ;
215
+ requestedCount = arguments . First . Value ;
216
+ }
217
+
218
+ if ( arguments . Last is not null )
219
+ {
220
+ requestedCount = arguments . Last . Value ;
221
+ }
222
+
223
+ if ( arguments . EnableRelativeCursors && cursor ? . IsRelative == true )
224
+ {
225
+ if ( ( arguments . Last is not null && cursor . Offset > 0 ) || ( arguments . First is not null && cursor . Offset < 0 ) )
226
+ {
227
+ throw new ArgumentException (
228
+ "Positive offsets are not allowed with `last`, and negative offsets are not allowed with `first`." ,
229
+ nameof ( arguments ) ) ;
230
+ }
228
231
}
229
232
233
+ offset = Math . Abs ( offset ) ;
234
+
230
235
if ( offset > 0 )
231
236
{
232
237
source = Expression . Call (
233
238
typeof ( Enumerable ) ,
234
239
"Skip" ,
235
240
[ typeof ( TV ) ] ,
236
241
source ,
237
- Expression . Constant ( offset ) ) ;
242
+ Expression . Constant ( offset * requestedCount ) ) ;
238
243
}
239
244
240
245
if ( arguments . First is not null )
@@ -245,7 +250,6 @@ public static (Expression<Func<IGrouping<TK, TV>, Group<TK, TV>>> SelectExpressi
245
250
[ typeof ( TV ) ] ,
246
251
source ,
247
252
Expression . Constant ( arguments . First . Value + 1 ) ) ;
248
- requestedCount = arguments . First . Value ;
249
253
}
250
254
251
255
if ( arguments . Last is not null )
@@ -256,7 +260,6 @@ public static (Expression<Func<IGrouping<TK, TV>, Group<TK, TV>>> SelectExpressi
256
260
[ typeof ( TV ) ] ,
257
261
source ,
258
262
Expression . Constant ( arguments . Last . Value + 1 ) ) ;
259
- requestedCount = arguments . Last . Value ;
260
263
}
261
264
262
265
source = Expression . Call (
@@ -273,7 +276,10 @@ public static (Expression<Func<IGrouping<TK, TV>, Group<TK, TV>>> SelectExpressi
273
276
} ;
274
277
275
278
var createGroup = Expression . MemberInit ( Expression . New ( groupType ) , bindings ) ;
276
- return ( Expression . Lambda < Func < IGrouping < TK , TV > , Group < TK , TV > > > ( createGroup , group ) , reverseOrder ) ;
279
+ return new BatchExpression < TK , TV > (
280
+ Expression . Lambda < Func < IGrouping < TK , TV > , Group < TK , TV > > > ( createGroup , group ) ,
281
+ arguments . Last is not null ,
282
+ cursor ) ;
277
283
278
284
static string ReverseOrder ( string method )
279
285
=> method switch
@@ -292,84 +298,6 @@ static MethodInfo GetEnumerableMethod(string methodName, Type elementType, Lambd
292
298
. MakeGenericMethod ( elementType , keySelector . Body . Type ) ;
293
299
}
294
300
295
- private static MethodCallExpression BuildBatchWhereExpression < T > (
296
- Expression enumerable ,
297
- ReadOnlySpan < CursorKey > keys ,
298
- object ? [ ] cursor ,
299
- bool forward )
300
- {
301
- var cursorExpr = new Expression [ cursor . Length ] ;
302
-
303
- for ( var i = 0 ; i < cursor . Length ; i ++ )
304
- {
305
- cursorExpr [ i ] = CreateParameter ( cursor [ i ] , keys [ i ] . Expression . ReturnType ) ;
306
- }
307
-
308
- var handled = new List < CursorKey > ( ) ;
309
- Expression ? expression = null ;
310
-
311
- var parameter = Expression . Parameter ( typeof ( T ) , "t" ) ;
312
- var zero = Expression . Constant ( 0 ) ;
313
-
314
- for ( var i = 0 ; i < keys . Length ; i ++ )
315
- {
316
- var key = keys [ i ] ;
317
- Expression ? current = null ;
318
- Expression keyExpr ;
319
-
320
- for ( var j = 0 ; j < handled . Count ; j ++ )
321
- {
322
- var handledKey = handled [ j ] ;
323
-
324
- keyExpr =
325
- Expression . Equal (
326
- Expression . Call (
327
- ReplaceParameter ( handledKey . Expression , parameter ) ,
328
- handledKey . CompareMethod ,
329
- cursorExpr [ j ] ) ,
330
- zero ) ;
331
-
332
- current = current is null
333
- ? keyExpr
334
- : Expression . AndAlso ( current , keyExpr ) ;
335
- }
336
-
337
- var greaterThan = forward
338
- ? key . Direction is CursorKeyDirection . Ascending
339
- : key . Direction is CursorKeyDirection . Descending ;
340
-
341
- keyExpr =
342
- greaterThan
343
- ? Expression . GreaterThan (
344
- Expression . Call (
345
- ReplaceParameter ( key . Expression , parameter ) ,
346
- key . CompareMethod ,
347
- cursorExpr [ i ] ) ,
348
- zero )
349
- : Expression . LessThan (
350
- Expression . Call (
351
- ReplaceParameter ( key . Expression , parameter ) ,
352
- key . CompareMethod ,
353
- cursorExpr [ i ] ) ,
354
- zero ) ;
355
-
356
- current = current is null
357
- ? keyExpr
358
- : Expression . AndAlso ( current , keyExpr ) ;
359
- expression = expression is null
360
- ? current
361
- : Expression . OrElse ( expression , current ) ;
362
- handled . Add ( key ) ;
363
- }
364
-
365
- return Expression . Call (
366
- typeof ( Enumerable ) ,
367
- "Where" ,
368
- [ typeof ( T ) ] ,
369
- enumerable ,
370
- Expression . Lambda < Func < T , bool > > ( expression ! , parameter ) ) ;
371
- }
372
-
373
301
/// <summary>
374
302
/// Extracts and removes the orderBy and thenBy expressions from the given expression tree.
375
303
/// </summary>
@@ -504,4 +432,14 @@ private static Expression StripQuotes(Expression e)
504
432
return e ;
505
433
}
506
434
}
435
+
436
+ internal readonly struct BatchExpression < TK , TV > (
437
+ Expression < Func < IGrouping < TK , TV > , Group < TK , TV > > > selectExpression ,
438
+ bool isBackward ,
439
+ Cursor ? cursor )
440
+ {
441
+ public Expression < Func < IGrouping < TK , TV > , Group < TK , TV > > > SelectExpression { get ; } = selectExpression ;
442
+ public bool IsBackward { get ; } = isBackward ;
443
+ public Cursor ? Cursor { get ; } = cursor ;
444
+ }
507
445
}
0 commit comments