@@ -86,36 +86,29 @@ public static PagedResult<TData> ApplyQueryPagedResult<TData, TQueryOptions>(thi
8686 query = query . ApplySort ( queryOption ) . ApplyPaging ( queryOption ) ;
8787 return new PagedResult < TData > ( query , page , totalPages , count ) ;
8888 }
89-
9089 /// <summary>
91- /// Applies query conditions and cursor-based pagination.
90+ /// Applies query conditions and cursor-based pagination with support for both IQueryable and IAsyncEnumerable sources .
9291 /// </summary>
9392 /// <remarks>
9493 /// Supports sorting by any field using composite cursors.
9594 /// The page token encodes all sort field values plus the cursor key for accurate pagination.
95+ /// Works with both synchronous (IQueryable) and asynchronous (IAsyncEnumerable) data sources.
9696 /// </remarks>
9797 /// <typeparam name="TData">The type of the entity being queried.</typeparam>
9898 /// <typeparam name="TQueryOptions">The type of the query options.</typeparam>
99- /// <param name="query ">The query object .</param>
99+ /// <param name="source ">The queryable or async enumerable source .</param>
100100 /// <param name="queryProcessor">The query processor.</param>
101101 /// <param name="queryOption">The query options.</param>
102+ /// <param name="cancellationToken">Cancellation token for async operations.</param>
102103 /// <returns>The cursor-based paginated result.</returns>
103- public static CursorPagedResult < TData > ApplyQueryCursorPagedResult < TData , TQueryOptions > (
104- this IQueryable < TData > query ,
105- IQueryProcessor queryProcessor ,
106- TQueryOptions queryOption )
104+ public static async Task < CursorPagedResult < TData > > ApplyQueryCursorPagedResultAsync < TData , TQueryOptions > (
105+ this object source ,
106+ IQueryProcessor queryProcessor ,
107+ TQueryOptions queryOption ,
108+ CancellationToken cancellationToken = default )
107109 where TQueryOptions : IQueryCursorOptions
108110 where TData : class
109111 {
110- var filterExpression = queryProcessor . BuildFilterExpression < TData , TQueryOptions > ( queryOption ) ;
111- var selectorExpression = queryProcessor . BuildSelectorExpression < TData , TQueryOptions > ( queryOption ) ;
112-
113- if ( filterExpression != null )
114- query = query . Where ( filterExpression ) ;
115-
116- if ( selectorExpression != null )
117- query = query . Select ( selectorExpression ) ;
118-
119112 var cursorKeySelector = queryProcessor . GetCursorKeySelector < TQueryOptions , TData > ( ) ;
120113 if ( cursorKeySelector == null )
121114 throw new InvalidOperationException ( $ "Cursor key selector not configured for { typeof ( TData ) . Name } . Use HasCursorKey() in your configuration.") ;
@@ -129,89 +122,106 @@ public static CursorPagedResult<TData> ApplyQueryCursorPagedResult<TData, TQuery
129122 sortFields . Add ( new SortField { PropertyName = cursorPropertyName , IsDescending = false } ) ;
130123 }
131124
132- query = ApplySort ( query , sortFields ) ;
125+ var pageSize = queryOption . PageSize ?? 10 ;
126+ IQueryable < TData > resultQuery ;
127+ string ? nextPageToken = null ;
128+ int count ;
133129
134- if ( ! string . IsNullOrWhiteSpace ( queryOption . PageToken ) )
130+ if ( source is IQueryable < TData > queryable )
135131 {
136- query = ApplyCompositeCursorFilter ( query , sortFields , queryOption . PageToken ) ;
137- }
132+ var filterExpression = queryProcessor . BuildFilterExpression < TData , TQueryOptions > ( queryOption ) ;
133+ var selectorExpression = queryProcessor . BuildSelectorExpression < TData , TQueryOptions > ( queryOption ) ;
134+
135+ if ( filterExpression != null )
136+ queryable = queryable . Where ( filterExpression ) ;
137+
138+ if ( selectorExpression != null )
139+ queryable = queryable . Select ( selectorExpression ) ;
138140
139- var pageSize = queryOption . PageSize ?? 10 ;
140-
141- var buffer = new List < TData > ( pageSize ) ;
142- string ? nextPageToken = null ;
141+ queryable = ApplySort ( queryable , sortFields ) ;
142+
143+ if ( ! string . IsNullOrWhiteSpace ( queryOption . PageToken ) )
144+ {
145+ queryable = ApplyCompositeCursorFilter ( queryable , sortFields , queryOption . PageToken ) ;
146+ }
143147
144- using ( var enumerator = query . Take ( pageSize + 1 ) . GetEnumerator ( ) )
148+ resultQuery = queryable . Take ( pageSize ) ;
149+
150+ var buffer = new List < TData > ( pageSize ) ;
151+ using ( var enumerator = queryable . Take ( pageSize + 1 ) . GetEnumerator ( ) )
152+ {
153+ while ( buffer . Count < pageSize && enumerator . MoveNext ( ) )
154+ {
155+ buffer . Add ( enumerator . Current ) ;
156+ }
157+
158+ if ( enumerator . MoveNext ( ) )
159+ {
160+ nextPageToken = CreateCompositeCursorToken ( buffer [ ^ 1 ] , sortFields ) ;
161+ }
162+ }
163+
164+ count = buffer . Count ;
165+ }
166+ else if ( source is IAsyncEnumerable < TData > asyncEnumerable )
145167 {
146- while ( buffer . Count < pageSize && enumerator . MoveNext ( ) )
168+ if ( ! string . IsNullOrWhiteSpace ( queryOption . PageToken ) )
147169 {
148- buffer . Add ( enumerator . Current ) ;
170+ var cursorValues = PageToken . DecodeComposite ( queryOption . PageToken ) ;
171+ asyncEnumerable = ApplyCompositeCursorFilterAsync ( asyncEnumerable , sortFields , cursorValues ) ;
149172 }
150173
151- if ( enumerator . MoveNext ( ) )
174+ var buffer = new List < TData > ( pageSize ) ;
175+ await foreach ( var item in asyncEnumerable . WithCancellation ( cancellationToken ) )
152176 {
153- nextPageToken = CreateCompositeCursorToken ( buffer [ ^ 1 ] , sortFields ) ;
177+ if ( buffer . Count < pageSize )
178+ {
179+ buffer . Add ( item ) ;
180+ }
181+ else
182+ {
183+ nextPageToken = CreateCompositeCursorToken ( buffer [ ^ 1 ] , sortFields ) ;
184+ break ;
185+ }
154186 }
187+
188+ count = buffer . Count ;
189+ resultQuery = buffer . Take ( pageSize ) . AsQueryable ( ) ;
190+ }
191+ else
192+ {
193+ throw new ArgumentException ( $ "Source must be IQueryable<{ typeof ( TData ) . Name } > or IAsyncEnumerable<{ typeof ( TData ) . Name } >", nameof ( source ) ) ;
155194 }
156195
157- return new CursorPagedResult < TData > ( buffer . AsQueryable ( ) , nextPageToken , buffer . Count ) ;
196+ return new CursorPagedResult < TData > ( resultQuery , nextPageToken , count ) ;
158197 }
159198
160199 /// <summary>
161- /// Applies cursor-based pagination to the async query results .
200+ /// Applies cursor-based pagination to an IQueryable source .
162201 /// </summary>
163- /// <typeparam name="TData">The element type.</typeparam>
164- /// <typeparam name="TQueryOptions">The query options type.</typeparam>
165- /// <param name="query">The async enumerable query.</param>
166- /// <param name="queryProcessor">The query processor.</param>
167- /// <param name="queryOption">The query options.</param>
168- /// <param name="cancellationToken">Cancellation token.</param>
169- /// <returns>The cursor-based paginated result.</returns>
170- public static async Task < CursorPagedResult < TData > > ApplyQueryCursorPagedResultAsync < TData , TQueryOptions > (
171- this IAsyncEnumerable < TData > query ,
202+ public static Task < CursorPagedResult < TData > > ApplyQueryCursorPagedResultAsync < TData , TQueryOptions > (
203+ this IQueryable < TData > query ,
172204 IQueryProcessor queryProcessor ,
173205 TQueryOptions queryOption ,
174206 CancellationToken cancellationToken = default )
175207 where TQueryOptions : IQueryCursorOptions
176208 where TData : class
177209 {
178- var cursorKeySelector = queryProcessor . GetCursorKeySelector < TQueryOptions , TData > ( ) ;
179- if ( cursorKeySelector == null )
180- throw new InvalidOperationException ( $ "Cursor key selector not configured for { typeof ( TData ) . Name } . Use HasCursorKey() in your configuration.") ;
181-
182- var sortFields = ParseSortFields ( queryOption . Sort ) ;
183- var cursorPropertyName = GetPropertyName ( cursorKeySelector ) ;
184-
185- if ( ! string . IsNullOrEmpty ( cursorPropertyName ) &&
186- ! sortFields . Any ( sf => string . Equals ( sf . PropertyName , cursorPropertyName , StringComparison . OrdinalIgnoreCase ) ) )
187- {
188- sortFields . Add ( new SortField { PropertyName = cursorPropertyName , IsDescending = false } ) ;
189- }
190-
191- if ( ! string . IsNullOrWhiteSpace ( queryOption . PageToken ) )
192- {
193- var cursorValues = PageToken . DecodeComposite ( queryOption . PageToken ) ;
194- query = ApplyCompositeCursorFilterAsync ( query , sortFields , cursorValues ) ;
195- }
196-
197- var pageSize = queryOption . PageSize ?? 10 ;
198- var buffer = new List < TData > ( pageSize ) ;
199- string ? nextPageToken = null ;
200-
201- await foreach ( var item in query . WithCancellation ( cancellationToken ) )
202- {
203- if ( buffer . Count < pageSize )
204- {
205- buffer . Add ( item ) ;
206- }
207- else
208- {
209- nextPageToken = CreateCompositeCursorToken ( buffer [ ^ 1 ] , sortFields ) ;
210- break ;
211- }
212- }
210+ return ApplyQueryCursorPagedResultAsync < TData , TQueryOptions > ( ( object ) query , queryProcessor , queryOption , cancellationToken ) ;
211+ }
213212
214- return new CursorPagedResult < TData > ( buffer . AsQueryable ( ) , nextPageToken , buffer . Count ) ;
213+ /// <summary>
214+ /// Applies cursor-based pagination to an IAsyncEnumerable source.
215+ /// </summary>
216+ public static Task < CursorPagedResult < TData > > ApplyQueryCursorPagedResultAsync < TData , TQueryOptions > (
217+ this IAsyncEnumerable < TData > query ,
218+ IQueryProcessor queryProcessor ,
219+ TQueryOptions queryOption ,
220+ CancellationToken cancellationToken = default )
221+ where TQueryOptions : IQueryCursorOptions
222+ where TData : class
223+ {
224+ return ApplyQueryCursorPagedResultAsync < TData , TQueryOptions > ( ( object ) query , queryProcessor , queryOption , cancellationToken ) ;
215225 }
216226
217227 private static async IAsyncEnumerable < TData > ApplyCompositeCursorFilterAsync < TData > (
0 commit comments