MongoDB · RetrieveMultiple
Page through results
Page through a result, in two scenarios: by page number with the total, and by the paging cookie each page returns.
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); SampleCatalog.cs (the tables' ids, keys, lookups and display names); SampleContext.Direct.cs (dbContext.Direct); MongoDirectHelper.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 products = Enumerable.Range(1, 7).Select(i =>
{
var product = new ArxEntity("product");
product["sku"] = $"SKU-M97{i}";
product["name"] = $"Product {i}";
product["category"] = "M97";
product["price"] = i % 3 * 5m;
return product;
}).ToList();
await service.CreateMultipleAsync(products);1 By page number
Page 2 of 3, with the total.
Already in scope: service (ArxMongoDbService)
var query = Category();
query.PageInfo = new ArxPagingInfo { Count = 3, PageNumber = 2, ReturnTotalRecordCount = true };
var page = await service.RetrieveMultipleAsync(query);
Console.WriteLine($"page {page.CurrentPage} of {page.TotalPageCount} ({page.TotalRecordCount} products): {string.Join(", ", page.Entities.Select(x => x["name"]))}");Defined once in this sample.
private static ArxQueryExpression Category()
{
var query = new ArxQueryExpression("product");
query.ColumnSet.AddColumns("name", "price");
query.Criteria.AddCondition("category", ArxConditionOperator.Equal, "M97");
query.AddOrder("price", ArxOrderType.Ascending);
return query;
}Check it in the database
Run it in mongosh connected to the database, after the code has run.
db.product.countDocuments({ category: "M97" })Expect: 7.
2 By cookie
Every page by cookie: pass back the one the previous page gave, until there is none.
Already in scope: service (ArxMongoDbService)
string? cookie = null;
var seen = new List<string>();
do
{
var query = Category();
query.PageInfo = new ArxPagingInfo { Count = 3, PagingCookie = cookie };
var page = await service.RetrieveMultipleAsync(query);
seen.AddRange(page.Entities.Select(x => (string)x["sku"]!));
cookie = page.PagingCookie;
}
while (cookie != null);
Console.WriteLine($"read {seen.Count} products, {seen.Distinct().Count()} different");Defined once in this sample.
private static ArxQueryExpression Category()
{
var query = new ArxQueryExpression("product");
query.ColumnSet.AddColumns("name", "price");
query.Criteria.AddCondition("category", ArxConditionOperator.Equal, "M97");
query.AddOrder("price", ArxOrderType.Ascending);
return query;
}Check it in the database
Run it in mongosh connected to the database, after the code has run.
db.product.find({ category: "M97" }, { name: 1, price: 1 }).sort({ price: 1, _id: 1 })Expect: 7 documents, in the order the pages read them.
Good to know
A page number skips the earlier documents on the server; the total comes in the same round trip. A cookie says where the next page starts (the last document's sort values and id), so no document is skipped or read twice even when values repeat; pass it back unchanged, with the same query.
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.MongoDb --version 1.0.0-rc.1 - Database: a MongoDB 7.0, a replica set (tested with 7.0.45, one node) database with the sample schema.
- Configuration: set
ARX_SAMPLES_MONGODBto 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,SampleCatalog.cs,SampleContext.Direct.cs,MongoDirectHelper.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.MongoDb;
using ArxSamples.MongoDb.Operations.RetrieveMultiple.Basics;
// The connection string comes from the environment, never from the code (see "Configure the connection").
var connectionString = Environment.GetEnvironmentVariable("ARX_SAMPLES_MONGODB")
?? throw new InvalidOperationException("Set the ARX_SAMPLES_MONGODB environment variable to your MongoDB 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.MongoDb.Services;
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.MongoDb.SampleOutput;
namespace ArxSamples.MongoDb.Operations.RetrieveMultiple.Basics;
/// <summary>
/// Page through a result, in two scenarios: by page number with the total, and by the paging cookie each page returns.
/// </summary>
/// <remarks>
/// A page number skips the earlier documents on the server; the total comes in the same round trip. A cookie says
/// where the next page starts (the last document's sort values and id), so no document is skipped or read twice even
/// when values repeat; pass it back unchanged, with the same query.
/// </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 products = Enumerable.Range(1, 7).Select(i =>
{
var product = new ArxEntity("product");
product["sku"] = $"SKU-M97{i}";
product["name"] = $"Product {i}";
product["category"] = "M97";
product["price"] = i % 3 * 5m;
return product;
}).ToList();
await dbContext.Service.CreateMultipleAsync(products);
await Scenario1_ByPageNumberAsync(dbContext.Service);
await Scenario2_ByCookieAsync(dbContext.Service);
}
private static ArxQueryExpression Category()
{
var query = new ArxQueryExpression("product");
query.ColumnSet.AddColumns("name", "price");
query.Criteria.AddCondition("category", ArxConditionOperator.Equal, "M97");
query.AddOrder("price", ArxOrderType.Ascending);
return query;
}
/// <summary>1. Page 2 of 3, with the total.</summary>
private static async Task Scenario1_ByPageNumberAsync(ArxMongoDbService service)
{
Step("1. By page number");
var query = Category();
query.PageInfo = new ArxPagingInfo { Count = 3, PageNumber = 2, ReturnTotalRecordCount = true };
var page = await service.RetrieveMultipleAsync(query);
Show($"page {page.CurrentPage} of {page.TotalPageCount} ({page.TotalRecordCount} products): {string.Join(", ", page.Entities.Select(x => x["name"]))}");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* db.product.countDocuments({ category: "M97" })
* -- Expect: 7.
* ===========================================================
*/
}
/// <summary>2. Every page by cookie: pass back the one the previous page gave, until there is none.</summary>
private static async Task Scenario2_ByCookieAsync(ArxMongoDbService service)
{
Step("2. By cookie");
string? cookie = null;
var seen = new List<string>();
do
{
var query = Category();
query.PageInfo = new ArxPagingInfo { Count = 3, PagingCookie = cookie };
var page = await service.RetrieveMultipleAsync(query);
seen.AddRange(page.Entities.Select(x => (string)x["sku"]!));
cookie = page.PagingCookie;
}
while (cookie != null);
Show($"read {seen.Count} products, {seen.Distinct().Count()} different");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* db.product.find({ category: "M97" }, { name: 1, price: 1 }).sort({ price: 1, _id: 1 })
* -- Expect: 7 documents, in the order the pages read them.
* ===========================================================
*/
}
}
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. By page number
page 2 of 3 (7 products): Product 4, Product 7, Product 2
> 2. By cookie
read 7 products, 7 different