Photo par Ergita Sela

Paginer vos résultats avec .NET V4 SDK pour Azure Cosmos DB

Utilisez les continuation tokens pour obtenir les éléments suivants !

Créé par Damien Aicheh le 28/09/2021 · 10 mins

Lors du développement de votre API, vous devez souvent retourner une liste d’éléments. Dans la plupart des cas, vous ne pouvez pas retourner tous ces éléments à la fois. C’est là qu’intervient le mécanisme de pagination !

Dans ce tutoriel, je vais vous montrer les parties importantes de ce mécanisme à l’aide du nouveau nuget Azure Cosmos DB dans une Azure Functions. Veuillez noter que ce package NuGet est en preview au moment de la rédaction de cet article.

Nugets Azure Cosmos

Cette nouvelle version implémente les meilleures pratiques et les patterns courants pour utiliser Cosmos DB. L’exemple de code source complet est bien entendu disponible dans le répeertoire Github associé.

Le workflow

Imaginons ce scénario : vous devez retourner une liste de légumes depuis votre API. Vous avez un très grand nombre de légumes dans votre base de données, vous devez donc paginer les résultats.

Le continuation token

Supposons que vous ayez 1000 éléments dans votre liste et que vous vouliez simplement envoyer 25 éléments par page. Votre API doit également envoyer quelque chose pour aider le client à savoir sur quelle page reprendre le reste des éléments. C’est là qu’intervient le continuation token.

Ce token est renvoyé par Azure Cosmos DB pour vous permettre d’obtenir les éléments suivants. Lorsque le client demande les éléments suivants, il doit envoyer ce continuation token à l’API. C’est comme un marque page dans votre API.

Voici un petit schéma pour vous aider à comprendre le principe :

Continuation token

Lorsqu’il n’y a plus d’éléments à récupérer, Azure Cosmos DB renvoie une valeur null pour le token de continuation.

Le package nuget Azure.Cosmos renvoie une propriété string appelée ContinuationToken qui est un json à plat. Voici le format :

{
   "Version":"1.1",
   "QueryPlan":"{\"partitionedQueryExecutionInfoVersion\":2,\"queryInfo\":{\"distinctType\":\"None\",\"top\":null,\"offset\":null,\"limit\":null,\"orderBy\":[],\"orderByExpressions\":[],\"groupByExpressions\":[],\"groupByAliases\":[],\"aggregates\":[],\"groupByAliasToAggregateType\":{},\"rewrittenQuery\":\"\",\"hasSelectValue\":false},\"queryRanges\":[{\"min\":\"\",\"max\":\"FF\",\"isMinInclusive\":true,\"isMaxInclusive\":false}]}",
   "SourceContinuationToken":"[{\"token\":\"-RID:~OuEWAPVCbA0UAAERTTAAAAA==#RT:1#TRC:20#ISV:2#IEO:65567#QCF:4\",\"range\":{\"min\":\"\",\"max\":\"FF\"}}]"
}

La valeur la plus importante ici est contenue dans la propriété SourceContinuationToken. Il s’agit du continuation token lui-même, le client devra le renvoyer à l’API pour obtenir la prochaine donnée.

Pour extraire cette valeur, on peut créer un objet basique pour désérialiser ce json :

public class ContinuationToken
{
    [JsonProperty("Version")]
    public string Version { get; set; }

    [JsonProperty("QueryPlan")]
    public string QueryPlan { get; set; }

    [JsonProperty("SourceContinuationToken")]
    public string SourceContinuationToken { get; set; }
}

Pagination

Il est maintenant temps de regarder le code de pagination lui-même :

public async Task<PagedListResponse<Vegetable>> GetPaginatedVegetablesAsync(int pageSize, string continuationToken)
{
    try
    {
        // Get the container where the elements are stored
        var container = GetContainer();
        // Create your query
        var query = new QueryDefinition("SELECT * FROM v");

        var queryResultSetIterator = container.GetItemQueryIterator<Vegetable>(query, requestOptions: new QueryRequestOptions()
        {
            MaxItemCount = pageSize,
        }, continuationToken: continuationToken).AsPages();

        var result = await queryResultSetIterator.FirstOrDefaultAsync();

        // Deserialize the ContinuationToken propety to access the SourceContinuationToken
        var sourceContinuationToken = result.ContinuationToken != null ? 
                                JsonConvert.DeserializeObject<ContinuationToken>(result.ContinuationToken).SourceContinuationToken : null;
        // Send the data with the continuation token
        return new PagedListResponse<Vegetable>()
        {
            ContinuationToken = sourceContinuationToken,
            Data = result.Values.ToList(),
        };
    }
    catch (CosmosException ex)
    {
        _logger.LogError($"Entities was not retrieved successfully - error details: {ex.Message}");

        if (ex.Status != (int)HttpStatusCode.NotFound)
        {
            throw;
        }

        return null;
    }
}

Comme vous pouvez le voir ci-dessus, nous commençons par obtenir le conteneur où nous voulons récupérer les données. Ensuite, nous définissons notre query, il s’agit d’un select basique mais vous pouvez injecter des paramètres avec la méthode WithParameter().

Les QueryRequestOptions définissent le nombre maximum d’éléments par page. Bien sûr, si vous en demandez 10 et que vous n’en avez que 3, vous en recevrez 3.

La méthode FirstOrDefaultAsync provient du package nuget System.Linq.Async qui est vraiment utile pour manipuler les objets IAsyncEnumerable. Cette méthode est accessible dans le namespace using System.Linq;.

Ensuite, nous désérialisons la propriété ContinuationToken afin de pouvoir accéder au SourceContinuationToken qui sera utilisé pour le prochain appel.

Enfin, nous retournons un objet PagedListResponse qui contient la liste des données et le continuation token.

Obtenez les éléments suivants de la liste

Avec ce mécanisme prêt, comment l’utiliser en pratique ?

Lorsque vous appelez le end point la première fois, vous le faites directement comme ceci : https://YOUR_HOST/api/demo-az-cosmos-pagination?page_size=25 sans spécifier la propriété continuation car vous ne l’avez pas au début.

Ensuite, pour les prochains appels, l’url sera renseignée avec la propriété continuation comme ci-dessous : https://YOUR_HOST/api/demo-az-cosmos-pagination?page_size=25&continuation=%5B%7B%22token%22%3A%22-RID%3A~OuEWA...

La valeur du continuation token est convertie en sa représentation échappée pour la transmettre à l’url. Pour aider le client, votre API peut l’envoyer directement comme ceci :

public string UrlEncodedContinuationToken
{
    get => Uri.EscapeDataString(this.ContinuationToken ?? string.Empty);
}

Voici le genre de résultats que vous aurez :

{
  "continuationToken": "[{\"token\":\"-RID:~OuEWAPVCbA0PAAAAAAAAAA==#RT:3#TRC:15#ISV:2#IEO:65567#QCF:4\",\"range\":{\"min\":\"\",\"max\":\"FF\"}}]",
  "data": [
    {
      "id": "ef99e80e-b028-4d0c-ac2f-f277800f6886",
      "name": "squash"
    },
    {
      "id": "cf8fc35d-adae-44ae-9a47-010d790de966",
      "name": "spinach"
    },
    {
      "id": "c6986662-78ab-422f-8887-8530621d9da9",
      "name": "bean"
    },
    {
      "id": "3bc0c55d-9f47-4deb-a31b-c0b877605e20",
      "name": "lamb's lettuce"
    },
    {
      "id": "8ecd6b1b-1d61-409f-beb5-91e9f2a7fa21",
      "name": "melon"
    }
  ],
  "urlEncodedContinuationToken": "%5B%7B%22token%22%3A%22-RID%3A~OuEWAPVCbA0PAAAAAAAAAA%3D%3D%23RT%3A3%23TRC%3A15%23ISV%3A2%23IEO%3A65567%23QCF%3A4%22%2C%22range%22%3A%7B%22min%22%3A%22%22%2C%22max%22%3A%22FF%22%7D%7D%5D"
}

Touche finale

Comme vous l’avez vu, il est très facile d’ajouter une pagination avec le nouveau nuget Azure.Cosmos. Vous trouverez un exemple complet utilisant une Azure Functions dans ce répertoire Github. Toute la configuration se trouve dans le fichier README.md de ce projet.

Happy coding!

Vous avez aimé ce tutoriel ? Laissez une étoile sur le répertoire Github associé !

Sources:

N'hésitez pas à me suivre sur pour ne pas rater mon prochain tutoriel !