Chasing DevOps

A blog about software development, DevOps, and delivering value.

Paging in Azure Cosmos DB SQL .NET SDK

Record paging is a really common requirement for APIs that expose a lot of data. Paging in Azure Cosmos DB SQL API is done using continuation tokens. This post demonstrates how to use them to implement a paged API.

When querying Cosmos DB through the REST API you can specify a maximum count to return in the x-ms-max-item-count header. This acts as a page size. If your query actually has more results than this then the response will include a continuation token. That token can be passed in another request in the x-ms-continuation header to get the next page of results. The .NET SDK allows you to specify these values in a FeedOptions object when building your query.

To demonstrate, I built a simple Azure Function with an HttpTrigger that allows clients to request paged data from Cosmos DB. The complete code is below.

[FunctionName(GetRecords)]
public static async Task<IActionResult> GetRecords(
[HttpTrigger(AuthorizationLevel.Function, get, Route = records)]
HttpRequest req,
[CosmosDB(databaseName: Records, collectionName: records, ConnectionStringSetting = CosmosDBConnection)]
DocumentClient client)
{
var queryParams = req.GetQueryParameterDictionary();
// Maximum records to return (default to 50)
var count = Int32.Parse(queryParams.FirstOrDefault(q => q.Key == count).Value ?? 50);
// Continuation token (for paging)
var continuationToken = queryParams.FirstOrDefault(q => q.Key == continuationToken).Value;
// Build query options
var feedOptions = new FeedOptions()
{
MaxItemCount = count,
RequestContinuation = continuationToken
};
var uri = UriFactory.CreateDocumentCollectionUri(Records, records);
var query = client.CreateDocumentQuery(uri, feedOptions)
.AsDocumentQuery();
var results = await query.ExecuteNextAsync();
return new OkObjectResult(new
{
hasMoreResults = query.HasMoreResults,
pagingToken = query.HasMoreResults ? results.ResponseContinuation : null,
results = results.ToList()
});
}
view raw GetRecords.cs hosted with ❤ by GitHub

A client first requests data by performing a GET request to api/records. I am defaulting the MaxItemCount to 50, but allow the caller to override that setting with a query string parameter. The response will look something like this:

{
“hasMoreResults”: true,
“pagingToken”: {\”token\”:\”AV40ANomRkpkAAAAAAAAAA==\”,\”range\”:{\”min\”:\”\”,\”max\”:\”FF\”}},
“results”: [{ }]
}
view raw response.json hosted with ❤ by GitHub

To get the next page of data the user simply passes the continuation token in the query string, for example api/records?continuationToken={token}.

There are some limitations with this method. For one, there is no way to retrieve a total record count without performing a second expensive query. Another concern is it leaks implementation details to the client by passing the Cosmos DB-specific token for paging. A better approach would be to cache this continuation token in Redis or table storage using your own unique string as a key. But I’ll leave that up to you to implement.

Leave a Reply

Your email address will not be published.

Jesse Barocio

Software developer, DevOps engineer, and productivity tool nut. Continuously improving. Have a question or problem you need solved? Email me!