diff --git a/QueryBuilder.Tests/PaginationResultTests.cs b/QueryBuilder.Tests/PaginationResultTests.cs new file mode 100644 index 00000000..6856c1eb --- /dev/null +++ b/QueryBuilder.Tests/PaginationResultTests.cs @@ -0,0 +1,75 @@ +using System.Linq; +using SqlKata.Execution; +using Xunit; + +namespace SqlKata.Tests +{ + public class PaginationResultTests + { + [Fact] + public void ReportsPageStateFromCountAndPageSize() + { + var result = new PaginationResult + { + Count = 25, + Page = 2, + PerPage = 10, + }; + + Assert.Equal(3, result.TotalPages); + Assert.False(result.IsFirst); + Assert.False(result.IsLast); + Assert.True(result.HasNext); + Assert.True(result.HasPrevious); + } + + [Fact] + public void BuildsQueriesForAdjacentPages() + { + var nextResult = new PaginationResult + { + Query = new Query("Posts"), + Page = 2, + PerPage = 10, + }; + + var nextQuery = nextResult.NextQuery(); + + Assert.Same(nextResult.Query, nextQuery); + Assert.Equal(20, nextQuery.GetOffset()); + Assert.Equal(10, nextQuery.GetLimit()); + + var previousResult = new PaginationResult + { + Query = new Query("Posts"), + Page = 2, + PerPage = 10, + }; + + var previousQuery = previousResult.PreviousQuery(); + + Assert.Same(previousResult.Query, previousQuery); + Assert.Equal(0, previousQuery.GetOffset()); + Assert.Equal(10, previousQuery.GetLimit()); + } + + [Fact] + public void EachYieldsTheCurrentPageWhenThereIsNoNextPage() + { + var result = new PaginationResult + { + Count = 2, + List = new[] { 1, 2 }, + Page = 1, + PerPage = 10, + }; + + var iterator = result.Each; + var pages = iterator.ToList(); + + Assert.Single(pages); + Assert.Same(result, pages[0]); + Assert.Same(result, iterator.CurrentPage); + } + } +} diff --git a/SqlKata.Execution/PaginationIterator.cs b/SqlKata.Execution/PaginationIterator.cs index 9af8d036..7fc78fb8 100644 --- a/SqlKata.Execution/PaginationIterator.cs +++ b/SqlKata.Execution/PaginationIterator.cs @@ -3,11 +3,27 @@ namespace SqlKata.Execution { + /// + /// Enumerates paginated results, beginning with a supplied first page and + /// loading each subsequent page as enumeration advances. + /// + /// The type of each item in the paginated results. public class PaginationIterator : IEnumerable> { + /// + /// Gets or sets the page from which enumeration starts. + /// public PaginationResult FirstPage { get; set; } + + /// + /// Gets or sets the page most recently returned by the iterator. + /// public PaginationResult CurrentPage { get; set; } + /// + /// Returns an enumerator that yields the first page and loads later pages on demand. + /// + /// An enumerator over the available pages. public IEnumerator> GetEnumerator() { CurrentPage = FirstPage; diff --git a/SqlKata.Execution/PaginationResult.cs b/SqlKata.Execution/PaginationResult.cs index 85277503..d28f0384 100644 --- a/SqlKata.Execution/PaginationResult.cs +++ b/SqlKata.Execution/PaginationResult.cs @@ -6,13 +6,41 @@ namespace SqlKata.Execution { + /// + /// Represents one page of query results together with pagination metadata + /// and helpers for loading adjacent pages. + /// + /// The type of each item in the result set. public class PaginationResult { + /// + /// Gets or sets the query used to produce the paginated results. + /// public Query Query { get; set; } + + /// + /// Gets or sets the total number of matching records across all pages. + /// public long Count { get; set; } + + /// + /// Gets or sets the records in the current page. + /// public IEnumerable List { get; set; } + + /// + /// Gets or sets the one-based current page number. + /// public int Page { get; set; } + + /// + /// Gets or sets the maximum number of records per page. + /// public int PerPage { get; set; } + + /// + /// Gets the total number of pages, rounded up from . + /// public int TotalPages { get @@ -30,6 +58,9 @@ public int TotalPages } } + /// + /// Gets a value indicating whether this is the first page. + /// public bool IsFirst { get @@ -38,6 +69,9 @@ public bool IsFirst } } + /// + /// Gets a value indicating whether this is the last page. + /// public bool IsLast { get @@ -46,6 +80,9 @@ public bool IsLast } } + /// + /// Gets a value indicating whether a page follows the current page. + /// public bool HasNext { get @@ -54,6 +91,9 @@ public bool HasNext } } + /// + /// Gets a value indicating whether a page precedes the current page. + /// public bool HasPrevious { get @@ -62,36 +102,73 @@ public bool HasPrevious } } + /// + /// Applies the next page's limit and offset to . + /// + /// The query configured for the next page. public Query NextQuery() { return this.Query.ForPage(Page + 1, PerPage); } + /// + /// Executes the query for the next page. + /// + /// The transaction to use, or for none. + /// The command timeout in seconds, or to use the configured default. + /// The next page of results. public PaginationResult Next(IDbTransaction transaction = null, int? timeout = null) { return this.Query.Paginate(Page + 1, PerPage, transaction, timeout); } + /// + /// Asynchronously executes the query for the next page. + /// + /// The transaction to use, or for none. + /// The command timeout in seconds, or to use the configured default. + /// The token used to cancel the operation. + /// A task containing the next page of results. public async Task> NextAsync(IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { return await this.Query.PaginateAsync(Page + 1, PerPage, transaction, timeout, cancellationToken); } + /// + /// Applies the previous page's limit and offset to . + /// + /// The query configured for the previous page. public Query PreviousQuery() { return this.Query.ForPage(Page - 1, PerPage); } + /// + /// Executes the query for the previous page. + /// + /// The transaction to use, or for none. + /// The command timeout in seconds, or to use the configured default. + /// The previous page of results. public PaginationResult Previous(IDbTransaction transaction = null, int? timeout = null) { return this.Query.Paginate(Page - 1, PerPage, transaction, timeout); } + /// + /// Asynchronously executes the query for the previous page. + /// + /// The transaction to use, or for none. + /// The command timeout in seconds, or to use the configured default. + /// The token used to cancel the operation. + /// A task containing the previous page of results. public async Task> PreviousAsync(IDbTransaction transaction = null, int? timeout = null, CancellationToken cancellationToken = default) { return await this.Query.PaginateAsync(Page - 1, PerPage, transaction, timeout, cancellationToken); } + /// + /// Gets an iterator that starts with this page and loads each remaining page. + /// public PaginationIterator Each { get