private static CursorPagingRange SliceRange( CursorPagingArguments arguments, int maxElementCount) { // [SPEC] if after is set then remove all elements of edges before and including // afterEdge. // // The cursor is increased by one so that the index points to the element after var startIndex = arguments.After is { } a ? IndexEdge <TEntity> .DeserializeCursor(a) + 1 : 0; // [SPEC] if before is set then remove all elements of edges before and including // beforeEdge. var before = arguments.Before is { } b ? IndexEdge <TEntity> .DeserializeCursor(b) : maxElementCount; // if after is negative we have know how much of the offset was in the negative range. // The amount of positions that are in the negative range, have to be subtracted from // the take or we will fetch too many items. var startOffsetCorrection = 0; if (startIndex < 0) { startOffsetCorrection = Math.Abs(startIndex); startIndex = 0; } CursorPagingRange range = new(startIndex, before); //[SPEC] If first is less than 0 throw an error ValidateFirst(arguments, out var first); if (first is not null) { first -= startOffsetCorrection; if (first < 0) { first = 0; } } //[SPEC] Slice edges to be of length first by removing edges from the end of edges. range.Take(first); //[SPEC] if last is less than 0 throw an error ValidateLast(arguments, out var last); //[SPEC] Slice edges to be of length last by removing edges from the start of edges. range.TakeLast(last); return(range); }
private async ValueTask <Connection> ResolveAsync( IQueryable <TEntity> source, CursorPagingArguments arguments = default, CancellationToken cancellationToken = default) { var count = await Task.Run(source.Count, cancellationToken) .ConfigureAwait(false); int?after = arguments.After is { } a ? (int?)IndexEdge <TEntity> .DeserializeCursor(a) : null; int?before = arguments.Before is { } b ? (int?)IndexEdge <TEntity> .DeserializeCursor(b) : null; IReadOnlyList <IndexEdge <TEntity> > selectedEdges = await GetSelectedEdgesAsync( source, arguments.First, arguments.Last, after, before, cancellationToken) .ConfigureAwait(false); IndexEdge <TEntity>?firstEdge = selectedEdges.Count == 0 ? null : selectedEdges[0]; IndexEdge <TEntity>?lastEdge = selectedEdges.Count == 0 ? null : selectedEdges[selectedEdges.Count - 1]; var pageInfo = new ConnectionPageInfo( lastEdge?.Index < count - 1, firstEdge?.Index > 0, firstEdge?.Cursor, lastEdge?.Cursor, count); return(new Connection <TEntity>( selectedEdges, pageInfo, ct => new ValueTask <int>(pageInfo.TotalCount ?? 0))); }
protected virtual async ValueTask <IReadOnlyList <IndexEdge <TEntity> > > ExecuteQueryableAsync( IQueryable <TEntity> queryable, int offset, CancellationToken cancellationToken) { var list = new List <IndexEdge <TEntity> >(); if (queryable is IAsyncEnumerable <TEntity> enumerable) { var index = offset; await foreach (TEntity item in enumerable.WithCancellation(cancellationToken) .ConfigureAwait(false)) { list.Add(IndexEdge <TEntity> .Create(item, index++)); } } else { await Task.Run(() => { var index = offset; foreach (TEntity item in queryable) { if (cancellationToken.IsCancellationRequested) { break; } list.Add(IndexEdge <TEntity> .Create(item, index++)); } }, cancellationToken) .ConfigureAwait(false); } return(list); }