/// <summary> /// Applies the cursor pagination algorithm to the <paramref name="query"/>. /// </summary> /// <param name="query"> /// The query on which the the cursor pagination algorithm shall be applied to. /// </param> /// <param name="context"> /// The field resolver context. /// </param> /// <param name="defaultPageSize"> /// The default page size if no boundaries are set. /// </param> /// <param name="totalCount"> /// The total count if already known. /// </param> /// <param name="cancellationToken"> /// The cancellation token. /// </param> /// <typeparam name="TEntity"> /// The entity type. /// </typeparam> /// <returns> /// Returns a connection instance that represents the result of applying the /// cursor paging algorithm to the provided <paramref name="query"/>. /// </returns> public static ValueTask <Connection <TEntity> > ApplyCursorPaginationAsync <TEntity>( this IQueryable <TEntity> query, IResolverContext context, int?defaultPageSize = null, int?totalCount = null, CancellationToken cancellationToken = default) { if (query is null) { throw new ArgumentNullException(nameof(query)); } if (context is null) { throw new ArgumentNullException(nameof(context)); } var first = context.ArgumentValue <int?>(CursorPagingArgumentNames.First); var last = context.ArgumentValue <int?>(CursorPagingArgumentNames.Last); if (first is null && last is null) { first = defaultPageSize; } var arguments = new CursorPagingArguments( first, last, context.ArgumentValue <string?>(CursorPagingArgumentNames.After), context.ArgumentValue <string?>(CursorPagingArgumentNames.Before)); return(QueryableCursorPagination <TEntity> .Instance.ApplyPaginationAsync( query, arguments, totalCount, cancellationToken)); }
async ValueTask <IPage> IPagingHandler.SliceAsync( IResolverContext context, object source) { var first = context.ArgumentValue <int?>(CursorPagingArgumentNames.First); var last = AllowBackwardPagination ? context.ArgumentValue <int?>(CursorPagingArgumentNames.Last) : null; if (first is null && last is null) { first = DefaultPageSize; } var arguments = new CursorPagingArguments( first, last, context.ArgumentValue <string?>(CursorPagingArgumentNames.After), AllowBackwardPagination ? context.ArgumentValue <string?>(CursorPagingArgumentNames.Before) : null); return(await SliceAsync(context, source, arguments).ConfigureAwait(false)); }
/// <summary> /// Applies the pagination algorithm to the provided query. /// </summary> /// <param name="query">The query builder.</param> /// <param name="arguments">The paging arguments.</param> /// <param name="totalCount">Specify the total amount of elements</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// Returns the connection. /// </returns> public async ValueTask <Connection <TEntity> > ApplyPaginationAsync( TQuery query, CursorPagingArguments arguments, int?totalCount, CancellationToken cancellationToken) { if (query is null) { throw new ArgumentNullException(nameof(query)); } var maxElementCount = int.MaxValue; Func <CancellationToken, ValueTask <int> > executeCount = totalCount is null ? ct => CountAsync(query, ct) : _ => new ValueTask <int>(totalCount.Value); // We only need the maximal element count if no `before` counter is set and no `first` // argument is provided. if (arguments.Before is null && arguments.First is null) { var count = await executeCount(cancellationToken); maxElementCount = count; // in case we already know the total count, we override the countAsync parameter // so that we do not have to fetch the count twice executeCount = _ => new ValueTask <int>(count); } CursorPagingRange range = SliceRange(arguments, maxElementCount); var skip = range.Start; var take = range.Count(); // we fetch one element more than we requested if (take != maxElementCount) { take++; } TQuery slicedSource = query; if (skip != 0) { slicedSource = ApplySkip(query, skip); } if (take != maxElementCount) { slicedSource = ApplyTake(slicedSource, take); } IReadOnlyList <Edge <TEntity> > selectedEdges = await ExecuteAsync(slicedSource, skip, cancellationToken); var moreItemsReturnedThanRequested = selectedEdges.Count > range.Count(); var isSequenceFromStart = range.Start == 0; selectedEdges = new SkipLastCollection <Edge <TEntity> >( selectedEdges, moreItemsReturnedThanRequested); ConnectionPageInfo pageInfo = CreatePageInfo(isSequenceFromStart, moreItemsReturnedThanRequested, selectedEdges); return(new Connection <TEntity>(selectedEdges, pageInfo, executeCount)); }
/// <summary> /// Applies the pagination algorithm to the provided query. /// </summary> /// <param name="query">The query builder.</param> /// <param name="arguments">The paging arguments.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// Returns the connection. /// </returns> public ValueTask <Connection <TEntity> > ApplyPaginationAsync( TQuery query, CursorPagingArguments arguments, CancellationToken cancellationToken) => ApplyPaginationAsync(query, arguments, null, cancellationToken);
protected override ValueTask <Connection> SliceAsync(IResolverContext context, object source, CursorPagingArguments arguments) { //If Appropriate we handle the values here to ensure that no post-processing is done other than // correctly mapping the results into a GraphQL Connection as Edges with Cursors... if (source is IPreProcessedCursorSlice <TEntity> pagedResults) { bool includeTotalCountEnabled = this.PagingOptions.IncludeTotalCount ?? PagingDefaults.IncludeTotalCount; var graphQLParamsContext = new GraphQLParamsContext(context); //Optimized to only require TotalCount value if the query actually requested it! if (includeTotalCountEnabled && graphQLParamsContext.IsTotalCountRequested && pagedResults.TotalCount == null) { throw new InvalidOperationException($"Total Count is requested in the query, but was not provided with the results [{this.GetType().GetTypeName()}] from the resolvers pre-processing logic; TotalCount is null."); } int?totalCount = pagedResults.TotalCount; //Ensure we are null safe and return a valid empty list by default. IReadOnlyList <IndexEdge <TEntity> > selectedEdges = pagedResults?.ToEdgeResults().ToList() ?? new List <IndexEdge <TEntity> >();; IndexEdge <TEntity>?firstEdge = selectedEdges.FirstOrDefault(); IndexEdge <TEntity>?lastEdge = selectedEdges.LastOrDefault(); var connectionPageInfo = new ConnectionPageInfo( hasNextPage: pagedResults?.HasNextPage ?? false, hasPreviousPage: pagedResults?.HasPreviousPage ?? false, startCursor: firstEdge?.Cursor, endCursor: lastEdge?.Cursor, totalCount: totalCount ?? 0 ); var graphQLConnection = new Connection <TEntity>( selectedEdges, connectionPageInfo, ct => new ValueTask <int>(connectionPageInfo.TotalCount ?? 0) ); return(new ValueTask <Connection>(graphQLConnection)); } throw new GraphQLException($"[{nameof(PreProcessedCursorPagingHandler<TEntity>)}] cannot handle the specified data source of type [{source.GetType().Name}]."); }
public static ICursorPageSlice <T> SliceAsCursorPage <T>(this IEnumerable <T> items, CursorPagingArguments graphQLPagingArgs) where T : class { return(items.SliceAsCursorPage( after: graphQLPagingArgs.After, first: graphQLPagingArgs.First, before: graphQLPagingArgs.Before, last: graphQLPagingArgs.Last )); }
protected abstract ValueTask <Connection> SliceAsync( IResolverContext context, object source, CursorPagingArguments arguments);