/// <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));
        }
Exemplo n.º 2
0
    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);
Exemplo n.º 5
0
        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
                ));
 }
Exemplo n.º 7
0
 protected abstract ValueTask <Connection> SliceAsync(
     IResolverContext context,
     object source,
     CursorPagingArguments arguments);