Amazon DynamoDB · RetrieveMultiple
A customer and its orders in one request
The single-table design's reason to exist: a customer and its orders in one request, in two scenarios: the whole item collection, and a page of it.
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 customer = new ArxEntity("customer");
customer["customer_id"] = "C-D431";
customer["name"] = "Adventure Works Cycles";
var batch = new List<ArxEntity> { customer };
for (int i = 1; i <= 4; i++)
{
var order = new ArxEntity("sales_order");
order["customer_id"] = "C-D431";
order["order_number"] = $"SO-D43{i}";
order["status"] = "OPEN";
order["ordered_on"] = $"2026-10-2{i}";
order["total"] = 100m * i;
batch.Add(order);
}
await service.CreateMultipleAsync(batch);1 The whole collection
The customer's partition: four orders and the profile, each its own entity.
Already in scope: service (ArxDynamoDbService)
var key = new ArxEntity("customer");
key["customer_id"] = "C-D431";
var items = await service.QueryPartitionAsync(key);
var profile = items.Entities.Single(x => x.EntityName == "customer");
var orders = items.Entities.Where(x => x.EntityName == "sales_order").ToList();
Console.WriteLine($"{profile["name"]}: {orders.Count} orders, {orders.Sum(x => (decimal)x["total"]!)} in all");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 "SK", "entity_type" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D431'Expect: 5 items: ORDER#SO-D431 to ORDER#SO-D434 (sales_order) and PROFILE (customer).
2 A page of it
Two items at a time, with the cookie.
Already in scope: service (ArxDynamoDbService)
var key = new ArxEntity("customer");
key["customer_id"] = "C-D431";
var pages = 0;
string? cookie = null;
do
{
var page = await service.QueryPartitionAsync(key, new ArxPagingInfo { Count = 2, PagingCookie = cookie });
pages++;
cookie = page.PagingCookie;
}
while (cookie != null);
Console.WriteLine($"read in {pages} pages");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 "SK" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D431'Expect: 5 items.
Good to know
QueryPartitionAsync reads every item of the partition a key gives, whatever its entity, and returns each as the entity its entity_type names, in sort key order: here the orders (ORDER#…) and then the profile (PROFILE). A relational database would join two tables; DynamoDB stores them together so it needn't.
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.RealCases;
// 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 RealCase1_ACustomerAndItsOrders().RunAsync(dbContext);
The sample, as it is in the repository.
using ARX.SDK.DynamoDb.Services;
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.DynamoDb.SampleOutput;
namespace ArxSamples.DynamoDb.Operations.RetrieveMultiple.RealCases;
/// <summary>
/// The single-table design's reason to exist: a customer and its orders in one request, in two scenarios: the whole
/// item collection, and a page of it.
/// </summary>
/// <remarks>
/// <c>QueryPartitionAsync</c> reads every item of the partition a key gives, whatever its entity, and returns each as the
/// entity its <c>entity_type</c> names, in sort key order: here the orders (<c>ORDER#…</c>) and then the profile
/// (<c>PROFILE</c>). A relational database would join two tables; DynamoDB stores them together so it needn't.
/// </remarks>
public sealed class RealCase1_ACustomerAndItsOrders : ISample
{
public string Name => "retrieve-multiple-real-1-a-customer-and-its-orders";
public string Title => "A customer and its orders in one request";
public async Task RunAsync(SampleContext dbContext)
{
var customer = new ArxEntity("customer");
customer["customer_id"] = "C-D431";
customer["name"] = "Adventure Works Cycles";
var batch = new List<ArxEntity> { customer };
for (int i = 1; i <= 4; i++)
{
var order = new ArxEntity("sales_order");
order["customer_id"] = "C-D431";
order["order_number"] = $"SO-D43{i}";
order["status"] = "OPEN";
order["ordered_on"] = $"2026-10-2{i}";
order["total"] = 100m * i;
batch.Add(order);
}
await dbContext.Service.CreateMultipleAsync(batch);
await Scenario1_TheWholeCollectionAsync(dbContext.Service);
await Scenario2_APageOfItAsync(dbContext.Service);
}
/// <summary>1. The customer's partition: four orders and the profile, each its own entity.</summary>
private static async Task Scenario1_TheWholeCollectionAsync(ArxDynamoDbService service)
{
Step("1. The whole collection");
var key = new ArxEntity("customer");
key["customer_id"] = "C-D431";
var items = await service.QueryPartitionAsync(key);
var profile = items.Entities.Single(x => x.EntityName == "customer");
var orders = items.Entities.Where(x => x.EntityName == "sales_order").ToList();
Show($"{profile["name"]}: {orders.Count} orders, {orders.Sum(x => (decimal)x["total"]!)} in all");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT "SK", "entity_type" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D431'
* -- Expect: 5 items: ORDER#SO-D431 to ORDER#SO-D434 (sales_order) and PROFILE (customer).
* ===========================================================
*/
}
/// <summary>2. Two items at a time, with the cookie.</summary>
private static async Task Scenario2_APageOfItAsync(ArxDynamoDbService service)
{
Step("2. A page of it");
var key = new ArxEntity("customer");
key["customer_id"] = "C-D431";
var pages = 0;
string? cookie = null;
do
{
var page = await service.QueryPartitionAsync(key, new ArxPagingInfo { Count = 2, PagingCookie = cookie });
pages++;
cookie = page.PagingCookie;
}
while (cookie != null);
Show($"read in {pages} pages");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT "SK" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D431'
* -- Expect: 5 items.
* ===========================================================
*/
}
}
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 whole collection
Adventure Works Cycles: 4 orders, 1000 in all
> 2. A page of it
read in 3 pages