I can empathize with your question; I was banging my head against the wall on this for a while.
EDIT: This may fit your use case also: https://learn.microsoft.com/en-us/dotnet/azure/sdk/pagination#take-the-first-n-elements if your filter determines the lex ordering and a simple take count is used.
Here is a snippet from our samples.
Nope, this is NOT helpful, if anything it is determinantal in its current form. I cannot help but nit on this; I've spent days going over those "samples", reviewing the underlying source code, the tests themselves, and god knows how many times I have re-read MIGRATION GUIDE. While these samples would be somewhat high-level example of usage in a Console App perhaps as a Service Worker, however it doesn't give ANY guidance in where the lion share of APIs would be using this, i.e. repository pattern in a microservice. Don't even get me started on the TestEnvironment base class
Even reviewing the SDK guideline for Azure.* packages offer little of the HOW, just a generous helping of '...' Paging. That leaves devs back to square one and in my own experience I ended up with now more questions than answers.
Another unreal oversight is cancellation tokens, so I will address that elephant while I am here.
Now let's focus on answering the question with a functional example of what could be used in an application. I currently use this in my own production application:
// Background context: Class is a data provider class that gets videos from a video table of an ephemeral nature.
// Periodically the InitializeAsync is called to create the table client and instantiate a new table since the entire table is deleted after running the associated batch processing.
// This is specific to my use case and you can easily revert to
// having the TableClient specified via ctor injection instead with a DI Singleton registration.
public class VideoTable : IVideoTable
{
private readonly TableServiceClient service;
private readonly IOptionsMonitor<BotConfiguration> settings;
private readonly ILogger<VideoTable> logger;
private TableClient client;
// For mocking instantances
protected VideoTable() { }
public VideoTable(TableServiceClient service, IOptionsMonitor<BotConfiguration> settings, ILogger<VideoTable> logger)
{
logger.LogDebug("{Object} constructed", GetType().Name);
this.service = service;
this.settings = settings;
this.logger = logger;
}
public async Task InitalizeAsync(CancellationToken ct)
{
_ = await service.CreateTableIfNotExistsAsync(settings.CurrentValue.Storage.Table.Videos.Name, ct).Go(); // Note: to explain where this comes from: I got tired of writing '.ConfigureAwait(false);' so I made this extension method, simply remove them and you'll obtain the same functionality
client = service.GetTableClient(settings.CurrentValue.Storage.Table.Videos.Name);
}
public async IAsyncEnumerable<Page<VideoEntity>> GetVideosPaginatedAsync(int pageNumber, int perPageCount, [EnumeratorCancellation] CancellationToken ct)
{
var accumulator = 0;
var total = pageNumber * perPageCount; // say we want page 3 with 10 items per page we'll go to 30 here
var select = client.QueryAsync<VideoEntity>(maxPerPage: perPageCount, cancellationToken: ct);
do
{
await foreach (var page in select.AsPages().WithCancellation(ct)) //note under the hood the ConfigureAwait is already specified, no need to specifiy it again. Honorable mention you can specify a different cancellation source token and combine them if you don't want a single use token for cancellations
{
if (page.ContinuationToken is null || page.Values?.Count == 0)
{
yield break;
}
accumulator += page.Values.Count;
yield return page;
}
}
while (total > accumulator);
}
}