Empezar a usar Spanner en C#


Objetivos

En este tutorial se explican los siguientes pasos para usar la biblioteca de cliente de Spanner para C#:

  • Crea una instancia y una base de datos de Spanner.
  • Escribir, leer y ejecutar consultas SQL sobre datos en la base de datos.
  • Actualizar el esquema de la base de datos.
  • Actualizar datos mediante una transacción de lectura y escritura.
  • Agregar un índice secundario a la base de datos.
  • Usar el índice para leer los datos y ejecutar consultas SQL sobre ellos.
  • Recuperar datos mediante una transacción de solo lectura.

Costes

En este tutorial se usa Spanner, que es un componente facturable deGoogle Cloud. Para obtener información sobre el coste de usar Spanner, consulta la página Precios.

Antes de empezar

Sigue los pasos que se describen en la sección Configuración, donde se explica cómo crear y definir un proyecto predeterminado Google Cloud , habilitar la facturación y la API Cloud Spanner, y configurar OAuth 2.0 para obtener las credenciales de autenticación que se usarán con la API Cloud Spanner.

En concreto, asegúrate de ejecutar gcloud auth application-default login para configurar tu entorno de desarrollo local con credenciales de autenticación.

Preparar el entorno local de C#

  1. Asigna a la variable de entorno GOOGLE_PROJECT_ID el ID de tu proyecto Google Cloud .

    1. Primero, define GOOGLE_PROJECT_ID para la sesión de PowerShell actual:

      $env:GOOGLE_PROJECT_ID = "MY_PROJECT_ID"
    2. A continuación, define GOOGLE_PROJECT_ID para todos los procesos creados después de este comando:

      [Environment]::SetEnvironmentVariable("GOOGLE_PROJECT_ID", "MY_PROJECT_ID", "User")
  2. Descarga las credenciales.

    1. Ve a la página Credenciales de la consola de Google Cloud .

      Ir a la página Credenciales

    2. Haz clic en Crear credenciales y elige Clave de cuenta de servicio.

    3. En "Cuenta de servicio", elige Cuenta de servicio predeterminada de Compute Engine y deja seleccionada la opción JSON en "Tipo de clave". Haz clic en Crear. Tu ordenador descargará un archivo JSON.

  3. Configura las credenciales. Para un archivo llamado FILENAME.json en el directorio CURRENT_USER de descargas, ubicado en la unidad C, ejecuta los siguientes comandos para definir GOOGLE_APPLICATION_CREDENTIALS de forma que apunte a la clave JSON:

    1. Primero, para definir GOOGLE_APPLICATION_CREDENTIALS en esta sesión de PowerShell:

      $env:GOOGLE_APPLICATION_CREDENTIALS = "C:\Users\CURRENT_USER\Downloads\FILENAME.json"
    2. A continuación, para definir GOOGLE_APPLICATION_CREDENTIALS en todos los procesos creados después de este comando:

      [Environment]::SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "C:\Users\CURRENT_USER\Downloads\FILENAME.json", "User")
  4. Clona el repositorio de aplicaciones de muestra en la máquina local:

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

    También puedes descargar el ejemplo como archivo ZIP y extraerlo.

  5. Abre Spanner.sln, que se encuentra en el directorio dotnet-docs-samples\spanner\api del repositorio descargado, con Visual Studio 2017 o una versión posterior y, a continuación, compílalo.

  6. Cambia al directorio del repositorio descargado que contiene la aplicación compilada. Por ejemplo:

    cd dotnet-docs-samples\spanner\api\Spanner
    

Crear una instancia

La primera vez que uses Spanner, debes crear una instancia, que es una asignación de recursos que utilizan las bases de datos de Spanner. Cuando creas una instancia, tienes que elegir una configuración de instancia, que determina dónde se almacenan tus datos y la cantidad de nodos que se van a usar, lo que permite conocer la cantidad de recursos de almacenamiento y publicación de la instancia.

Consulta Crear una instancia para saber cómo crear una instancia de Spanner con cualquiera de los siguientes métodos. Puedes llamar a tu instancia test-instance para usarla con otros temas de este documento que hagan referencia a una instancia llamada test-instance.

  • Google Cloud CLI
  • La Google Cloud consola
  • Una biblioteca de cliente (C++, C#, Go, Java, Node.js, PHP, Python o Ruby)

Consultar los archivos de muestra

El repositorio de ejemplos contiene un ejemplo que muestra cómo usar Spanner con C#.

Consulta el repositorio de GitHub de .NET de Spanner, donde se explica cómo crear una base de datos y modificar un esquema de base de datos. Los datos usan el esquema de ejemplo que se muestra en la página Esquema y modelo de datos.

Crear una base de datos

Deberías ver lo siguiente:

El siguiente código crea una base de datos y dos tablas en ella.

GoogleSQL


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

public class CreateDatabaseAsyncSample
{
    public async Task CreateDatabaseAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}";

        using var connection = new SpannerConnection(connectionString);
        var createDatabase = $"CREATE DATABASE `{databaseId}`";
        // Define create table statement for table #1.
        var createSingersTable =
            @"CREATE TABLE Singers (
                SingerId INT64 NOT NULL,
                FirstName STRING(1024),
                LastName STRING(1024),
                ComposerInfo BYTES(MAX),
                FullName STRING(2048) AS (ARRAY_TO_STRING([FirstName, LastName], "" "")) STORED
            ) PRIMARY KEY (SingerId)";
        // Define create table statement for table #2.
        var createAlbumsTable =
            @"CREATE TABLE Albums (
                SingerId INT64 NOT NULL,
                AlbumId INT64 NOT NULL,
                AlbumTitle STRING(MAX)
            ) PRIMARY KEY (SingerId, AlbumId),
            INTERLEAVE IN PARENT Singers ON DELETE CASCADE";

        using var createDbCommand = connection.CreateDdlCommand(createDatabase, createSingersTable, createAlbumsTable);
        await createDbCommand.ExecuteNonQueryAsync();
    }
}

PostgreSQL


using Google.Cloud.Spanner.Admin.Database.V1;
using Google.Cloud.Spanner.Common.V1;
using System;
using System.Threading.Tasks;

public class CreateDatabaseAsyncPostgresSample
{
    public async Task CreateDatabaseAsyncPostgres(string projectId, string instanceId, string databaseId)
    {
        DatabaseAdminClient databaseAdminClient = await DatabaseAdminClient.CreateAsync();

        // Create the CreateDatabaseRequest with PostgreSQL dialect and execute it.
        // There cannot be Extra DDL statements while creating PostgreSQL.
        var createDatabaseRequest = new CreateDatabaseRequest
        {
            ParentAsInstanceName = InstanceName.FromProjectInstance(projectId, instanceId),
            CreateStatement = $"CREATE DATABASE \"{databaseId}\"",
            DatabaseDialect = DatabaseDialect.Postgresql
        };

        var createOperation = await databaseAdminClient.CreateDatabaseAsync(createDatabaseRequest);

        // Wait until the operation has finished.
        Console.WriteLine("Waiting for the database to be created.");
        var completedResponse = await createOperation.PollUntilCompletedAsync();
        if (completedResponse.IsFaulted)
        {
            Console.WriteLine($"Error while creating PostgreSQL database: {completedResponse.Exception}");
            throw completedResponse.Exception;
        }

        // PostgreSQL Database is created. Now, we can create the tables.
        // Define create table statement for table #1 in PostgreSQL syntax.
        var createSingersTable = @"CREATE TABLE Singers (
            SingerId bigint NOT NULL PRIMARY KEY,
            FirstName varchar(1024),
            LastName varchar(1024),
            Rating numeric,
            SingerInfo bytea,
            FullName character varying(2048) GENERATED ALWAYS AS (FirstName || ' ' || LastName) STORED)";

        // Define create table statement for table #2 in PostgreSQL syntax.
        var createAlbumsTable = @"CREATE TABLE Albums (
            AlbumId bigint NOT NULL PRIMARY KEY,
            SingerId bigint NOT NULL REFERENCES Singers (SingerId),
            AlbumTitle text,
            MarketingBudget BIGINT)";

        DatabaseName databaseName = DatabaseName.FromProjectInstanceDatabase(projectId, instanceId, databaseId);

        // Create UpdateDatabaseRequest to create the tables. 
        var updateDatabaseRequest = new UpdateDatabaseDdlRequest
        {
            DatabaseAsDatabaseName = databaseName,
            Statements = { createSingersTable, createAlbumsTable }
        };

        var updateOperation = await databaseAdminClient.UpdateDatabaseDdlAsync(updateDatabaseRequest);
        // Wait until the operation has finished.
        Console.WriteLine("Waiting for the tables to be created.");
        var updateResponse = await updateOperation.PollUntilCompletedAsync();
        if (updateResponse.IsFaulted)
        {
            Console.WriteLine($"Error while updating database: {updateResponse.Exception}");
            throw updateResponse.Exception;
        }
    }
}

El siguiente paso consiste en escribir datos en la base de datos.

Crear un cliente de base de datos

Antes de poder leer o escribir, debes crear un SpannerConnection:


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

namespace GoogleCloudSamples.Spanner
{
    public class QuickStart
    {
        static async Task MainAsync()
        {
            string projectId = "YOUR-PROJECT-ID";
            string instanceId = "my-instance";
            string databaseId = "my-database";
            string connectionString =
                $"Data Source=projects/{projectId}/instances/{instanceId}/"
                + $"databases/{databaseId}";
            // Create connection to Cloud Spanner.
            using (var connection = new SpannerConnection(connectionString))
            {
                // Execute a simple SQL statement.
                var cmd = connection.CreateSelectCommand(
                    @"SELECT ""Hello World"" as test");
                using (var reader = await cmd.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        Console.WriteLine(
                            reader.GetFieldValue<string>("test"));
                    }
                }
            }
        }
        public static void Main(string[] args)
        {
            MainAsync().Wait();
        }
    }
}

Puedes considerar un SpannerConnection como una conexión de base de datos: todas tus interacciones con Spanner deben pasar por un SpannerConnection.

Consulte más información en la referencia de SpannerConnection.

Escribir datos con DML

Puedes insertar datos mediante el lenguaje de manipulación de datos (DML) en una transacción de lectura y escritura.

Utilizas el método ExecuteNonQueryAsync() para ejecutar una instrucción DML.


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

public class WriteUsingDmlCoreAsyncSample
{
    public async Task<int> WriteUsingDmlCoreAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        SpannerCommand cmd = connection.CreateDmlCommand(
            "INSERT Singers (SingerId, FirstName, LastName) VALUES "
               + "(12, 'Melissa', 'Garcia'), "
               + "(13, 'Russell', 'Morales'), "
               + "(14, 'Jacqueline', 'Long'), "
               + "(15, 'Dylan', 'Shaw')");
        int rowCount = await cmd.ExecuteNonQueryAsync();

        Console.WriteLine($"{rowCount} row(s) inserted...");
        return rowCount;
    }
}

Ejecuta la muestra con el argumento writeUsingDml.

dotnet run writeUsingDml $env:GOOGLE_PROJECT_ID test-instance example-db

Deberías ver lo siguiente:

4 row(s) inserted...

Escribir datos con mutaciones

También puedes insertar datos mediante mutaciones.

Puedes insertar datos con el método connection.CreateInsertCommand(), que crea un nuevo SpannerCommand para insertar filas en una tabla. El método SpannerCommand.ExecuteNonQueryAsync() añade filas a la tabla.

Este código muestra cómo insertar datos mediante mutaciones:


using Google.Cloud.Spanner.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public class InsertDataAsyncSample
{
    public class Singer
    {
        public int SingerId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task InsertDataAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        List<Singer> singers = new List<Singer>
        {
            new Singer { SingerId = 1, FirstName = "Marc", LastName = "Richards" },
            new Singer { SingerId = 2, FirstName = "Catalina", LastName = "Smith" },
            new Singer { SingerId = 3, FirstName = "Alice", LastName = "Trentor" },
            new Singer { SingerId = 4, FirstName = "Lea", LastName = "Martin" },
            new Singer { SingerId = 5, FirstName = "David", LastName = "Lomond" },
        };
        List<Album> albums = new List<Album>
        {
            new Album { SingerId = 1, AlbumId = 1, AlbumTitle = "Total Junk" },
            new Album { SingerId = 1, AlbumId = 2, AlbumTitle = "Go, Go, Go" },
            new Album { SingerId = 2, AlbumId = 1, AlbumTitle = "Green" },
            new Album { SingerId = 2, AlbumId = 2, AlbumTitle = "Forever Hold your Peace" },
            new Album { SingerId = 2, AlbumId = 3, AlbumTitle = "Terrified" },
        };

        // Create connection to Cloud Spanner.
        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        await connection.RunWithRetriableTransactionAsync(async transaction =>
        {
            await Task.WhenAll(singers.Select(singer =>
            {
                // Insert rows into the Singers table.
                using var cmd = connection.CreateInsertCommand("Singers", new SpannerParameterCollection
                {
                        { "SingerId", SpannerDbType.Int64, singer.SingerId },
                        { "FirstName", SpannerDbType.String, singer.FirstName },
                        { "LastName", SpannerDbType.String, singer.LastName }
                });
                cmd.Transaction = transaction;
                return cmd.ExecuteNonQueryAsync();
            }));

            await Task.WhenAll(albums.Select(album =>
            {
                // Insert rows into the Albums table.
                using var cmd = connection.CreateInsertCommand("Albums", new SpannerParameterCollection
                {
                        { "SingerId", SpannerDbType.Int64, album.SingerId },
                        { "AlbumId", SpannerDbType.Int64, album.AlbumId },
                        { "AlbumTitle", SpannerDbType.String,album.AlbumTitle }
                });
                cmd.Transaction = transaction;
                return cmd.ExecuteNonQueryAsync();
            }));
        });
        Console.WriteLine("Data inserted.");
    }
}

Ejecuta la muestra con el argumento insertSampleData.

dotnet run insertSampleData $env:GOOGLE_PROJECT_ID test-instance example-db

Deberías ver lo siguiente:

Inserted data.

Consultar datos mediante SQL

Spanner admite una interfaz SQL para leer datos, a la que puedes acceder en la línea de comandos mediante la CLI de Google Cloud o de forma programática con la biblioteca de cliente de Spanner para C#.

En la línea de comandos

Ejecuta la siguiente instrucción SQL para leer los valores de todas las columnas de la tabla Albums:

gcloud spanner databases execute-sql example-db --instance=test-instance `
    --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Albums'

El resultado muestra lo siguiente:

SingerId AlbumId AlbumTitle
1        1       Total Junk
1        2       Go, Go, Go
2        1       Green
2        2       Forever Hold Your Peace
2        3       Terrified

Usar la biblioteca de cliente de Spanner para C#

Además de ejecutar una instrucción SQL en la línea de comandos, puedes emitir la misma instrucción SQL de forma programática mediante la biblioteca de cliente de Spanner para C#.

Usa ExecuteReaderAsync() para ejecutar la consulta de SQL.


using Google.Cloud.Spanner.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

public class QuerySampleDataAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task<List<Album>> QuerySampleDataAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        var albums = new List<Album>();
        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand("SELECT SingerId, AlbumId, AlbumTitle FROM Albums");

        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
            });
        }
        return albums;
    }
}

A continuación, se indica cómo emitir la consulta y acceder a los datos:

dotnet run querySampleData $env:GOOGLE_PROJECT_ID test-instance example-db

El resultado debe ser el siguiente:

SingerId: 1 AlbumId: 1 AlbumTitle: Total Junk
SingerId: 1 AlbumId: 2 AlbumTitle: Go, Go, Go
SingerId: 2 AlbumId: 1 AlbumTitle: Green
SingerId: 2 AlbumId: 2 AlbumTitle: Forever Hold your Peace
SingerId: 2 AlbumId: 3 AlbumTitle: Terrified

Consultar usando un parámetro de SQL

Si tu aplicación tiene una consulta que se ejecuta con frecuencia, puedes mejorar su rendimiento parametrizándola. La consulta paramétrica resultante se puede almacenar en caché y reutilizar, lo que reduce los costes de compilación. Para obtener más información, consulta Usar parámetros de consulta para acelerar las consultas que se ejecutan con frecuencia.

Aquí tienes un ejemplo de cómo usar un parámetro en la cláusula WHERE para consultar registros que contengan un valor específico de LastName.


using Google.Cloud.Spanner.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

public class QueryWithParameterAsyncSample
{
    public class Singer
    {
        public int SingerId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public async Task<List<Singer>> QueryWithParameterAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand(
            $"SELECT SingerId, FirstName, LastName FROM Singers WHERE LastName = @lastName",
            new SpannerParameterCollection { { "lastName", SpannerDbType.String, "Garcia" } });

        var singers = new List<Singer>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            singers.Add(new Singer
            {
                SingerId = reader.GetFieldValue<int>("SingerId"),
                FirstName = reader.GetFieldValue<string>("FirstName"),
                LastName = reader.GetFieldValue<string>("LastName")
            });
        }
        return singers;
    }
}

A continuación, te indicamos cómo enviar la consulta con un parámetro y acceder a los datos:

dotnet run queryWithParameter $env:GOOGLE_PROJECT_ID test-instance example-db

El resultado debe ser el siguiente:

SingerId : 12 FirstName : Melissa LastName : Garcia

Actualizar el esquema de la base de datos

Supongamos que quiere añadir una nueva columna llamada MarketingBudget a la tabla Albums. Para agregar una nueva columna a una tabla existente, es preciso actualizar el esquema de base de datos. Spanner admite actualizaciones de esquemas en una base de datos mientras esta sigue atendiendo tráfico. Para actualizar el esquema, no es necesario desconectar la base de datos y no se bloquean tablas ni columnas completas. Puedes seguir escribiendo datos en la base de datos durante la actualización del esquema. Consulta más información sobre las actualizaciones de esquemas y el rendimiento de los cambios de esquemas admitidos en el artículo Hacer actualizaciones de esquemas.

Añadir una columna

Puedes añadir una columna en la línea de comandos con la CLI de Google Cloud o de forma programática con la biblioteca de cliente de Spanner para C#.

En la línea de comandos

Usa el siguiente comando ALTER TABLE para añadir la nueva columna a la tabla:

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget INT64'

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT'

Deberías ver lo siguiente:

Schema updating...done.

Usar la biblioteca de cliente de Spanner para C#

Usa CreateDdlCommand() para modificar el esquema:


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

public class AddColumnAsyncSample
{
    public async Task AddColumnAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string alterStatement = "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64";

        using var connection = new SpannerConnection(connectionString);
        using var updateCmd = connection.CreateDdlCommand(alterStatement);
        await updateCmd.ExecuteNonQueryAsync();
        Console.WriteLine("Added the MarketingBudget column.");
    }
}

Ejecuta el ejemplo con el comando addColumn.

dotnet run addColumn $env:GOOGLE_PROJECT_ID test-instance example-db

Deberías ver lo siguiente:

Added the MarketingBudget column.

Escribir datos en la nueva columna

El siguiente código sirve para escribir datos en la nueva columna. Define MarketingBudget como 100000 en la fila con la clave Albums(1, 1) y como 500000 en la fila con la clave Albums(2, 2).


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

public class UpdateDataAsyncSample
{
    public async Task<int> UpdateDataAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);

        var rowCount = 0;
        SpannerCommand cmd = connection.CreateDmlCommand(
            "UPDATE Albums SET MarketingBudget = @MarketingBudget "
            + "WHERE SingerId = 1 and AlbumId = 1");
        cmd.Parameters.Add("MarketingBudget", SpannerDbType.Int64, 100000);
        rowCount += await cmd.ExecuteNonQueryAsync();

        cmd = connection.CreateDmlCommand(
            "UPDATE Albums SET MarketingBudget = @MarketingBudget "
            + "WHERE SingerId = 2 and AlbumId = 2");
        cmd.Parameters.Add("MarketingBudget", SpannerDbType.Int64, 500000);
        rowCount += await cmd.ExecuteNonQueryAsync();

        Console.WriteLine("Data Updated.");
        return rowCount;
    }
}

Ejecuta el ejemplo con el comando writeDataToNewColumn.

dotnet run writeDataToNewColumn $env:GOOGLE_PROJECT_ID test-instance example-db

Deberías ver lo siguiente:

Updated data.

También puedes ejecutar una consulta de SQL para obtener los valores que acabas de escribir.

Aquí está el código para ejecutar la consulta:


using Google.Cloud.Spanner.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

public class QueryNewColumnAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public long MarketingBudget { get; set; }
    }

    public async Task<List<Album>> QueryNewColumnAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        var albums = new List<Album>();
        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand("SELECT * FROM Albums");
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                MarketingBudget = reader.IsDBNull(reader.GetOrdinal("MarketingBudget")) ? 0 : reader.GetFieldValue<long>("MarketingBudget")
            });
        }
        return albums;
    }
}

Para ejecutar esta consulta, ejecuta la muestra con el argumento queryNewColumn.

dotnet run queryNewColumn $env:GOOGLE_PROJECT_ID test-instance example-db

Deberías ver lo siguiente:

SingerId : 1 AlbumId : 1 MarketingBudget : 100000
SingerId : 1 AlbumId : 2 MarketingBudget :
SingerId : 2 AlbumId : 1 MarketingBudget :
SingerId : 2 AlbumId : 2 MarketingBudget : 500000
SingerId : 2 AlbumId : 3 MarketingBudget :

Actualizar datos

Puedes actualizar datos mediante DML en una transacción de lectura y escritura.

Utilizas el método ExecuteNonQueryAsync() para ejecutar una instrucción DML.


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

public class WriteWithTransactionUsingDmlCoreAsyncSample
{
    public async Task<int> WriteWithTransactionUsingDmlCoreAsync(string projectId, string instanceId, string databaseId)
    {
        // This sample transfers 200,000 from the MarketingBudget
        // field of the second Album to the first Album. Make sure to run
        // the AddColumnAsyncSample and WriteDataToNewColumnAsyncSample first,
        // in that order.
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        decimal transferAmount = 200000;
        decimal secondBudget = 0;

        // Create connection to Cloud Spanner.
        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        // Create a readwrite transaction that we'll assign
        // to each SpannerCommand.
        using var transaction = await connection.BeginTransactionAsync();
        // Create statement to select the second album's data.
        var cmdLookup = connection.CreateSelectCommand("SELECT * FROM Albums WHERE SingerId = 2 AND AlbumId = 2");
        cmdLookup.Transaction = transaction;
        // Execute the select query.
        using var reader1 = await cmdLookup.ExecuteReaderAsync();
        while (await reader1.ReadAsync())
        {
            // Read the second album's budget.
            secondBudget = reader1.GetFieldValue<decimal>("MarketingBudget");
            // Confirm second Album's budget is sufficient and
            // if not raise an exception. Raising an exception
            // will automatically roll back the transaction.
            if (secondBudget < transferAmount)
            {
                throw new Exception($"The second album's budget {secondBudget} is less than the amount to transfer.");
            }
        }

        // Update second album to remove the transfer amount.
        secondBudget -= transferAmount;
        SpannerCommand cmd = connection.CreateDmlCommand("UPDATE Albums SET MarketingBudget = @MarketingBudget  WHERE SingerId = 2 and AlbumId = 2");
        cmd.Parameters.Add("MarketingBudget", SpannerDbType.Int64, secondBudget);
        cmd.Transaction = transaction;
        var rowCount = await cmd.ExecuteNonQueryAsync();

        // Update first album to add the transfer amount.
        cmd = connection.CreateDmlCommand("UPDATE Albums SET MarketingBudget = MarketingBudget + @MarketingBudgetIncrement WHERE SingerId = 1 and AlbumId = 1");
        cmd.Parameters.Add("MarketingBudgetIncrement", SpannerDbType.Int64, transferAmount);
        cmd.Transaction = transaction;
        rowCount += await cmd.ExecuteNonQueryAsync();

        await transaction.CommitAsync();

        Console.WriteLine("Transaction complete.");
        return rowCount;
    }
}

Ejecuta la muestra con el argumento writeWithTransactionUsingDml.

dotnet run writeWithTransactionUsingDml $env:GOOGLE_PROJECT_ID test-instance example-db

Deberías ver lo siguiente:

Transaction complete.

Usar un índice secundario

Supongamos que quieres obtener todas las filas de Albums que tengan valores de AlbumTitle en un intervalo determinado. Podría leer todos los valores de la columna AlbumTitle con una instrucción SQL o una llamada de lectura y, a continuación, descartar las filas que no cumplan los criterios, pero hacer este análisis de toda la tabla es caro, sobre todo en el caso de las tablas con muchas filas. En su lugar, puedes acelerar la recuperación de filas al buscar por columnas que no sean de clave principal creando un índice secundario en la tabla.

Para añadir un índice secundario a una tabla existente, es preciso actualizar el esquema. Al igual que otras actualizaciones de esquema, Spanner permite añadir un índice mientras la base de datos sigue sirviendo tráfico. Spanner rellena automáticamente el índice con los datos que ya tengas. Los rellenos pueden tardar unos minutos en completarse, pero no es necesario que pongas la base de datos sin conexión ni que evites escribir en la tabla indexada durante este proceso. Para obtener más información, consulta Añadir un índice secundario.

Después de añadir un índice secundario, Spanner lo usa automáticamente en las consultas de SQL que probablemente se ejecuten más rápido con el índice. Si usas la interfaz de lectura, debes especificar el índice que quieras usar.

Añadir un índice secundario

Puedes añadir un índice en la línea de comandos con la CLI de gcloud o de forma programática con la biblioteca de cliente de Spanner para C#.

En la línea de comandos

Usa el siguiente comando CREATE INDEX para añadir un índice a la base de datos:

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)'

Deberías ver lo siguiente:

Schema updating...done.

Usar la biblioteca de cliente de Spanner para C#

Usa CreateDdlCommand() para añadir un índice:


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

public class AddIndexAsyncSample
{
    public async Task AddIndexAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string createStatement = "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)";

        using var connection = new SpannerConnection(connectionString);
        using var createCmd = connection.CreateDdlCommand(createStatement);
        await createCmd.ExecuteNonQueryAsync();
        Console.WriteLine("Added the AlbumsByAlbumTitle index.");
    }
}

Ejecuta el ejemplo con el comando addIndex.

  dotnet run addIndex $env:GOOGLE_PROJECT_ID test-instance example-db

Añadir un índice es un proceso que puede llevar unos minutos. Una vez que se haya añadido el índice, deberías ver lo siguiente:

  Added the AlbumsByAlbumTitle index.

Añadir un índice para lecturas solo de índice

Puede que hayas observado que en el ejemplo de lectura anterior no se incluye la lectura de la columna MarketingBudget. Esto se debe a que la interfaz de lectura de Spanner no admite la posibilidad de combinar un índice con una tabla de datos para buscar valores que no estén almacenados en el índice.

Crea una definición alternativa de AlbumsByAlbumTitle que almacene una copia de MarketingBudget en el índice.

En la línea de comandos

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget)

Añadir un índice es un proceso que puede llevar unos minutos. Esto es lo que debes ver después de añadir el índice:

Schema updating...done.

Usar la biblioteca de cliente de Spanner para C#

Usa CreateDdlCommand() para añadir un índice con una cláusula STORING:


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

public class AddStoringIndexAsyncSample
{
    public async Task AddStoringIndexAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string createStatement = "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)";

        using var connection = new SpannerConnection(connectionString);
        using var createCmd = connection.CreateDdlCommand(createStatement);
        await createCmd.ExecuteNonQueryAsync();
        Console.WriteLine("Added the AlbumsByAlbumTitle2 index.");
    }
}

Ejecuta el ejemplo con el comando addStoringIndex.

dotnet run addStoringIndex $env:GOOGLE_PROJECT_ID test-instance example-db

Deberías ver lo siguiente:

Added the AlbumsByAlbumTitle2 index.

Ahora puedes ejecutar una lectura que obtenga todas las columnas AlbumId, AlbumTitle y MarketingBudget del índice AlbumsByAlbumTitle2:

Lee datos con el índice de almacenamiento que has creado ejecutando una consulta que especifica explícitamente el índice:


using Google.Cloud.Spanner.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

public class QueryDataWithStoringIndexAsyncSample
{
    public class Album
    {
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
        public long? MarketingBudget { get; set; }
    }

    public async Task<List<Album>> QueryDataWithStoringIndexAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        var cmd = connection.CreateSelectCommand(
            "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
            + "{FORCE_INDEX=AlbumsByAlbumTitle2}");

        var albums = new List<Album>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle"),
                MarketingBudget = reader.IsDBNull(reader.GetOrdinal("MarketingBudget")) ? 0 : reader.GetFieldValue<long>("MarketingBudget")
            });
        }
        return albums;
    }
}

Ejecuta el ejemplo con el comando queryDataWithStoringIndex.

dotnet run queryDataWithStoringIndex $env:GOOGLE_PROJECT_ID test-instance example-db

El resultado que verás debe parecerse al siguiente:

AlbumId : 2 AlbumTitle : Forever Hold your Peace MarketingBudget : 300000
AlbumId : 2 AlbumTitle : Go, Go, Go MarketingBudget : 300000

Recuperar datos mediante transacciones de solo lectura

Supongamos que deseas ejecutar más de una lectura en la misma marca de tiempo. En las transacciones de solo lectura se observa un prefijo uniforme del historial de confirmación de transacción, por lo que la aplicación siempre obtiene datos uniformes. Usa el framework .NET TransactionScope() junto con OpenAsReadOnlyAsync() para ejecutar transacciones de solo lectura.

A continuación, se muestra cómo ejecutar una consulta y realizar una lectura en la misma transacción de solo lectura:

.NET Standard 2.0


using Google.Cloud.Spanner.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Transactions;

public class QueryDataWithTransactionAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task<List<Album>> QueryDataWithTransactionAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        var albums = new List<Album>();
        using TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
        using var connection = new SpannerConnection(connectionString);

        // Opens the connection so that the Spanner transaction included in the TransactionScope
        // is read-only TimestampBound.Strong.
        await connection.OpenAsync(SpannerTransactionCreationOptions.ReadOnly, options: null, cancellationToken: default);
        using var cmd = connection.CreateSelectCommand("SELECT SingerId, AlbumId, AlbumTitle FROM Albums");

        // Read #1.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : " + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : " + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : " + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }

        // Read #2. Even if changes occur in-between the reads,
        // the transaction ensures that Read #1 and Read #2
        // return the same data.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                albums.Add(new Album
                {
                    AlbumId = reader.GetFieldValue<int>("AlbumId"),
                    SingerId = reader.GetFieldValue<int>("SingerId"),
                    AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
                });
            }
        }
        scope.Complete();
        Console.WriteLine("Transaction complete.");
        return albums;
    }
}

.NET Standard 1.5


using Google.Cloud.Spanner.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class QueryDataWithTransactionCoreAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task<List<Album>> QueryDataWithTransactionCoreAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        var albums = new List<Album>();

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        // Open a new read only transaction.
        using var transaction = await connection.BeginTransactionAsync(
            SpannerTransactionCreationOptions.ReadOnly,
            transactionOptions: null,
            cancellationToken: default);
        using var cmd = connection.CreateSelectCommand("SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
        cmd.Transaction = transaction;

        // Read #1.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : " + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : " + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : " + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }

        // Read #2. Even if changes occur in-between the reads,
        // the transaction ensures that Read #1 and Read #2
        // return the same data.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                albums.Add(new Album
                {
                    AlbumId = reader.GetFieldValue<int>("AlbumId"),
                    SingerId = reader.GetFieldValue<int>("SingerId"),
                    AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
                });
            }
        }

        Console.WriteLine("Transaction complete.");
        return albums;
    }
}

Ejecuta el ejemplo con el comando queryDataWithTransaction.

dotnet run queryDataWithTransaction $env:GOOGLE_PROJECT_ID test-instance example-db

El resultado que verás debe parecerse al siguiente:

SingerId : 2 AlbumId : 2 AlbumTitle : Forever Hold your Peace
SingerId : 1 AlbumId : 2 AlbumTitle : Go, Go, Go
SingerId : 2 AlbumId : 1 AlbumTitle : Green
SingerId : 2 AlbumId : 3 AlbumTitle : Terrified
SingerId : 1 AlbumId : 1 AlbumTitle : Total Junk
SingerId : 2 AlbumId : 2 AlbumTitle : Forever Hold your Peace
SingerId : 1 AlbumId : 2 AlbumTitle : Go, Go, Go
SingerId : 2 AlbumId : 1 AlbumTitle : Green
SingerId : 2 AlbumId : 3 AlbumTitle : Terrified
SingerId : 1 AlbumId : 1 AlbumTitle : Total Junk

Limpieza

Para evitar que se apliquen cargos adicionales en tu cuenta de Facturación de Cloud por los recursos utilizados en este tutorial, elimina la base de datos y la instancia que has creado.

Eliminar la base de datos

Al eliminar una instancia, se eliminan automáticamente todas sus bases de datos. En este paso se muestra cómo eliminar una base de datos sin eliminar una instancia (se seguirían generando costes por la instancia).

En la línea de comandos

gcloud spanner databases delete example-db --instance=test-instance

Usar la Google Cloud consola

  1. Ve a la página Instancias de Spanner de la Google Cloud consola.

    Ir a la página Instancias

  2. Haz clic en la instancia.

  3. Haz clic en la base de datos que quieras eliminar.

  4. En la página sobre detalles de la base de datos, haz clic en Eliminar.

  5. Confirma que deseas eliminar la base de datos y haz clic en Eliminar.

Eliminar la instancia

Al eliminar una instancia, se borran todas las bases de datos creadas en dicha instancia.

En la línea de comandos

gcloud spanner instances delete test-instance

Usar la Google Cloud consola

  1. Ve a la página Instancias de Spanner de la Google Cloud consola.

    Ir a la página Instancias

  2. Haz clic en tu instancia.

  3. Haz clic en Eliminar.

  4. Confirma que deseas eliminar la instancia y haz clic en Eliminar.

Siguientes pasos