1+ using System ;
2+ using System . Linq ;
3+ using System . Linq . Expressions ;
4+
5+ namespace Eocron . Algorithms . Queryable . Paging
6+ {
7+ /// <summary>
8+ /// Provides extension to efficiently page through any queryable, without full scan
9+ /// </summary>
10+ public static class PagingQueryableExtensions
11+ {
12+ /// <summary>
13+ /// Apply WHERE condition to IQueryable if continuation token is not empty.
14+ /// It will not apply Skip/Take operations, you should do it yourself.
15+ /// </summary>
16+ /// <param name="source"></param>
17+ /// <param name="configuration"></param>
18+ /// <param name="continuationToken"></param>
19+ /// <typeparam name="TEntity"></typeparam>
20+ /// <returns></returns>
21+ public static IQueryable < TEntity > Continue < TEntity > (
22+ this IQueryable < TEntity > source ,
23+ PagingConfiguration < TEntity > configuration ,
24+ string continuationToken ) where TEntity : class
25+ {
26+ if ( source == null )
27+ throw new ArgumentNullException ( nameof ( source ) ) ;
28+ if ( configuration == null )
29+ throw new ArgumentNullException ( nameof ( configuration ) ) ;
30+
31+ var result = source ;
32+ result = ApplyOrdering ( result , configuration ) ;
33+ if ( string . IsNullOrWhiteSpace ( continuationToken ) )
34+ return result ;
35+ result = result . Where ( CreateSkipCondition ( configuration , continuationToken ) ) ;
36+ return result ;
37+ }
38+
39+ private static Expression < Func < TEntity , bool > > CreateSkipCondition < TEntity > (
40+ PagingConfiguration < TEntity > configuration ,
41+ string continuationToken )
42+ {
43+ var keyValues = configuration . GetKeyValues ( continuationToken ) . Select ( Expression . Constant ) . ToList ( ) ;
44+ Expression predicate = null ;
45+
46+ for ( var i = configuration . Keys . Count - 1 ; i >= 0 ; i -- )
47+ {
48+ var keyCfg = configuration . Keys [ i ] ;
49+ var keyValue = keyValues [ i ] ;
50+
51+ var comparison = keyCfg . IsDescending
52+ ? Expression . LessThan ( keyCfg . KeySelector . Body , keyValue )
53+ : Expression . GreaterThan ( keyCfg . KeySelector . Body , keyValue ) ;
54+
55+ for ( var j = 0 ; j < i ; j ++ )
56+ {
57+ var prevKeyCfg = configuration . Keys [ j ] ;
58+ var prevKeyValue = keyValues [ j ] ;
59+ var prevEqual = Expression . Equal ( prevKeyCfg . KeySelector . Body , prevKeyValue ) ;
60+ comparison = Expression . AndAlso ( prevEqual , comparison ) ;
61+ }
62+
63+ predicate = predicate == null ? comparison : Expression . OrElse ( predicate , comparison ) ;
64+ }
65+
66+ var lambda = Expression . Lambda < Func < TEntity , bool > > ( predicate ! , configuration . Input ) ;
67+ return lambda ;
68+ }
69+
70+ private static IQueryable < TEntity > ApplyOrdering < TEntity > ( IQueryable < TEntity > queryable ,
71+ PagingConfiguration < TEntity > configuration )
72+ {
73+ for ( int i = 0 ; i < configuration . Keys . Count ; i ++ )
74+ {
75+ var keyCfg = configuration . Keys [ i ] ;
76+ queryable = ApplyOrdering ( queryable , keyCfg . KeySelector , keyCfg . IsDescending , isFirst : i == 0 ) ;
77+ }
78+
79+ return queryable ;
80+ }
81+
82+ private static IOrderedQueryable < TEntity > ApplyOrdering < TEntity > (
83+ IQueryable < TEntity > source ,
84+ LambdaExpression keySelector ,
85+ bool isDescending ,
86+ bool isFirst )
87+ {
88+ var methodName = ( isFirst , isDescending ) switch
89+ {
90+ ( true , false ) => nameof ( System . Linq . Queryable . OrderBy ) ,
91+ ( true , true ) => nameof ( System . Linq . Queryable . OrderByDescending ) ,
92+ ( false , false ) => nameof ( System . Linq . Queryable . ThenBy ) ,
93+ ( false , true ) => nameof ( System . Linq . Queryable . ThenByDescending ) ,
94+ } ;
95+
96+ var method = typeof ( System . Linq . Queryable )
97+ . GetMethods ( )
98+ . First ( m =>
99+ m . Name == methodName &&
100+ m . GetParameters ( ) . Length == 2 )
101+ . MakeGenericMethod ( typeof ( TEntity ) , keySelector . Body . Type ) ;
102+
103+ return ( IOrderedQueryable < TEntity > ) method . Invoke ( null , [ source , keySelector ] ) ! ;
104+ }
105+ }
106+ }
0 commit comments