Cloud Spanner: Crea una tabla de clasificación de videojuegos con C#

1. Descripción general

Google Cloud Spanner es un servicio de base de datos relacional, distribuido de forma global y escalable de forma vertical completamente administrado que proporciona transacciones ACID y semántica de SQL sin renunciar al rendimiento y a la alta disponibilidad.

En este lab, aprenderás a configurar una instancia de Cloud Spanner. Completarás los pasos para crear una base de datos y un esquema que se puedan usar en una tabla de clasificación de videojuegos. Comenzarás por crear una tabla de jugadores a fin de almacenar su información y una tabla de puntuaciones para almacenar sus puntuaciones.

A continuación, propagarás las tablas con datos de muestra. Por último, finalizarás el lab mediante la ejecución de las diez consultas de muestra principales y borrarás la instancia para liberar recursos.

Qué aprenderá

  • Cómo configurar una instancia de Cloud Spanner
  • Cómo crear una base de datos y tablas.
  • Cómo usar una columna de marca de tiempo de confirmación
  • Cómo cargar datos en tu tabla de base de datos de Cloud Spanner con marcas de tiempo
  • Cómo consultar tu base de datos de Cloud Spanner.
  • Cómo borrar tu instancia de Cloud Spanner.

Qué necesitarás

¿Cómo usarás este instructivo?

Ler Leer y completar los ejercicios

¿Cómo calificarías tu experiencia en Google Cloud Platform?

Principiante Intermedio Avanzado .
.

2. Configuración y requisitos

Configuración del entorno de autoaprendizaje

Si aún no tienes una Cuenta de Google (Gmail o Google Apps), debes crear una. Accede a Google Cloud Platform Console (console.cloud.google.com) y crea un proyecto nuevo.

Si ya tienes un proyecto, haz clic en el menú desplegable de selección de proyectos en la parte superior izquierda de la Console:

6c9406d9b014760.png

y haz clic en el botón “PROYECTO NUEVO” en el diálogo resultante para crear un proyecto nuevo:

f708315ae07353d0.png

Si aún no tienes un proyecto, deberías ver un cuadro de diálogo como este para crear el primero:

870a3cbd6541ee86.png

El cuadro de diálogo de creación posterior del proyecto te permite ingresar los detalles de tu proyecto nuevo:

6a92c57d3250a4b3.png

Recuerda el ID del proyecto, que es un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se mencionará más adelante en este codelab como PROJECT_ID.

A continuación, si aún no lo has hecho, deberás habilitar la facturación en Developers Console para usar los recursos de Google Cloud y habilitar la API de Cloud Spanner.

15d0ef27a8fbab27.png

Ejecutar este codelab debería costar solo unos pocos dólares, pero su costo podría aumentar si decides usar más recursos o si los dejas en ejecución (consulta la sección “Limpiar” al final de este documento). Los precios de Google Cloud Spanner se documentan aquí.

Los usuarios nuevos de Google Cloud Platform están aptas para obtener una prueba gratuita de $300, por lo que este codelab es completamente gratuito.

Configuración de Google Cloud Shell

Si bien Google Cloud y Spanner se pueden operar de manera remota desde tu laptop, en este codelab usaremos Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

Esta máquina virtual basada en Debian está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Esto significa que todo lo que necesitarás para este Codelab es un navegador (sí, funciona en una Chromebook).

  1. Para activar Cloud Shell desde la consola de Cloud, solo haz clic en Activar Cloud ShellgcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (el aprovisionamiento y la conexión al entorno debería llevar solo unos minutos).

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

Captura de pantalla del 14 de junio de 2017 a las 10.13.43 p.m. .png

Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu PROJECT_ID:

gcloud auth list

Resultado del comando

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

Resultado del comando

[core]
project = <PROJECT_ID>

Si, por algún motivo, el proyecto no está configurado, solo emite el siguiente comando:

gcloud config set project <PROJECT_ID>

Si no conoce su PROJECT_ID, Observa el ID que usaste en los pasos de configuración o búscalo en el panel de la consola de Cloud:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell también configura algunas variables de entorno de forma predeterminada, lo que puede resultar útil cuando ejecutas comandos futuros.

echo $GOOGLE_CLOUD_PROJECT

Resultado del comando

<PROJECT_ID>
  1. Establece la zona predeterminada y la configuración del proyecto.
gcloud config set compute/zone us-central1-f

Puedes elegir una variedad de zonas diferentes. Para obtener más información, consulta Regiones y zonas.

Resumen

En este paso, configurarás tu entorno.

A continuación

A continuación, configurarás una instancia de Cloud Spanner.

3. Configura una instancia de Cloud Spanner

En este paso, configuraremos nuestra instancia de Cloud Spanner para este codelab. Busca la entrada de Spanner 1a6580bd3d3e6783.png en el menú de opciones superior izquierdo 3129589f7bc9e5ce.png o ingresa “/” y escribe “Spanner”

36e52f8df8e13b99.png

A continuación, haz clic en 95269e75bc8c3e4d.png y completa el formulario. Para ello, ingresa el nombre de la instancia cloudspanner-leaderboard de la instancia, elige una configuración (selecciona una instancia regional) y establece la cantidad de nodos. Para este codelab solo necesitaremos 1 nodo. Para las instancias de producción y a fin de cumplir con los requisitos del ANS de Cloud Spanner, debes ejecutar 3 o más nodos en tu instancia de Cloud Spanner.

Por último, pero no menos importante, haz clic en “Crear” y, en segundos, selecciona una instancia de Cloud Spanner a su disposición.

dceb68e9ed3801e8.png

En el siguiente paso, usaremos la biblioteca cliente de C# para crear una base de datos y un esquema en nuestra nueva instancia.

4. Crea una base de datos y un esquema

En este paso, crearemos nuestra base de datos de muestra y un esquema.

Usemos la biblioteca cliente de C# para crear dos tablas: una tabla de jugadores para la información de los jugadores y una tabla de puntuaciones para almacenar las puntuaciones de los jugadores. Para ello, repasaremos los pasos que te permitirán crear una aplicación de consola de C# en Cloud Shell.

Primero, clona el código de muestra para este codelab desde GitHub. Para ello, escribe el siguiente comando en Cloud Shell:

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

Luego, cambia el directorio al directorio “aplicaciones” en el que crearás tu aplicación.

cd dotnet-docs-samples/applications/

Todo el código necesario para este codelab se encuentra en el directorio dotnet-docs-samples/applications/leaderboard existente como una aplicación ejecutable C# llamada Leaderboard que sirve como referencia a medida que avanzas a través del codelab. Crearemos un directorio nuevo y compilaremos una copia de la aplicación Tablas de clasificación en etapas.

Crea un nuevo directorio llamado “codelab” para la aplicación y usa el comando de cambio de directorio a este con el siguiente comando:

mkdir codelab && cd $_

Crea una nueva aplicación de la consola .NET C# llamada "Leaderboard" con el siguiente comando:

dotnet new console -n Leaderboard

Este comando crea una aplicación de consola simple que consta de dos archivos principales: el archivo de proyecto Leaderboard.csproj y el archivo de programa Program.cs.

Ejecutémosla. Cambia el directorio al directorio de la tabla de clasificación recién creado en el que reside la aplicación:

cd Leaderboard

Luego, ingresa el siguiente comando para ejecutarlo.

dotnet run

Deberías ver el resultado de la aplicación “Hello World!”.

Ahora, actualicemos nuestra app de consola editando Program.cs para usar la biblioteca cliente de C# Spanner a fin de crear una tabla de clasificación con dos tablas: Jugadores y puntuaciones. Puedes hacerlo directamente en el editor de Cloud Shell:

Haz clic en el siguiente ícono destacado para abrir el editor de Cloud Shell:

73cf70e05f653ca.png

A continuación, abre el archivo Program.cs en el editor de Cloud Shell y reemplaza el código existente del archivo por el código requerido para crear la base de datos leaderboard y las tablas Players y Scores. Para ello, pega el siguiente código de la aplicación C# en el archivo 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);
        }
    }
}

Para proporcionar una imagen más clara del código del Programa, a continuación se incluye un diagrama del Programa con sus componentes principales etiquetados:

b70b1b988ea3ac8a.png

Puedes usar el archivo Program.cs en el directorio dotnet-docs-samples/applications/leaderboard/step4 si deseas ver un ejemplo de cómo debe verse tu archivo Program.cs después de agregar el código para habilitar el comando create.

A continuación, usa el editor de Cloud Shell para abrir y editar el archivo de proyecto del programa Leaderboard.csproj, y actualízalo para que se vea como el siguiente código. Asegúrese de guardar todos los cambios con la opción "Archivo" de Cloud Shell Editor.

<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>

Este cambio agregó una referencia al paquete Google.Cloud.Spanner.Data de Nuget de Spanner de C# que necesitamos para interactuar con la API de Cloud Spanner. Este cambio también agrega una referencia al proyecto CommandLineUtil, que forma parte del repositorio dotnet-doc-samples de GitHub y proporciona un "mapa de verbo" útil extensión para el CommandLineParser de código abierto una biblioteca práctica para manejar la entrada de la línea de comandos para aplicaciones de consola.

Puedes usar el archivo Leaderboard.csproj en el directorio dotnet-docs-samples/applications/leaderboard/step4 si deseas ver un ejemplo de cómo debe verse tu archivo Leaderboard.csproj después de agregar el código para habilitar el comando create.

Ya está todo listo para ejecutar la muestra actualizada. Escribe lo siguiente para ver la respuesta predeterminada de tu aplicación actualizada:

dotnet run

Deberías ver un resultado como el siguiente:

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.

En esta respuesta, podemos ver que esta es la aplicación Leaderboard que se puede ejecutar con uno de tres comandos posibles: create, help y version.

Probemos el comando create para crear una base de datos y tablas de Spanner. Ejecuta el comando sin argumentos para ver los argumentos esperados.

dotnet run create

Deberías ver una respuesta como la siguiente:

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.

Aquí podemos ver que los argumentos esperados del comando create son ID del proyecto, ID de instancia y ID de base de datos.

Ahora, ejecuta el siguiente comando: Asegúrate de reemplazar el PROJECT_ID por el ID del proyecto que creaste al comienzo de este codelab.

dotnet run create PROJECT_ID cloudspanner-leaderboard leaderboard

Después de unos segundos, deberías ver una respuesta como la siguiente:

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

En la sección de Cloud Spanner de la consola de Cloud, deberías ver su nueva base de datos y tablas en el menú del lado izquierdo.

ba9008bb84cb90b0.png

En el próximo paso, actualizaremos nuestra aplicación para cargar algunos datos en tu base de datos nueva.

5. Cargar datos

Ahora tenemos una base de datos llamada leaderboard que contiene dos tablas, Players y Scores. Ahora, usemos la biblioteca cliente de C# para propagar la tabla Players con jugadores y la tabla Scores con puntuaciones aleatorias para cada jugador.

Haz clic en el siguiente ícono destacado para abrir el editor de Cloud Shell:

4d17840699d8e7ce.png

A continuación, edita el archivo Program.cs en el editor de Cloud Shell a fin de agregar un comando insert que se pueda usar para insertar 100 jugadores a la tabla Players o se puede usar a fin de insertar 4 puntuaciones aleatorias en el Scores de cada jugador en la tabla Players.

Primero, agrega un nuevo bloque de comandos insert en el “Verbmap”. en la parte superior del programa, debajo del bloque de comando create existente:

[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; }
    }

Luego, agrega los siguientes métodos Insert, InsertPlayersAsync y InsertScoresAsync debajo del método CreateAsync existente:

        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..."
                            );
                        }
                    }
                });
            }
        }

Luego, para que el comando insert funcione, agrega el siguiente código al elemento "Main" de tu programa. método:

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

Puedes usar el archivo Program.cs en el directorio dotnet-docs-samples/applications/leaderboard/step5 si deseas ver un ejemplo de cómo debe verse tu archivo Program.cs después de agregar el código para habilitar el comando insert.

Ahora ejecutemos el programa para confirmar que el nuevo comando insert esté incluido en la lista de comandos posibles del programa. Ejecuta el siguiente comando:

dotnet run

Deberías ver el comando insert ahora incluido en el resultado predeterminado del programa:

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.

Ahora ejecutemos el comando insert para ver sus argumentos de entrada. Escribe el comando siguiente.

dotnet run insert

Esto debería mostrar la siguiente respuesta:

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'.

Puedes ver en la respuesta que, además del ID del proyecto, el ID de instancia y el ID de la base de datos, hay otro argumento value pos. 3 esperado, que es el "tipo de inserción" para realizar. Este argumento puede tener un valor de "jugadores" o 'puntuaciones'.

Ahora ejecutemos el comando insert con los mismos valores de argumento que usamos cuando llamamos al comando create, y agreguemos “jugadores” como el argumento adicional “tipo de inserción”. Asegúrate de reemplazar el PROJECT_ID por el ID del proyecto que creaste al comienzo de este codelab.

dotnet run insert PROJECT_ID cloudspanner-leaderboard leaderboard players

Después de unos segundos, deberías ver una respuesta como la siguiente:

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

Ahora, usemos la biblioteca cliente de C# para propagar la tabla Scores con cuatro puntuaciones aleatorias junto con marcas de tiempo para cada jugador de la tabla Players.

La columna Timestamp de la tabla Scores se definió como una columna de “marca de tiempo de confirmación” a través de la siguiente instrucción de SQL que se ejecutaba cuando ejecutamos el comando 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

Observa el atributo OPTIONS(allow_commit_timestamp=true). Esto hace que Timestamp sea una columna de “marca de tiempo de confirmación” y permite que se propague automáticamente con la marca de tiempo exacta de la transacción para operaciones INSERTAR y ACTUALIZAR en una fila de tabla determinada.

También puedes insertar tus propios valores de marca de tiempo en una columna “marca de tiempo de confirmación”, siempre y cuando insertes una marca de tiempo con un valor anterior, lo que haremos para este codelab.

Ahora, ejecutemos el comando insert con los mismos valores de argumento que usamos cuando llamamos al comando create y agregamos “puntuaciones” como el argumento adicional “tipo de inserción”. Asegúrate de reemplazar el PROJECT_ID por el ID del proyecto que creaste al comienzo de este codelab.

dotnet run insert PROJECT_ID cloudspanner-leaderboard leaderboard scores

Después de unos segundos, deberías ver una respuesta como la siguiente:

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

La ejecución de insert con el “tipo de inserción” especificado como scores llama al método InsertScoresAsync, que usa los siguientes fragmentos de código para insertar una marca de tiempo generada de forma aleatoria con una fecha y hora en el pasado:

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");

Para propagar automáticamente la columna Timestamp con la marca de tiempo del momento exacto en que aparece el elemento “Insertar” cuando se realiza la transacción, puedes insertar la constante SpannerParameter.CommitTimestamp de C# como en el siguiente fragmento de código:

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

Ahora que terminamos de cargar los datos, verifiquemos los valores que acabamos de escribir en nuestras tablas nuevas. Primero, selecciona la base de datos leaderboard y, luego, la tabla Players. Haz clic en la pestaña Data. Deberías ver que tienes datos en las columnas PlayerId y PlayerName de la tabla.

7bc2c96293c31c49.png

A continuación, verifiquemos la tabla de puntuaciones. Para ello, haz clic en la tabla Scores y selecciona la pestaña Data. Deberías ver que tiene datos en las columnas PlayerId, Timestamp y Score de la tabla.

d8a4ee4f13244c19.png

¡Bien hecho! Actualicemos nuestro programa para ejecutar algunas consultas que podamos usar para crear una tabla de clasificación de videojuegos.

6. Ejecuta consultas sobre tablas de clasificación

Ahora que configuramos nuestra base de datos y cargamos información en nuestras tablas, vamos a crear una tabla de clasificación con estos datos. Para ello, debemos responder las siguientes cuatro preguntas:

  1. ¿Cuáles son los diez principales jugadores de todos los tiempos?
  2. ¿Cuáles son los diez jugadores principales de este año?
  3. ¿Cuáles son los diez jugadores principales del mes?
  4. ¿Cuáles son los diez jugadores principales de la semana?

Actualicemos nuestro programa para ejecutar las consultas en SQL que responderán estas preguntas.

Agregaremos un comando query que proporcionará una manera de ejecutar las consultas a fin de responder las preguntas que producirán la información requerida en nuestra tabla de clasificación.

Edita el archivo Program.cs en el editor de Cloud Shell para actualizar el programa y agregar un comando query.

Primero, agrega un nuevo bloque de comandos query en el “Verbmap”. en la parte superior del programa, debajo del bloque de comando insert existente:

    [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; }
    }

Luego, agrega los siguientes métodos Query y QueryAsync debajo del método InsertScoresAsync existente:

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));
                    }
                }
            }
        }

Luego, para que el comando query funcione, agrega el siguiente código al elemento "Main" de tu programa. método:

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

Puedes usar el archivo Program.cs en el directorio dotnet-docs-samples/applications/leaderboard/step6 si deseas ver un ejemplo de cómo debe verse tu archivo Program.cs después de agregar el código para habilitar el comando query.

Ahora ejecutemos el programa para confirmar que el nuevo comando query esté incluido en la lista de comandos posibles del programa. Ejecuta el siguiente comando:

dotnet run

Deberías ver el comando query ahora incluido en el resultado predeterminado del programa como una nueva opción de comando:

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.

Ahora ejecutemos el comando query para ver sus argumentos de entrada. Ingresa el siguiente comando:

dotnet run query

Se mostrará la siguiente respuesta:

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.

Como puedes ver en la respuesta, además del ID del proyecto, el ID de instancia y el ID de la base de datos, se espera otro argumento value pos. 3 que nos permite especificar un período en la cantidad de horas que se debe usar para filtrar registros según su valor en la columna Timestamp de la tabla Scores. Este argumento tiene un valor predeterminado de 0, lo que significa que no se filtrará ningún registro por marcas de tiempo. Por lo tanto, podemos usar el comando query sin un valor de “intervalo” para obtener una lista de los “diez mejores” jugadores de todos los tiempos.

Ejecutamos el comando query sin especificar un “período” con los mismos valores de argumento que usamos cuando ejecutamos el comando create. Asegúrate de reemplazar el PROJECT_ID por el ID del proyecto que creaste al comienzo de este codelab.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard

Deberías ver una respuesta que incluya los “diez mejores” jugadores de todos los tiempos, como se indica a continuación:

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

Ahora, ejecutemos el comando query con los argumentos necesarios a fin de consultar los “diez mejores” jugadores del año. Para ello, especifica un “intervalo” igual a la cantidad de horas en un año, que es 8,760. Asegúrate de reemplazar el PROJECT_ID por el ID del proyecto que creaste al comienzo de este codelab.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 8760

Deberías ver una respuesta que incluya a los “diez mejores” jugadores del año, como se muestra a continuación:

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

Ahora, ejecutemos el comando query a fin de consultar a los “diez mejores” jugadores del mes. Para ello, especifica un “intervalo” igual a la cantidad de horas de un mes, que es 730. Asegúrate de reemplazar el PROJECT_ID por el ID del proyecto que creaste al comienzo de este codelab.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 730

Deberías ver una respuesta que incluya a los “diez mejores” jugadores del mes, como se muestra a continuación:

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

Ahora, ejecutemos el comando query a fin de consultar a los “diez mejores” jugadores de la semana. Para ello, especifica un “intervalo” igual a la cantidad de horas de una semana, que es 168. Asegúrate de reemplazar el PROJECT_ID por el ID del proyecto que creaste al comienzo de este codelab.

dotnet run query PROJECT_ID cloudspanner-leaderboard leaderboard 168

Deberías ver una respuesta que incluya a los “diez mejores” jugadores de la semana, como se muestra a continuación:

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

¡Buen trabajo!

Ahora, a medida que agregues registros, Spanner escalará tu base de datos al tamaño que necesites.

Sin importar cuánto crezca tu base de datos, la tabla de clasificación de tu juego puede seguir escalando con precisión gracias a Spanner y su tecnología de Truetime.

7. Limpieza

Después de la diversión cuando se juega con Spanner, debemos limpiar nuestro zona de prueba para ahorrar dinero y recursos valiosos. Por suerte, este es un paso sencillo. Solo tienes que ir a Play Console y borrar la instancia que creamos en el paso del codelab llamado “Configura una instancia de Cloud Spanner”.

8. ¡Felicitaciones!

Temas abordados:

  • Instancias, bases de datos y esquemas de tablas de Google Cloud Spanner para una tabla de clasificación
  • Cómo crear una aplicación de consola de C# con .NET Core
  • Cómo crear una base de datos y tablas de Spanner con la biblioteca cliente de C#
  • Cómo cargar datos en una base de datos de Spanner con la biblioteca cliente de C#
  • Cómo buscar los "diez principales" resultados de tus datos con las marcas de tiempo de confirmación de Spanner y la biblioteca cliente de C#

Próximos pasos:

Envíanos tus comentarios

  • Tómate un momento para completar nuestra breve encuesta