private static CursorPageSlice <TEntity> PostProcessResultsIntoCursorPageSlice <TEntity>( List <CursorResult <TEntity> > results, SqlQuerySliceInfo sqlQuerySliceInfo, int?totalCount ) where TEntity : class { bool hasPreviousPage = false; bool hasNextPage = false; if (sqlQuerySliceInfo.IsPreviousPagePossible) { var firstCursor = results.FirstOrDefault(); hasPreviousPage = firstCursor?.CursorIndex > 1; //Cursor Index is 1 Based; 0 would be the Cursor before the First } if (sqlQuerySliceInfo.IsNextPagePossible) { //GENERALLY This should Always Be True as we always increment the EndIndex if there is the Possibility that there might // be a NEXT Page, and the ExpectedCount is always a value that should satisfy the processing // (e.g. ExpectedCount might be int.MaxValue which would ensure our Take is always successful to get ALL Results). if (sqlQuerySliceInfo.IsEndIndexOverFetchedForNextPageCheck && sqlQuerySliceInfo.ExpectedCount < int.MaxValue) { hasNextPage = results.Count > sqlQuerySliceInfo.ExpectedCount; if (hasNextPage) { results.RemoveAt(results.Count - 1); } } } //Return a CursorPagedResult decorator for the results along with the Total Count! var cursorPage = new CursorPageSlice <TEntity>(results, totalCount, hasPreviousPage, hasNextPage); return(cursorPage); }
/// <summary> /// Helper method for converting Cursor Slice to OffsetPageResults for easier processing by calling code. /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="cursorPageSlice"></param> /// <returns></returns> public static OffsetPageResults <TEntity> ToOffsetPageResults <TEntity>(this CursorPageSlice <TEntity> cursorPageSlice) //ALL entities retrieved and Mapped for Cursor Pagination must support IHaveCursor interface. where TEntity : class { return(new OffsetPageResults <TEntity>( cursorPageSlice.Results, cursorPageSlice.HasNextPage, cursorPageSlice.HasPreviousPage, cursorPageSlice.TotalCount )); }
public static ICursorPageSlice <T> SliceAsCursorPage <T>(this IEnumerable <T> items, string?after, int?first, string?before, int?last) where T : class { //Do nothing if there are no results... if (!items.Any()) { return(new CursorPageSlice <T>(Enumerable.Empty <ICursorResult <T> >(), 0, false, false)); } var afterIndex = after != null ? IndexEdge <string> .DeserializeCursor(after) : 0; var beforeIndex = before != null ? IndexEdge <string> .DeserializeCursor(before) : 0; //FIRST log the index of all items in the list BEFORE slicing, as these indexes are // the Cursor Indexes for paging up/down the entire list, & ICursorResult is the Decorator // around the Entity Models. //NOTE: We MUST materialize this after applying index values to prevent ongoing increments... int index = 0; IEnumerable <ICursorResult <T> > slice = items .Select(c => new CursorResult <T>(c, ++index)) .ToList(); int totalCount = slice.Count(); //If After specified, remove all before After (or skip past After) if (afterIndex > 0 && slice.Last().CursorIndex > afterIndex) { slice = slice.Skip(afterIndex); } //If Before is specified, remove all after Before (Skip Until Before is reached) if (beforeIndex > 0 && slice.Last().CursorIndex > beforeIndex) { slice = slice.SkipWhile(c => c.CursorIndex < beforeIndex); } //If First is specified, then take the first/top rows from the current Slice! if (first.HasValue && first > 0 && slice.Count() > first) { slice = slice.Take(first.Value); } //If First is specified, then take the first/top rows from the current Slice! if (last.HasValue && last > 0 && slice.Count() > last) { slice = slice.TakeLast(last.Value); } //Wrap all results into a PagedCursor Slice result wit Total Count... //NOTE: to ensure our pagination is complete, we materialize the Results! var results = slice.ToList(); var firstCursor = results.FirstOrDefault(); var lastCursor = results.LastOrDefault(); var cursorPageSlice = new CursorPageSlice <T>( results, totalCount, hasPreviousPage: firstCursor?.CursorIndex > 1, hasNextPage: lastCursor?.CursorIndex < totalCount ); return(cursorPageSlice); }