diff --git a/src/Microsoft.Azure.CosmosRepository/GlobalUsings.cs b/src/Microsoft.Azure.CosmosRepository/GlobalUsings.cs index d7d94ca29..320e57c91 100644 --- a/src/Microsoft.Azure.CosmosRepository/GlobalUsings.cs +++ b/src/Microsoft.Azure.CosmosRepository/GlobalUsings.cs @@ -34,4 +34,4 @@ global using Microsoft.Extensions.Options; global using Newtonsoft.Json; global using Newtonsoft.Json.Linq; -global using Newtonsoft.Json.Serialization; +global using Newtonsoft.Json.Serialization; \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs index 4bd2a486f..28abdaa7a 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs @@ -3,29 +3,47 @@ // ReSharper disable once CheckNamespace + namespace Microsoft.Azure.CosmosRepository; internal sealed partial class DefaultRepository { /// - public async ValueTask> PageAsync( + public ValueTask> PageAsync( Expression>? predicate = null, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, - CancellationToken cancellationToken = default) - { + CancellationToken cancellationToken = default) => + PageAsync( + new QueryRequestOptions { MaxItemCount = pageSize }, + predicate, + pageSize, + continuationToken, + returnTotal, + cancellationToken); + + /// + public async ValueTask> PageAsync( + QueryRequestOptions requestOptions, + Expression>? predicate = null, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, + CancellationToken cancellationToken = default) + { Container container = await containerProvider.GetContainerAsync() - .ConfigureAwait(false); - - QueryRequestOptions options = new() - { - MaxItemCount = pageSize - }; + .ConfigureAwait(false); + + // make sure that if the user hasn't said the value already we take it from the pageSize parameter + if (requestOptions.MaxItemCount is null) + { + requestOptions.MaxItemCount = pageSize; + } IQueryable query = container .GetItemLinqQueryable( - requestOptions: options, + requestOptions: requestOptions, continuationToken: continuationToken, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider.Build( @@ -51,23 +69,40 @@ public async ValueTask> PageAsync( pageSize, items.AsReadOnly(), charge + countResponse?.RequestCharge ?? 0, - resultingContinuationToken); + resultingContinuationToken); } /// - public async ValueTask> PageAsync( + public ValueTask> PageAsync( Expression>? predicate = null, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, - CancellationToken cancellationToken = default) - { + CancellationToken cancellationToken = default) => + PageAsync( + requestOptions: new QueryRequestOptions { MaxItemCount = pageSize }, + predicate: predicate, + pageNumber: pageNumber, + pageSize: pageSize, + returnTotal: returnTotal, + cancellationToken: cancellationToken); + + /// + public async ValueTask> PageAsync( + QueryRequestOptions requestOptions, + Expression>? predicate = null, + int pageNumber = 1, + int pageSize = 25, + bool returnTotal = false, + CancellationToken cancellationToken = default) + { Container container = await containerProvider.GetContainerAsync() .ConfigureAwait(false); IQueryable query = container .GetItemLinqQueryable( - linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions, + requestOptions: requestOptions) .Where(repositoryExpressionProvider .Build(predicate ?? repositoryExpressionProvider.Default())); @@ -94,6 +129,6 @@ public async ValueTask> PageAsync( pageSize, items.AsReadOnly(), charge + countResponse?.RequestCharge ?? 0, - resultingContinuationToken /* This was missing, is this correct? */); + resultingContinuationToken /* This was missing, is this correct? */); } } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs index 47e5f6857..9923af0ff 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs @@ -8,25 +8,30 @@ namespace Microsoft.Azure.CosmosRepository; internal sealed partial class DefaultRepository { /// - public async ValueTask QueryAsync( + public ValueTask QueryAsync( ISpecification specification, CancellationToken cancellationToken = default) - where TResult : IQueryResult - { + where TResult : IQueryResult => + QueryAsync(specification, new QueryRequestOptions(), cancellationToken); + + /// + public async ValueTask QueryAsync( + ISpecification specification, + QueryRequestOptions requestOptions, + CancellationToken cancellationToken = default) where TResult : IQueryResult + { Container container = await containerProvider.GetContainerAsync() .ConfigureAwait(false); - QueryRequestOptions options = new(); - if (specification.UseContinuationToken) { - options.MaxItemCount = specification.PageSize; + requestOptions.MaxItemCount = specification.PageSize; } IQueryable query = container .GetItemLinqQueryable( - requestOptions: options, - continuationToken: specification.ContinuationToken, + requestOptions: requestOptions, + continuationToken: specification.ContinuationToken, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider.Default()); @@ -45,6 +50,6 @@ await GetAllItemsAsync(query, specification.PageSize, cancellationToken) return specification.PostProcessingAction( items.AsReadOnly(), count.Resource, charge + count.RequestCharge, - continuationToken); + continuationToken); } } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index ca09bc816..a6e20b964 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. // ReSharper disable once CheckNamespace + + namespace Microsoft.Azure.CosmosRepository; /// diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs index 222596d26..3406e7972 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs @@ -184,6 +184,26 @@ ValueTask> PageAsync( bool returnTotal = false, CancellationToken cancellationToken = default); + /// + /// Offers a load more paging implementation for infinite scroll scenarios. + /// Allows for efficient paging making use of cosmos DBs continuation tokens, making this implementation cost effective. + /// + /// An to specify query options for Cosmos SDK + /// A filter criteria for the paging operation, if null it will get all s + /// The size of the page to return from cosmos db. + /// The token returned from a previous query, if null starts at the beginning of the data + /// Specifies whether or not to return the total number of items that matched the query. This defaults to false as it can be a very expensive operation. + /// The cancellation token to use when making asynchronous operations. + /// An of s + /// This method makes use of cosmos dbs continuation tokens for efficient, cost effective paging utilising low RUs + ValueTask> PageAsync( + QueryRequestOptions requestOptions, + Expression>? predicate = null, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, + CancellationToken cancellationToken = default); + /// /// Get items based on a specification. /// The specification is used to define which filters are used, the order of the search results and how they are paged. @@ -202,10 +222,49 @@ ValueTask QueryAsync( CancellationToken cancellationToken = default) where TResult : IQueryResult; + /// + /// Get items based on a specification. + /// The specification is used to define which filters are used, the order of the search results and how they are paged. + /// Depending on how results are paged derive specification implementations from different classes: + /// For non paged results derive + /// For continuation token derive + /// For page number results derive + /// + /// Decides which paging information is retrieved. Use + /// A specification used to filtering, ordering and paging. A + /// An to specify query options for Cosmos SDK + /// The cancellation token to use when making asynchronous operations. + /// The selected implementation that implements of + /// This method makes use of cosmos dbs continuation tokens for efficient, cost effective paging utilising low RUs + ValueTask QueryAsync( + ISpecification specification, + QueryRequestOptions requestOptions, + CancellationToken cancellationToken = default) + where TResult : IQueryResult; + + /// + /// Offers a load more paging implementation for infinite scroll scenarios. + /// Allows for efficient paging making use of cosmos DBs continuation tokens, making this implementation cost effective. + /// + /// A filter criteria for the paging operation, if null it will get all s + /// The page number to return from cosmos db. + /// The size of the page to return from cosmos db. + /// Specifies whether or not to return the total number of items that matched the query. This defaults to false as it can be a very expensive operation. + /// The cancellation token to use when making asynchronous operations. + /// An of s + /// This method makes use of Cosmos DB's continuation tokens for efficient, cost effective paging utilizing low RUs + ValueTask> PageAsync( + Expression>? predicate = null, + int pageNumber = 1, + int pageSize = 25, + bool returnTotal = false, + CancellationToken cancellationToken = default); + /// /// Offers a load more paging implementation for infinite scroll scenarios. /// Allows for efficient paging making use of cosmos DBs continuation tokens, making this implementation cost effective. /// + /// An to specify query options for Cosmos SDK /// A filter criteria for the paging operation, if null it will get all s /// The page number to return from cosmos db. /// The size of the page to return from cosmos db. @@ -214,6 +273,7 @@ ValueTask QueryAsync( /// An of s /// This method makes use of Cosmos DB's continuation tokens for efficient, cost effective paging utilizing low RUs ValueTask> PageAsync( + QueryRequestOptions requestOptions, Expression>? predicate = null, int pageNumber = 1, int pageSize = 25, @@ -262,5 +322,49 @@ async IAsyncEnumerable PageAsync( } } } + + /// + /// Wraps the existing paging support to return an + /// where T is . + /// + /// A filter criteria for the paging operation, if null it will get all s + /// The limit of how many items to yield. Defaults to 1,000. + /// The optional used to + /// An where T is . + /// This method makes use of Cosmos DB's continuation tokens for efficient, cost effective paging utilizing low RUs + async IAsyncEnumerable PageAsync( + QueryRequestOptions requestOptions, + Expression>? predicate = null, + int limit = 1_000, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var collected = 0; + var currentPage = 0; + var hasMoreResults = true; + + while (hasMoreResults && collected < limit + && cancellationToken.IsCancellationRequested is false) + { + IPageQueryResult page = await PageAsync( + requestOptions, + predicate, + pageNumber: ++currentPage, + 25, + returnTotal: false, + cancellationToken); + + hasMoreResults = page.HasNextPage.GetValueOrDefault(); + + foreach (TItem item in page.Items) + { + if (collected < limit) + { + yield return item; + } + + collected++; + } + } + } #endif } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs index 8d9d2366f..24f4cff5e 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs @@ -3,6 +3,7 @@ // ReSharper disable once CheckNamespace + namespace Microsoft.Azure.CosmosRepository; /// @@ -22,4 +23,19 @@ public InMemoryRepository(ISpecificationEvaluator specificationEvaluator) => private void NotFound() => throw new CosmosException(string.Empty, HttpStatusCode.NotFound, 0, string.Empty, 0); private void Conflict() => throw new CosmosException(string.Empty, HttpStatusCode.Conflict, 0, string.Empty, 0); + + public ValueTask> PageAsync(QueryRequestOptions requestOptions, Expression>? predicate = null, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask QueryAsync(ISpecification specification, QueryRequestOptions requestOptions, CancellationToken cancellationToken = default) where TResult : IQueryResult + { + throw new NotImplementedException(); + } + + public ValueTask> PageAsync(QueryRequestOptions requestOptions, Expression>? predicate = null, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } } \ No newline at end of file