Amazon DynamoDB · RetrieveMultiple
Page through results
Page through a partition with paging cookies, in three scenarios: the first page, the next pages from the cookie, and a page number, which DynamoDB can't jump to.
Set up for this page
The scenarios build on the sample application: SampleContext.cs (the dbContext every sample is given); SampleOutput.cs (Step and Show, which print); SampleContext.Application.cs (dbContext.Service, the application's ARX service); SampleModel.cs (the DynamoDB layout and the schema catalog); SampleContext.Direct.cs (dbContext.Direct); DynamoDirectHelper.cs (the checks behind dbContext.Direct).
dbContext.Direct is the samples' own checking code on the plain driver, not part of ARX: leave it out of your application.
Before scenario 1 the sample runs this, so the service and the rows the scenarios use are there:
var orders = Enumerable.Range(1, 12).Select(i =>
{
var order = new ArxEntity("sales_order");
order["customer_id"] = "C-D421";
order["order_number"] = $"SO-D42{i:00}";
order["status"] = "OPEN";
order["ordered_on"] = "2026-11-08";
return order;
}).ToList();
await service.CreateMultipleAsync(orders);1 The first page
Five orders, and the cookie for the rest.
Already in scope: service (ArxDynamoDbService)
var page = await service.RetrieveMultipleAsync(Query(new ArxPagingInfo { Count = 5 }));
Console.WriteLine($"{string.Join(", ", page.Entities.Select(x => x["order_number"]))}; more: {page.HasNextPage}");
return page;Defined once in this sample.
private static ArxQueryExpression Query(ArxPagingInfo page)
{
var query = new ArxQueryExpression("sales_order");
query.ColumnSet.AddColumns("order_number");
query.Criteria.AddCondition("customer_id", ArxConditionOperator.Equal, "C-D421");
query.PageInfo = page;
return query;
}Check it in the database
A PartiQL statement: run it in NoSQL Workbench, or with aws dynamodb execute-statement --statement, after the code has run.
SELECT "order_number" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D421' AND begins_with("SK", 'ORDER#')Expect: 12 items; the first page holds SO-D4201 to SO-D4205.
2 The next pages
Each page from the last one's cookie, until there is none.
Already in scope: service (ArxDynamoDbService), page (ArxEntityCollection)
while (page.PagingCookie != null)
{
page = await service.RetrieveMultipleAsync(Query(new ArxPagingInfo { Count = 5, PagingCookie = page.PagingCookie }));
Console.WriteLine($"{string.Join(", ", page.Entities.Select(x => x["order_number"]))}; more: {page.HasNextPage}");
}Defined once in this sample.
private static ArxQueryExpression Query(ArxPagingInfo page)
{
var query = new ArxQueryExpression("sales_order");
query.ColumnSet.AddColumns("order_number");
query.Criteria.AddCondition("customer_id", ArxConditionOperator.Equal, "C-D421");
query.PageInfo = page;
return query;
}Check it in the database
A PartiQL statement: run it in NoSQL Workbench, or with aws dynamodb execute-statement --statement, after the code has run.
SELECT "order_number" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D421' AND "SK" > 'ORDER#SO-D4205'Expect: 7 items, SO-D4206 to SO-D4212.
3 A page number
Page 3 straight away: ARX8001. Page with cookies.
Already in scope: service (ArxDynamoDbService)
try
{
await service.RetrieveMultipleAsync(Query(new ArxPagingInfo { Count = 5, PageNumber = 3 }));
}
catch (ArxNotSupportedException exception)
{
Console.WriteLine(exception.Message);
}Defined once in this sample.
private static ArxQueryExpression Query(ArxPagingInfo page)
{
var query = new ArxQueryExpression("sales_order");
query.ColumnSet.AddColumns("order_number");
query.Criteria.AddCondition("customer_id", ArxConditionOperator.Equal, "C-D421");
query.PageInfo = page;
return query;
}Check it in the database
A PartiQL statement: run it in NoSQL Workbench, or with aws dynamodb execute-statement --statement, after the code has run.
SELECT "order_number" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D421' AND begins_with("SK", 'ORDER#')Expect: 12 items (PartiQL pages by its own NextToken, never by number).
Good to know
A page continues from where the last one ended: pass back the PagingCookie the page returned, with the same query. HasNextPage is exact, and the cookie is null on the last page. DynamoDB can't count or skip, so a page number above 1 and a total count are ARX8001.
Run the complete sample
The code above comes from one program, built in a fresh project and run against the database. To run it yourself, here is every file and step.
The program, its files and how to run it
Run it locally
- .NET SDK: .NET SDK 10.0.100 or later 10.0.x (target
net10.0). - Create the project:
Terminal dotnet new console -n ArxSample -f net10.0 cd ArxSample - Install the packages:
Terminal dotnet add package ARX.SDK.DynamoDb --version 1.0.0-rc.1 - Database: a Amazon DynamoDB, or DynamoDB Local for development (tested with DynamoDB Local 2.5.2) database with the sample schema.
- Configuration: set
ARX_SAMPLES_DYNAMODBto its connection string. Nothing is hard-coded. - Source code: add the files of the sample application this sample uses (
SampleContext.cs,SampleOutput.cs,SampleContext.Application.cs,SampleModel.cs,SampleContext.Direct.cs,DynamoDirectHelper.cs), then replaceProgram.csand add the other file below, with the file names shown. - Run it:
Terminal dotnet run - Expected output: below.
The files
Runs the sample. Generated for this page; it reads the connection string from the environment and first remakes the sample database from sample-schema.json.
using ArxSamples.DynamoDb;
using ArxSamples.DynamoDb.Operations.RetrieveMultiple.Basics;
// The connection string comes from the environment, never from the code (see "Configure the connection").
var connectionString = Environment.GetEnvironmentVariable("ARX_SAMPLES_DYNAMODB")
?? throw new InvalidOperationException("Set the ARX_SAMPLES_DYNAMODB environment variable to your Amazon DynamoDB connection string.");
var dbContext = new SampleContext(connectionString);
// Every run starts from the sample data: this deletes and makes again what sample-schema.json lists
// (save the file in the project folder, see Get started).
await dbContext.Direct.ResetAsync();
await new Basic2_PageThroughResults().RunAsync(dbContext);
The sample, as it is in the repository.
using ARX.SDK;
using ARX.SDK.DynamoDb.Services;
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.DynamoDb.SampleOutput;
namespace ArxSamples.DynamoDb.Operations.RetrieveMultiple.Basics;
/// <summary>
/// Page through a partition with paging cookies, in three scenarios: the first page, the next pages from the cookie, and
/// a page number, which DynamoDB can't jump to.
/// </summary>
/// <remarks>
/// A page continues from where the last one ended: pass back the <c>PagingCookie</c> the page returned, with the same
/// query. <c>HasNextPage</c> is exact, and the cookie is null on the last page. DynamoDB can't count or skip, so a page
/// number above 1 and a total count are ARX8001.
/// </remarks>
public sealed class Basic2_PageThroughResults : ISample
{
public string Name => "retrieve-multiple-basics-2-page-through-results";
public string Title => "Page through results";
public async Task RunAsync(SampleContext dbContext)
{
var orders = Enumerable.Range(1, 12).Select(i =>
{
var order = new ArxEntity("sales_order");
order["customer_id"] = "C-D421";
order["order_number"] = $"SO-D42{i:00}";
order["status"] = "OPEN";
order["ordered_on"] = "2026-11-08";
return order;
}).ToList();
await dbContext.Service.CreateMultipleAsync(orders);
var first = await Scenario1_TheFirstPageAsync(dbContext.Service);
await Scenario2_TheNextPagesAsync(dbContext.Service, first);
await Scenario3_APageNumberAsync(dbContext.Service);
}
private static ArxQueryExpression Query(ArxPagingInfo page)
{
var query = new ArxQueryExpression("sales_order");
query.ColumnSet.AddColumns("order_number");
query.Criteria.AddCondition("customer_id", ArxConditionOperator.Equal, "C-D421");
query.PageInfo = page;
return query;
}
/// <summary>1. Five orders, and the cookie for the rest.</summary>
private static async Task<ArxEntityCollection> Scenario1_TheFirstPageAsync(ArxDynamoDbService service)
{
Step("1. The first page");
var page = await service.RetrieveMultipleAsync(Query(new ArxPagingInfo { Count = 5 }));
Show($"{string.Join(", ", page.Entities.Select(x => x["order_number"]))}; more: {page.HasNextPage}");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT "order_number" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D421' AND begins_with("SK", 'ORDER#')
* -- Expect: 12 items; the first page holds SO-D4201 to SO-D4205.
* ===========================================================
*/
return page;
}
/// <summary>2. Each page from the last one's cookie, until there is none.</summary>
private static async Task Scenario2_TheNextPagesAsync(ArxDynamoDbService service, ArxEntityCollection page)
{
Step("2. The next pages");
while (page.PagingCookie != null)
{
page = await service.RetrieveMultipleAsync(Query(new ArxPagingInfo { Count = 5, PagingCookie = page.PagingCookie }));
Show($"{string.Join(", ", page.Entities.Select(x => x["order_number"]))}; more: {page.HasNextPage}");
}
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT "order_number" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D421' AND "SK" > 'ORDER#SO-D4205'
* -- Expect: 7 items, SO-D4206 to SO-D4212.
* ===========================================================
*/
}
/// <summary>3. Page 3 straight away: ARX8001. Page with cookies.</summary>
private static async Task Scenario3_APageNumberAsync(ArxDynamoDbService service)
{
Step("3. A page number");
try
{
await service.RetrieveMultipleAsync(Query(new ArxPagingInfo { Count = 5, PageNumber = 3 }));
}
catch (ArxNotSupportedException exception)
{
ShowError(exception);
}
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT "order_number" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D421' AND begins_with("SK", 'ORDER#')
* -- Expect: 12 items (PartiQL pages by its own NextToken, never by number).
* ===========================================================
*/
}
}
Expected output
What dotnet run printed when the sample was verified, on a freshly created sample schema. Ids differ when the tables already hold rows.
> 1. The first page
SO-D4201, SO-D4202, SO-D4203, SO-D4204, SO-D4205; more: True
> 2. The next pages
SO-D4206, SO-D4207, SO-D4208, SO-D4209, SO-D4210; more: True
SO-D4211, SO-D4212; more: False
> 3. A page number
caught ArxNotSupportedException: ARX8001 NotSupported: DynamoDB can't jump to page 3: it reads on from where the last page ended. Pass the previous page's PagingCookie (PageNumber 1).