Cloud Spanner: créer un classement de jeu avec C#

1. Présentation

Google Cloud Spanner est un service de base de données relationnelle entièrement géré, distribué à l'échelle mondiale et évolutif horizontalement qui fournit des transactions ACID et une sémantique SQL sans compromis sur les performances et le niveau de disponibilité.

Dans cet atelier, vous allez apprendre à configurer une instance Cloud Spanner. Au fil des étapes, vous allez créer une base de données et un schéma qui peuvent être utilisés pour un classement de jeu. Vous allez commencer par créer une table "Players" (Joueurs) pour stocker les informations concernant les joueurs et un tableau "Scores" pour stocker les scores des joueurs.

Vous remplirez ensuite ces tables avec des exemples de données. Vous finirez l'atelier en exécutant quelques exemples de requêtes de "Top 10" avant de supprimer l'instance pour libérer les ressources.

Points abordés

  • Comment configurer une instance Cloud Spanner.
  • Comment créer une base de données et des tables.
  • Comment utiliser une colonne d'horodatage de commit.
  • Comment charger des données dans une table de base de données Cloud Spanner avec des horodatages.
  • Comment interroger votre base de données Cloud Spanner.
  • Comment supprimer une instance Cloud Spanner.

Ce dont vous aurez besoin

Comment allez-vous utiliser ce tutoriel ?

Je vais le lire uniquement Je vais le lire et effectuer les exercices

Comment évalueriez-vous votre expérience avec Google Cloud Platform ?

<ph type="x-smartling-placeholder"></ph> Débutant Intermédiaire Expert
.

2. Préparation

Configuration de l'environnement au rythme de chacun

Si vous ne possédez pas encore de compte Google (Gmail ou Google Apps), vous devez en créer un. Connectez-vous à la console Google Cloud Platform (console.cloud.google.com) et créez un projet.

Si vous avez déjà un projet, cliquez sur le menu déroulant de sélection du projet dans l'angle supérieur gauche de la console :

6c9406d9b014760.png

Cliquez ensuite sur le bouton "NEW PROJECT" (NOUVEAU PROJET) dans la boîte de dialogue qui s'affiche pour créer un projet :

f708315ae07353d0.png

Si vous n'avez pas encore de projet, une boîte de dialogue semblable à celle-ci apparaîtra pour vous permettre d'en créer un :

870a3cbd6541ee86.png

La boîte de dialogue de création de projet suivante vous permet de saisir les détails de votre nouveau projet :

6a92c57d3250a4b3.png

Notez l'ID du projet. Il s'agit d'un nom unique pour tous les projets Google Cloud, ce qui implique que le nom ci-dessus n'est plus disponible pour vous… Désolé ! Tout au long de cet atelier de programmation, nous utiliserons PROJECT_ID pour faire référence à cet ID.

Ensuite, si ce n'est pas déjà fait, vous devez activer la facturation dans Developers Console afin de pouvoir utiliser les ressources Google Cloud puis activer l'API Cloud Spanner.

15d0ef27a8fbab27.png

Suivre cet atelier de programmation ne devrait pas vous coûter plus d'un euro. Cependant, cela peut s'avérer plus coûteux si vous décidez d'utiliser davantage de ressources ou si vous n'interrompez pas les ressources (voir la section "Effectuer un nettoyage" à la fin du présent document). Les tarifs de Google Cloud Spanner sont décrits sur cette page.

Les nouveaux utilisateurs de Google Cloud Platform peuvent bénéficier d'un Essai gratuit avec 300 $ de crédits afin de suivre gratuitement le présent atelier.

Configuration de Google Cloud Shell

Bien que Google Cloud et Spanner puissent être utilisés à distance depuis votre ordinateur portable, nous allons utiliser Google Cloud Shell pour cet atelier de programmation, un environnement de ligne de commande exécuté dans le cloud.

Cette machine virtuelle basée sur Debian contient tous les outils de développement dont vous aurez besoin. Elle intègre un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Cela signifie que tout ce dont vous avez besoin pour cet atelier de programmation est un navigateur (oui, tout fonctionne sur un Chromebook).

  1. Pour activer Cloud Shell à partir de Cloud Console, cliquez simplement sur Activer Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (l'opération de provisionnement et la connexion à l'environnement ne devraient prendre que quelques minutes).

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

Capture d&#39;écran du 2017-06-14 à 10.13.43 PM.png

Une fois connecté à Cloud Shell, vous êtes normalement déjà authentifié et le projet PROJECT_ID est sélectionné :

gcloud auth list

Résultat de la commande

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Résultat de la commande

[core]
project = <PROJECT_ID>

Si, pour une raison quelconque, le projet n'est pas défini, exécutez simplement la commande suivante :

gcloud config set project <PROJECT_ID>

Vous recherchez votre PROJECT_ID ? Vérifiez l'ID que vous avez utilisé pendant les étapes de configuration ou recherchez-le dans le tableau de bord Cloud Console :

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Par défaut, Cloud Shell définit certaines variables d'environnement qui pourront s'avérer utiles pour exécuter certaines commandes dans le futur.

echo $GOOGLE_CLOUD_PROJECT

Résultat de la commande

<PROJECT_ID>
  1. Pour finir, définissez la configuration du projet et de la zone par défaut :
gcloud config set compute/zone us-central1-f

Vous pouvez choisir parmi différentes zones. Pour en savoir plus, consultez la page Régions et zones.

Résumé

Cette étape consiste à configurer votre environnement.

Étapes suivantes

Vous allez maintenant configurer une instance Cloud Spanner.

3. Configurer une instance Cloud Spanner

Dans cette étape, nous allons configurer notre instance Cloud Spanner pour cet atelier de programmation. Recherchez l'entrée Spanner 1a6580bd3d3e6783.pngdans le menu principal en haut à gauche 3129589f7bc9e5ce.png ou recherchez Spanner en appuyant sur "/" et saisissez "Spanner"

36e52f8df8e13b99.png

Cliquez ensuite sur 95269e75bc8c3e4d.png et remplissez le formulaire en renseignant le nom d'instance cloudspanner-leaderboard, en choisissant une configuration (sélectionnez une instance régionale), puis en définissant le nombre de nœuds (pour le présent atelier, nous n'aurons besoin que d'un seul nœud). Pour les instances en production et pour bénéficier du contrat de niveau de service de Cloud Spanner, vous devez exécuter au moins trois nœuds dans votre instance Cloud Spanner.

Dernière étape mais pas des moindres, cliquez sur "Créer". L'instance Cloud Spanner sera prête en quelques secondes.

dceb68e9ed3801e8.png

À l'étape suivante, nous allons utiliser la bibliothèque cliente C# pour créer une base de données et un schéma dans notre nouvelle instance.

4. Créer une base de données et un schéma

Au cours de cette étape, nous allons créer notre exemple de base de données et notre schéma.

Utilisons la bibliothèque cliente C# pour créer deux tables : un tableau "Joueurs" pour les informations sur les joueurs et un tableau des scores pour stocker les scores des joueurs. Pour ce faire, nous allons suivre la procédure de création d'une application de console C# dans Cloud Shell.

Commencez par cloner l'exemple de code du présent atelier de programmation à partir de GitHub en saisissant la commande suivante dans Cloud Shell :

git clone https://github.com/GoogleCloudPlatform/dotnet-docs-samples.git

Accédez ensuite au répertoire "applications" dans lequel vous allez créer votre application.

cd dotnet-docs-samples/applications/

L'ensemble du code requis pour cet atelier de programmation se trouve dans le répertoire dotnet-docs-samples/applications/leaderboard existant en tant qu'application C# exécutable Leaderboard, pour vous servir de référence tout au long de cet atelier. Nous allons créer un répertoire et créer une copie de l'application de classement étape par étape.

Créez un répertoire nommé "codelab" pour l'application et accédez-y avec la commande suivante :

mkdir codelab && cd $_

Créer une application de console .NET C# nommée "Leaderboard" en exécutant la commande suivante:

dotnet new console -n Leaderboard

Cette commande crée une application de console simple composée de deux fichiers principaux : le fichier de projet Leaderboard.csproj et le fichier de programme Program.cs.

Exécutons-la. Accédez au répertoire Leaderboard que vous venez de créer et qui contient l'application:

cd Leaderboard

Saisissez ensuite la commande suivante pour l'exécuter.

dotnet run

Le résultat de l'application "Hello World!" doit s'afficher.

Nous allons maintenant mettre à jour notre application de console en modifiant Program.cs pour utiliser la bibliothèque cliente Spanner C# afin de créer un classement composé de deux tables, "Joueurs" et "Scores". Vous pouvez faire tout cela directement depuis l'éditeur Cloud Shell :

Ouvrez l'éditeur Cloud Shell en cliquant sur l'icône en surbrillance ci-dessous :

73cf70e05f653ca.png

Ensuite, ouvrez le fichier Program.cs dans l'éditeur Cloud Shell et remplacez le code existant du fichier par le code requis pour créer la base de données leaderboard et les tables Players et Scores en collant le code d'application C# suivant dans le fichier Program.cs:

using System;
using System.Threading.Tasks;
using Google.Cloud.Spanner.Data;
using CommandLine;

namespace GoogleCloudSamples.Leaderboard
{
    [Verb("create", HelpText = "Create a sample Cloud Spanner database "
        + "along with sample 'Players' and 'Scores' tables in your project.")]
    class CreateOptions
    {
        [Value(0, HelpText = "The project ID of the project to use "
            + "when creating Cloud Spanner resources.", Required = true)]
        public string projectId { get; set; }
        [Value(1, HelpText = "The ID of the instance where the sample database "
            + "will be created.", Required = true)]
        public string instanceId { get; set; }
        [Value(2, HelpText = "The ID of the sample database to create.",
            Required = true)]
        public string databaseId { get; set; }
    }

    public class Program
    {
        enum ExitCode : int
        {
            Success = 0,
            InvalidParameter = 1,
        }

        public static object Create(string projectId,
            string instanceId, string databaseId)
        {
            var response =
                CreateAsync(projectId, instanceId, databaseId);
            Console.WriteLine("Waiting for operation to complete...");
            response.Wait();
            Console.WriteLine($"Operation status: {response.Status}");
            Console.WriteLine($"Created sample database {databaseId} on "
                + $"instance {instanceId}");
            return ExitCode.Success;
        }

        public static async Task CreateAsync(
            string projectId, string instanceId, string databaseId)
        {
            // Initialize request connection string for database creation.
            string connectionString =
                $"Data Source=projects/{projectId}/instances/{instanceId}";
            using (var connection = new SpannerConnection(connectionString))
            {
                string createStatement = $"CREATE DATABASE `{databaseId}`";
                string[] createTableStatements = new string[] {
                  // Define create table statement for Players table.
                  @"CREATE TABLE Players(
                    PlayerId INT64 NOT NULL,
                    PlayerName STRING(2048) NOT NULL
                  ) PRIMARY KEY(PlayerId)",
                  // Define create table statement for Scores table.
                  @"CREATE TABLE Scores(
                    PlayerId INT64 NOT NULL,
                    Score INT64 NOT NULL,
                    Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
                  ) PRIMARY KEY(PlayerId, Timestamp),
                      INTERLEAVE IN PARENT Players ON DELETE NO ACTION" };
                // Make the request.
                var cmd = connection.CreateDdlCommand(
                    createStatement, createTableStatements);
                try
                {
                    await cmd.ExecuteNonQueryAsync();
                }
                catch (SpannerException e) when
                    (e.ErrorCode == ErrorCode.AlreadyExists)
                {
                    // OK.
                }
            }
        }

        public static int Main(string[] args)
        {
            var verbMap = new VerbMap<object>();
            verbMap
                .Add((CreateOptions opts) => Create(
                    opts.projectId, opts.instanceId, opts.databaseId))
                .NotParsedFunc = (err) => 1;
            return (int)verbMap.Run(args);
        }
    }
}

Pour vous donner une image plus claire du code du programme, voici un schéma du programme avec ses principaux composants libellés:

b70b1b988ea3ac8a.png

Vous pouvez utiliser le fichier Program.cs du répertoire dotnet-docs-samples/applications/leaderboard/step4 comme référence de ce à quoi doit ressembler votre fichier Program.cs après l'ajout du code pour activer la commande create.

Utilisez ensuite l'éditeur Cloud Shell pour ouvrir et modifier le fichier de projet du programme Leaderboard.csproj, en le mettant à jour pour qu'il ressemble au code suivant. Veillez à enregistrer toutes vos modifications à l'aide du menu "Fichier" de l'éditeur Cloud Shell.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Google.Cloud.Spanner.Data" Version="3.3.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\..\commandlineutil\Lib\CommandLineUtil.csproj" />
  </ItemGroup>

</Project>

Cette modification a ajouté une référence au package NuGet Google.Cloud.Spanner.Data Spanner en C#, dont nous avons besoin pour interagir avec l'API Cloud Spanner. Cette modification ajoute également une référence au projet CommandLineUtil, qui fait partie du dépôt GitHub dotnet-doc-samples et fournit un "verbmap" utile. une extension vers l'Open Source CommandLineParser. une bibliothèque pratique pour gérer les entrées de ligne de commande pour les applications de console.

Vous pouvez utiliser le fichier Leaderboard.csproj du répertoire dotnet-docs-samples/applications/leaderboard/step4 comme référence de ce à quoi doit ressembler votre fichier Leaderboard.csproj après l'ajout du code pour activer la commande create.

Vous êtes maintenant prêt à exécuter votre exemple mis à jour. Saisissez la commande suivante pour afficher la réponse par défaut de votre application mise à jour:

dotnet run

Vous devriez voir une sortie semblable à ce qui suit.

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  No verb selected.

  create     Create a sample Cloud Spanner database along with sample 'Players' and 'Scores' tables in your project.

  help       Display more information on a specific command.

  version    Display version information.

D'après cette réponse, nous pouvons voir qu'il s'agit de l'application Leaderboard, qui peut être exécutée avec l'une des trois commandes possibles: create, help et version.

Essayons la commande create pour créer une base de données et des tables Spanner. Exécutez la commande sans arguments pour afficher les arguments attendus.

dotnet run create

Vous devriez obtenir une réponse semblable à celle-ci:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  A required value not bound to option name is missing.

  --help          Display this help screen.

  --version       Display version information.

  value pos. 0    Required. The project ID of the project to use when creating Cloud Spanner resources.

  value pos. 1    Required. The ID of the instance where the sample database will be created.

  value pos. 2    Required. The ID of the sample database to create.

Ici, nous pouvons voir que les arguments attendus de la commande create sont l'ID de projet, l'ID d'instance et l'ID de base de données.

Exécutez maintenant la commande suivante : Assurez-vous de bien remplacer PROJECT_ID par l'ID de projet que vous avez créé au début du présent atelier de programmation.

dotnet run create PROJECT_ID cloudspanner-leaderboard leaderboard

Après quelques secondes, une réponse de ce type doit s'afficher :

Waiting for operation to complete...
Operation status: RanToCompletion
Created sample database leaderboard on instance cloudspanner-leaderboard

Dans la section Cloud Spanner de Cloud Console, vous devriez voir votre nouvelle base de données et vos tables dans le menu de gauche.

ba9008bb84cb90b0.png

À l'étape suivante, nous mettrons à jour notre application afin de charger des données dans votre nouvelle base de données.

5. Charger les données

Nous disposons à présent d'une base de données appelée leaderboard et contenant deux tables, Players et Scores. Utilisons maintenant la bibliothèque cliente C# pour remplir notre table Players avec des joueurs et notre table Scores avec des scores aléatoires pour chaque joueur.

Ouvrez l'éditeur Cloud Shell en cliquant sur l'icône en surbrillance ci-dessous :

4d17840699d8e7ce.png

Ensuite, modifiez le fichier Program.cs dans l'éditeur Cloud Shell afin d'ajouter une commande insert pouvant être utilisée pour insérer 100 joueurs dans la table Players, ou pour insérer quatre scores aléatoires dans le fichier Scores pour chaque joueur dans la table Players.

Commencez par ajouter un nouveau bloc de commande insert dans le "Verbmap" en haut du programme, sous le bloc de commande create existant:

[Verb("insert", HelpText = "Insert sample 'players' records or 'scores' records "
        + "into the database.")]
    class InsertOptions
    {
        [Value(0, HelpText = "The project ID of the project to use "
            + "when managing Cloud Spanner resources.", Required = true)]
        public string projectId { get; set; }
        [Value(1, HelpText = "The ID of the instance where the sample database resides.",
            Required = true)]
        public string instanceId { get; set; }
        [Value(2, HelpText = "The ID of the database where the sample database resides.",
            Required = true)]
        public string databaseId { get; set; }
        [Value(3, HelpText = "The type of insert to perform, 'players' or 'scores'.",
            Required = true)]
        public string insertType { get; set; }
    }

Ajoutez ensuite les méthodes Insert, InsertPlayersAsync et InsertScoresAsync suivantes sous la méthode CreateAsync existante:

        public static object Insert(string projectId,
            string instanceId, string databaseId, string insertType)
        {
            if (insertType.ToLower() == "players")
            {
                var responseTask =
                    InsertPlayersAsync(projectId, instanceId, databaseId);
                Console.WriteLine("Waiting for insert players operation to complete...");
                responseTask.Wait();
                Console.WriteLine($"Operation status: {responseTask.Status}");
            }
            else if (insertType.ToLower() == "scores")
            {
                var responseTask =
                    InsertScoresAsync(projectId, instanceId, databaseId);
                Console.WriteLine("Waiting for insert scores operation to complete...");
                responseTask.Wait();
                Console.WriteLine($"Operation status: {responseTask.Status}");
            }
            else
            {
                Console.WriteLine("Invalid value for 'type of insert'. "
                    + "Specify 'players' or 'scores'.");
                return ExitCode.InvalidParameter;
            }
            Console.WriteLine($"Inserted {insertType} into sample database "
                + $"{databaseId} on instance {instanceId}");
            return ExitCode.Success;
        }

       public static async Task InsertPlayersAsync(string projectId,
            string instanceId, string databaseId)
        {
            string connectionString =
                $"Data Source=projects/{projectId}/instances/{instanceId}"
                + $"/databases/{databaseId}";

            long numberOfPlayers = 0;
            using (var connection = new SpannerConnection(connectionString))
            {
                await connection.OpenAsync();
                await connection.RunWithRetriableTransactionAsync(async (transaction) =>
                {
                    // Execute a SQL statement to get current number of records
                    // in the Players table to use as an incrementing value 
                    // for each PlayerName to be inserted.
                    var cmd = connection.CreateSelectCommand(
                        @"SELECT Count(PlayerId) as PlayerCount FROM Players");
                    numberOfPlayers = await cmd.ExecuteScalarAsync<long>();
                    // Insert 100 player records into the Players table.
                    SpannerBatchCommand cmdBatch = connection.CreateBatchDmlCommand();
                    for (int i = 0; i < 100; i++)
                    {
                        numberOfPlayers++;
                        SpannerCommand cmdInsert = connection.CreateDmlCommand(
                            "INSERT INTO Players "
                            + "(PlayerId, PlayerName) "
                            + "VALUES (@PlayerId, @PlayerName)",
                                new SpannerParameterCollection {
                                    {"PlayerId", SpannerDbType.Int64},
                                    {"PlayerName", SpannerDbType.String}});
                        cmdInsert.Parameters["PlayerId"].Value =
                            Math.Abs(Guid.NewGuid().GetHashCode());
                        cmdInsert.Parameters["PlayerName"].Value =
                            $"Player {numberOfPlayers}";
                        cmdBatch.Add(cmdInsert);
                    }
                    await cmdBatch.ExecuteNonQueryAsync();
                });
            }
            Console.WriteLine("Done inserting player records...");
        }

        public static async Task InsertScoresAsync(
            string projectId, string instanceId, string databaseId)
        {
            string connectionString =
            $"Data Source=projects/{projectId}/instances/{instanceId}"
            + $"/databases/{databaseId}";

            // Insert 4 score records into the Scores table for each player
            // in the Players table.
            using (var connection = new SpannerConnection(connectionString))
            {
                await connection.OpenAsync();
                await connection.RunWithRetriableTransactionAsync(async (transaction) =>
                {
                    Random r = new Random();
                    bool playerRecordsFound = false;
                    SpannerBatchCommand cmdBatch =
                                connection.CreateBatchDmlCommand();
                    var cmdLookup =
                    connection.CreateSelectCommand("SELECT * FROM Players");
                    using (var reader = await cmdLookup.ExecuteReaderAsync())
                    {
                        while (await reader.ReadAsync())
                        {
                            playerRecordsFound = true;
                            for (int i = 0; i < 4; i++)
                            {
                                DateTime randomTimestamp = DateTime.Now
                                        .AddYears(r.Next(-2, 1))
                                        .AddMonths(r.Next(-12, 1))
                                        .AddDays(r.Next(-28, 0))
                                        .AddHours(r.Next(-24, 0))
                                        .AddSeconds(r.Next(-60, 0))
                                        .AddMilliseconds(r.Next(-100000, 0));
                                SpannerCommand cmdInsert =
                                connection.CreateDmlCommand(
                                    "INSERT INTO Scores "
                                    + "(PlayerId, Score, Timestamp) "
                                    + "VALUES (@PlayerId, @Score, @Timestamp)",
                                    new SpannerParameterCollection {
                                        {"PlayerId", SpannerDbType.Int64},
                                        {"Score", SpannerDbType.Int64},
                                        {"Timestamp",
                                            SpannerDbType.Timestamp}});
                                cmdInsert.Parameters["PlayerId"].Value =
                                    reader.GetFieldValue<int>("PlayerId");
                                cmdInsert.Parameters["Score"].Value =
                                    r.Next(1000, 1000001);
                                cmdInsert.Parameters["Timestamp"].Value =
                                    randomTimestamp.ToString("o");
                                cmdBatch.Add(cmdInsert);
                            }
                        }
                        if (!playerRecordsFound)
                        {
                            Console.WriteLine("Parameter 'scores' is invalid "
                            + "since no player records currently exist. First "
                            + "insert players then insert scores.");
                            Environment.Exit((int)ExitCode.InvalidParameter);
                        }
                        else
                        {
                            await cmdBatch.ExecuteNonQueryAsync();
                            Console.WriteLine(
                                "Done inserting score records..."
                            );
                        }
                    }
                });
            }
        }

Ensuite, pour que la commande insert fonctionne, ajoutez le code suivant à la section "Main" de votre programme méthode:

                .Add((InsertOptions opts) => Insert(
                    opts.projectId, opts.instanceId, opts.databaseId, opts.insertType))

Vous pouvez utiliser le fichier Program.cs du répertoire dotnet-docs-samples/applications/leaderboard/step5 comme référence de ce à quoi doit ressembler votre fichier Program.cs après l'ajout du code pour activer la commande insert.

Exécutons maintenant le programme pour vérifier que la nouvelle commande insert est incluse dans la liste des commandes possibles du programme. Exécutez la commande suivante :

dotnet run

La commande insert devrait maintenant être incluse dans le résultat par défaut du programme:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  No verb selected.

  create     Create a sample Cloud Spanner database along with sample 'Players' and 'Scores' tables in your project.

  insert     Insert sample 'players' records or 'scores' records into the database.

  help       Display more information on a specific command.

  version    Display version information.

Exécutons maintenant la commande insert pour afficher ses arguments d'entrée. Saisissez la commande suivante :

dotnet run insert

Vous devriez obtenir la réponse suivante:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  A required value not bound to option name is missing.

  --help          Display this help screen.

  --version       Display version information.

  value pos. 0    Required. The project ID of the project to use when managing Cloud Spanner resources.

  value pos. 1    Required. The ID of the instance where the sample database resides.

  value pos. 2    Required. The ID of the database where the sample database resides.

  value pos. 3    Required. The type of insert to perform, 'players' or 'scores'.

Dans la réponse, vous pouvez voir qu'en plus de l'ID de projet, de l'ID d'instance et de l'ID de base de données, un autre argument value pos. 3 est attendu, qui est le "type d'insertion". à effectuer. Cet argument peut avoir la valeur "players" ou "scores".

Nous allons maintenant exécuter la commande insert avec les mêmes valeurs d'argument que celles utilisées lors de l'appel de la commande create en ajoutant "players" comme argument "type of insert" supplémentaire. Assurez-vous de bien remplacer PROJECT_ID par l'ID de projet que vous avez créé au début du présent atelier de programmation.

dotnet run insert PROJECT_ID cloudspanner-leaderboard leaderboard players

Après quelques secondes, une réponse de ce type doit s'afficher :

Waiting for insert players operation to complete...
Done inserting player records...
Operation status: RanToCompletion
Inserted players into sample database leaderboard on instance cloudspanner-leaderboard

Utilisons maintenant la bibliothèque cliente C# pour remplir notre table Scores avec quatre scores aléatoires, ainsi que des codes temporels pour chaque joueur de la table Players.

La colonne Timestamp de la table Scores a été définie comme colonne d'horodatage de commit ("commit timestamp") via l'instruction SQL suivante, exécutée lors de l'exécution de la commande create :

CREATE TABLE Scores(
  PlayerId INT64 NOT NULL,
  Score INT64 NOT NULL,
  Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
    INTERLEAVE IN PARENT Players ON DELETE NO ACTION

Notez l'attribut OPTIONS(allow_commit_timestamp=true). Cela fait de Timestamp une colonne d'horodatage de commit qu'il est possible de renseigner avec l'horodatage de transaction exact des opérations INSERT et UPDATE sur une ligne de table donnée.

Vous pouvez également insérer vos propres valeurs d'horodatage dans une colonne "commit timestamp" à condition d'utiliser un horodatage situé dans le passé, ce qui est exactement ce que nous avons fait dans le présent atelier de programmation.

Nous allons maintenant exécuter la commande insert avec les mêmes valeurs d'argument que celles utilisées lors de l'appel de la commande create en ajoutant "scores" comme argument "type of insert" supplémentaire. Assurez-vous de bien remplacer PROJECT_ID par l'ID de projet que vous avez créé au début du présent atelier de programmation.

dotnet run insert PROJECT_ID cloudspanner-leaderboard leaderboard scores

Après quelques secondes, une réponse de ce type doit s'afficher :

Waiting for insert players operation to complete...
Done inserting player records...
Operation status: RanToCompletion
Inserted players into sample database leaderboard on instance cloudspanner-leaderboard

L'exécution de insert avec scores spécifié comme argument "type of insert" supplémentaire appelle la méthode InsertScoresAsync. Celle-ci utilise les extraits de code ci-dessous pour insérer un horodatage généré de manière aléatoire avec une date et une heure dans le passé :

DateTime randomTimestamp = DateTime.Now
    .AddYears(r.Next(-2, 1))
    .AddMonths(r.Next(-12, 1))
    .AddDays(r.Next(-28, 0))
    .AddHours(r.Next(-24, 0))
    .AddSeconds(r.Next(-60, 0))
    .AddMilliseconds(r.Next(-100000, 0));
...
 cmdInsert.Parameters["Timestamp"].Value = randomTimestamp.ToString("o");

Pour renseigner automatiquement la colonne Timestamp avec le code temporel exact du moment où la mention "Insert" transaction a lieu, vous pouvez insérer la constante C# SpannerParameter.CommitTimestamp à la place, comme dans l'extrait de code suivant:

cmd.Parameters["Timestamp"].Value = SpannerParameter.CommitTimestamp;

Maintenant que le chargement des données est terminé, vérifions les valeurs que nous venons d'écrire dans les nouvelles tables. Sélectionnez d'abord la base de données leaderboard, puis la table Players. Cliquez sur l'onglet Data. Vous devriez voir des données dans les colonnes PlayerId et PlayerName de la table.

7bc2c96293c31c49.png

Vérifions maintenant le tableau des scores contient bien des données en cliquant sur le tableau Scores et en sélectionnant l'onglet Data. Vous devriez voir des données dans les colonnes PlayerId, Timestamp et Score de la table.

d8a4ee4f13244c19.png

Bravo ! Mettons à jour notre programme pour exécuter des requêtes que nous pouvons utiliser pour créer un classement de jeu.

6. Exécuter les requêtes de classement

Maintenant que nous avons configuré notre base de données et chargé des informations dans nos tables, nous allons créer un classement à l'aide de ces données. Pour ce faire, nous devons répondre aux quatre questions suivantes :

  1. Quels joueurs sont les "Top 10" joueurs de tous les temps ?
  2. Quels joueurs sont les "Top 10" joueurs de l'année ?
  3. Quels joueurs sont les "Top 10" joueurs du mois ?
  4. Quels joueurs sont les "Top 10" joueurs de la semaine ?

Mettons à jour notre programme pour exécuter les requêtes SQL qui répondront à ces questions.

Nous allons ajouter une commande query afin d'exécuter les requêtes permettant de répondre à ces questions en générant les informations requises pour notre classement.

Modifiez le fichier Program.cs dans l'éditeur Cloud Shell pour mettre à jour le programme afin d'ajouter une commande query.

Commencez par ajouter un nouveau bloc de commande query dans le "Verbmap" en haut du programme, sous le bloc de commande insert existant:

    [Verb("query", HelpText = "Query players with 'Top Ten' scores within a specific timespan "
        + "from sample Cloud Spanner database table.")]
    class QueryOptions
    {
        [Value(0, HelpText = "The project ID of the project to use "
            + "when managing Cloud Spanner resources.", Required = true)]
        public string projectId { get; set; }
        [Value(1, HelpText = "The ID of the instance where the sample data resides.",
            Required = true)]
        public string instanceId { get; set; }
        [Value(2, HelpText = "The ID of the database where the sample data resides.",
            Required = true)]
        public string databaseId { get; set; }
        [Value(3, Default = 0, HelpText = "The timespan in hours that will be used to filter the "
            + "results based on a record's timestamp. The default will return the "
            + "'Top Ten' scores of all time.")]
        public int timespan { get; set; }
    }

Ajoutez ensuite les méthodes Query et QueryAsync suivantes sous la méthode InsertScoresAsync existante:

public static object Query(string projectId,
            string instanceId, string databaseId, int timespan)
        {
            var response = QueryAsync(
                projectId, instanceId, databaseId, timespan);
            response.Wait();
            return ExitCode.Success;
        }        

public static async Task QueryAsync(
            string projectId, string instanceId, string databaseId, int timespan)
        {
            string connectionString =
            $"Data Source=projects/{projectId}/instances/"
            + $"{instanceId}/databases/{databaseId}";
            // Create connection to Cloud Spanner.
            using (var connection = new SpannerConnection(connectionString))
            {
                string sqlCommand;
                if (timespan == 0)
                {
                    // No timespan specified. Query Top Ten scores of all time.
                    sqlCommand =
                        @"SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
                            FROM Players p
                            JOIN Scores s ON p.PlayerId = s.PlayerId
                            ORDER BY s.Score DESC LIMIT 10";
                }
                else
                {
                    // Query Top Ten scores filtered by the timepan specified.
                    sqlCommand =
                        $@"SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
                            FROM Players p
                            JOIN Scores s ON p.PlayerId = s.PlayerId
                            WHERE s.Timestamp >
                            TIMESTAMP_SUB(CURRENT_TIMESTAMP(),
                                INTERVAL {timespan.ToString()} HOUR)
                            ORDER BY s.Score DESC LIMIT 10";
                }
                var cmd = connection.CreateSelectCommand(sqlCommand);
                using (var reader = await cmd.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        Console.WriteLine("PlayerId : "
                          + reader.GetFieldValue<string>("PlayerId")
                          + " PlayerName : "
                          + reader.GetFieldValue<string>("PlayerName")
                          + " Score : "
                          + string.Format("{0:n0}",
                            Int64.Parse(reader.GetFieldValue<string>("Score")))
                          + " Timestamp : "
                          + reader.GetFieldValue<string>("Timestamp").Substring(0, 10));
                    }
                }
            }
        }

Ensuite, pour que la commande query fonctionne, ajoutez le code suivant à la section "Main" de votre programme méthode:

                .Add((QueryOptions opts) => Query(
                    opts.projectId, opts.instanceId, opts.databaseId, opts.timespan))

Vous pouvez utiliser le fichier Program.cs du répertoire dotnet-docs-samples/applications/leaderboard/step6 comme référence de ce à quoi doit ressembler votre fichier Program.cs après l'ajout du code pour activer la commande query.

Exécutons maintenant le programme pour vérifier que la nouvelle commande query est incluse dans la liste des commandes possibles du programme. Exécutez la commande suivante :

dotnet run

La commande query devrait désormais être incluse dans la sortie par défaut du programme en tant que nouvelle option de commande:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  No verb selected.

  create     Create a sample Cloud Spanner database along with sample 'Players' and 'Scores' tables in your project.

  insert     Insert sample 'players' records or 'scores' records into the database.

  query      Query players with 'Top Ten' scores within a specific timespan from sample Cloud Spanner database table.

  help       Display more information on a specific command.

  version    Display version information.

Exécutons maintenant la commande query pour afficher ses arguments d'entrée. Saisissez la commande suivante :

dotnet run query

Cette commande renvoie la réponse suivante:

Leaderboard 1.0.0
Copyright (C) 2018 Leaderboard

ERROR(S):
  A required value not bound to option name is missing.

  --help          Display this help screen.

  --version       Display version information.

  value pos. 0    Required. The project ID of the project to use when managing Cloud Spanner resources.

  value pos. 1    Required. The ID of the instance where the sample data resides.

  value pos. 2    Required. The ID of the database where the sample data resides.

  value pos. 3    (Default: 0) The timespan in hours that will be used to filter the results based on a record's timestamp. The default will return the 'Top Ten' scores of all time.

Dans la réponse, vous pouvez voir qu'en plus de l'ID du projet, de l'ID d'instance et de l'ID de base de données, un autre argument value pos. 3 est attendu. Il nous permet de spécifier une période en nombre d'heures à utiliser pour filtrer les enregistrements en fonction de leur valeur dans la colonne Timestamp de la table Scores. La valeur par défaut de cet argument est 0, ce qui signifie qu'aucun enregistrement n'est filtré par horodatage. Nous pouvons donc utiliser la commande query sans argument "timespan" pour obtenir la liste des joueurs du "Top 10" sur l'intégralité des données disponibles.

Exécutons la commande query sans spécifier de valeur "timespan", en utilisant les mêmes valeurs d'argument que celles utilisées lors de l'exécution de la commande create. Assurez-vous de bien remplacer PROJECT_ID par l'ID de projet que vous avez créé au début du présent atelier de programmation.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard

Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" :

PlayerId : 1843159180 PlayerName : Player 87 Score : 998,955 Timestamp : 2016-03-23
PlayerId : 61891198 PlayerName : Player 19 Score : 998,720 Timestamp : 2016-03-26
PlayerId : 340906298 PlayerName : Player 48 Score : 993,302 Timestamp : 2015-08-27
PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 857460496 PlayerName : Player 68 Score : 988,010 Timestamp : 2015-05-25
PlayerId : 1826646419 PlayerName : Player 91 Score : 984,022 Timestamp : 2016-11-26
PlayerId : 1002199735 PlayerName : Player 35 Score : 982,933 Timestamp : 2015-09-26
PlayerId : 2002563755 PlayerName : Player 23 Score : 979,041 Timestamp : 2016-10-25
PlayerId : 1377548191 PlayerName : Player 2 Score : 978,632 Timestamp : 2016-05-02
PlayerId : 1358098565 PlayerName : Player 65 Score : 973,257 Timestamp : 2016-10-30

Exécutez maintenant la commande query avec les arguments nécessaires pour obtenir les joueurs du "Top 10" de l'année en spécifiant une valeur "timespan" égale au nombre d'heures dans une année (8760). Assurez-vous de bien remplacer PROJECT_ID par l'ID de projet que vous avez créé au début du présent atelier de programmation.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 8760

Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" de l'année :

PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 228469898 PlayerName : Player 82 Score : 967,177 Timestamp : 2018-01-26
PlayerId : 1131343000 PlayerName : Player 26 Score : 944,725 Timestamp : 2017-05-26
PlayerId : 396780730 PlayerName : Player 41 Score : 929,455 Timestamp : 2017-09-26
PlayerId : 61891198 PlayerName : Player 19 Score : 921,251 Timestamp : 2018-05-01
PlayerId : 634269851 PlayerName : Player 54 Score : 909,379 Timestamp : 2017-07-24
PlayerId : 821111159 PlayerName : Player 55 Score : 908,402 Timestamp : 2017-05-25
PlayerId : 228469898 PlayerName : Player 82 Score : 889,040 Timestamp : 2017-12-26
PlayerId : 1408782275 PlayerName : Player 27 Score : 874,124 Timestamp : 2017-09-24
PlayerId : 1002199735 PlayerName : Player 35 Score : 864,758 Timestamp : 2018-04-24

À présent, vous allez exécuter la commande query pour obtenir les joueurs du "Top 10 " du mois en spécifiant une valeur "timespan" égale au nombre d'heures dans un mois (730). Assurez-vous de bien remplacer PROJECT_ID par l'ID de projet que vous avez créé au début du présent atelier de programmation.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 730

Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" du mois :

PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 61891198 PlayerName : Player 19 Score : 921,251 Timestamp : 2018-05-01
PlayerId : 1002199735 PlayerName : Player 35 Score : 864,758 Timestamp : 2018-04-24
PlayerId : 1228490432 PlayerName : Player 11 Score : 682,033 Timestamp : 2018-04-26
PlayerId : 648239230 PlayerName : Player 92 Score : 653,895 Timestamp : 2018-05-02
PlayerId : 70762849 PlayerName : Player 77 Score : 598,074 Timestamp : 2018-04-22
PlayerId : 1671215342 PlayerName : Player 62 Score : 506,770 Timestamp : 2018-04-28
PlayerId : 1208850523 PlayerName : Player 21 Score : 216,008 Timestamp : 2018-04-30
PlayerId : 1587692674 PlayerName : Player 63 Score : 188,157 Timestamp : 2018-04-25
PlayerId : 992391797 PlayerName : Player 37 Score : 167,175 Timestamp : 2018-04-30

À présent, exécutez la commande query pour obtenir les joueurs du "Top 10 " de la semaine en spécifiant une valeur "timespan" égale au nombre d'heures dans une semaine (168). Assurez-vous de bien remplacer PROJECT_ID par l'ID de projet que vous avez créé au début du présent atelier de programmation.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 168

Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" de la semaine :

PlayerId : 541473117 PlayerName : Player 22 Score : 991,368 Timestamp : 2018-04-30
PlayerId : 61891198 PlayerName : Player 19 Score : 921,251 Timestamp : 2018-05-01
PlayerId : 228469898 PlayerName : Player 82 Score : 853,602 Timestamp : 2018-04-28
PlayerId : 1131343000 PlayerName : Player 26 Score : 695,318 Timestamp : 2018-04-30
PlayerId : 1228490432 PlayerName : Player 11 Score : 682,033 Timestamp : 2018-04-26
PlayerId : 1408782275 PlayerName : Player 27 Score : 671,827 Timestamp : 2018-04-27
PlayerId : 648239230 PlayerName : Player 92 Score : 653,895 Timestamp : 2018-05-02
PlayerId : 816861444 PlayerName : Player 83 Score : 622,277 Timestamp : 2018-04-27
PlayerId : 162043954 PlayerName : Player 75 Score : 572,634 Timestamp : 2018-05-02
PlayerId : 1671215342 PlayerName : Player 62 Score : 506,770 Timestamp : 2018-04-28

Très beau travail !

Désormais, lorsque vous ajoutez des enregistrements, Spanner adapte votre base de données à la taille de votre choix.

Quelle que soit l'ampleur de la croissance de votre base de données, le classement de votre jeu peut continuer à évoluer avec précision grâce à Spanner et sa technologie Truetime.

7. Nettoyage

Après s'être amusé avec Spanner, il faut maintenant nettoyer notre aire de jeux pour ne gaspiller de préciseuses ressources et donc d'argent. Heureusement, cette étape est simple. Il vous suffit d'accéder à la Play Console et de supprimer l'instance que nous avons créée à l'étape "Configurer une instance Cloud Spanner" dans l'atelier de programmation.

8. Félicitations !

Points abordés :

  • Instances, bases de données et schéma de table Google Cloud Spanner pour un classement.
  • Créer une application de console C# .NET Core
  • Créer une base de données et des tables Spanner à l'aide de la bibliothèque cliente C#
  • Charger des données dans une base de données Spanner à l'aide de la bibliothèque cliente C#
  • Comment interroger les données "Top 10" résultats de vos données à l'aide des horodatages de commit Spanner et de la bibliothèque cliente C#

Prochaines étapes :

Votre avis nous intéresse !

  • Veuillez prendre quelques minutes pour répondre à notre courte enquête.