Paging in Azure Cosmos DB SQL .NET SDK

October 26, 2018

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.


© 2020 Jesse Barocio. Built with Gatsby