Iniziare a utilizzare Spanner in C#


Obiettivi

Questo tutorial illustra i seguenti passaggi utilizzando la libreria client Spanner per C#:

  • Crea un'istanza e un database Spanner.
  • Scrivi, leggi ed esegui query SQL sui dati nel database.
  • Aggiorna lo schema del database.
  • Aggiorna i dati utilizzando una transazione di lettura/scrittura.
  • Aggiungi un indice secondario al database.
  • Utilizza l'indice per leggere ed eseguire query SQL sui dati.
  • Recupera i dati utilizzando una transazione di sola lettura.

Costi

Questo tutorial utilizza Spanner, un componente fatturabile diGoogle Cloud. Per informazioni sul costo dell'utilizzo di Spanner, consulta Prezzi.

Prima di iniziare

Completa i passaggi descritti in Configurazione, che riguardano la creazione e la configurazione di un progetto Google Cloud predefinito, l'attivazione della fatturazione, l'attivazione dell'API Cloud Spanner e la configurazione di OAuth 2.0 per ottenere le credenziali di autenticazione per utilizzare l'API Cloud Spanner.

In particolare, assicurati di eseguire gcloud auth application-default login per configurare l'ambiente di sviluppo locale con le credenziali di autenticazione.

Prepara l'ambiente C# locale

  1. Imposta la variabile di ambiente GOOGLE_PROJECT_ID sull'ID progetto Google Cloud .

    1. Innanzitutto, imposta GOOGLE_PROJECT_ID per la sessione PowerShell corrente:

      $env:GOOGLE_PROJECT_ID = "MY_PROJECT_ID"
    2. Poi, imposta GOOGLE_PROJECT_ID per tutti i processi creati dopo questo comando:

      [Environment]::SetEnvironmentVariable("GOOGLE_PROJECT_ID", "MY_PROJECT_ID", "User")
  2. Scarica le credenziali.

    1. Vai alla pagina Credenziali nella console Google Cloud .

      Vai alla pagina Credenziali

    2. Fai clic su Crea credenziali e scegli Chiave account di servizio.

    3. In "Account di servizio", scegli Account di servizio predefinito di Compute Engine e lascia selezionato JSON in "Tipo di chiave". Fai clic su Crea. Il computer scarica un file JSON.

  3. Configura le credenziali. Per un file denominato FILENAME.json nella directory Download di CURRENT_USER, situata sul disco C, esegui i seguenti comandi per impostare GOOGLE_APPLICATION_CREDENTIALS in modo che punti alla chiave JSON:

    1. Innanzitutto, per impostare GOOGLE_APPLICATION_CREDENTIALS per questa sessione PowerShell:

      $env:GOOGLE_APPLICATION_CREDENTIALS = "C:\Users\CURRENT_USER\Downloads\FILENAME.json"
    2. Quindi, per impostare GOOGLE_APPLICATION_CREDENTIALS per tutti i processi creati dopo questo comando:

      [Environment]::SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "C:\Users\CURRENT_USER\Downloads\FILENAME.json", "User")
  4. Clona il repository dell'app di esempio sulla tua macchina locale:

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

    In alternativa, puoi scaricare il sample come file ZIP ed estrarlo.

  5. Apri Spanner.sln, che si trova nella directory dotnet-docs-samples\spanner\api del repository scaricato, con Visual Studio 2017 o versioni successive, quindi compilalo.

  6. Passa alla directory all'interno del repository scaricato che contiene l'applicazione compilata. Ad esempio:

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

Crea un'istanza

Quando utilizzi Spanner per la prima volta, devi creare un'istanza, ovvero un'allocazione delle risorse utilizzate dai database Spanner. Quando crei un'istanza, scegli una configurazione dell'istanza, che determina dove vengono archiviati i dati e anche il numero di nodi da utilizzare, che determina la quantità di risorse di gestione e archiviazione nell'istanza.

Esegui il seguente comando per creare un'istanza Spanner nella regione us-central1 con 1 nodo:

gcloud spanner instances create test-instance --config=regional-us-central1 `
    --description="Test Instance" --nodes=1

Tieni presente che viene creata un'istanza con le seguenti caratteristiche:

  • ID istanza test-instance
  • Nome visualizzato Test Instance
  • Configurazione dell'istanza regional-us-central1 (le configurazioni regionali memorizzano i dati in una regione, mentre quelle multiregionali li distribuiscono in più regioni. Per ulteriori informazioni, consulta Informazioni sulle istanze.
  • Un conteggio dei nodi pari a 1 (node_count) corrisponde alla quantità di risorse di gestione e archiviazione disponibili per i database nell'istanza. Scopri di più in Nodi e unità di elaborazione.

Dovresti vedere:

Creating instance...done.

Sfogliare i file di esempio

Il repository di esempi contiene un esempio che mostra come utilizzare Spanner con C#.

Dai un'occhiata al repository GitHub di Spanner .NET, che mostra come creare un database e modificare uno schema di database. I dati utilizzano lo schema di esempio mostrato nella pagina Schema e modello dati.

Crea un database

Dovresti vedere:

Il seguente codice crea un database e due tabelle al suo interno.

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

Il passaggio successivo consiste nello scrivere i dati nel database.

Crea un client di database

Prima di poter eseguire letture o scritture, devi creare 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();
        }
    }
}

Puoi pensare a un SpannerConnection come a una connessione al database: tutte le tue interazioni con Spanner devono passare attraverso un SpannerConnection.

Scopri di più nella documentazione di riferimento di SpannerConnection.

Scrivere dati con DML

Puoi inserire dati utilizzando il Data Manipulation Language (DML) in una transazione di lettura/scrittura.

Utilizza il metodo ExecuteNonQueryAsync() per eseguire un'istruzione 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;
    }
}

Esegui l'esempio utilizzando l'argomento writeUsingDml.

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

Dovresti vedere:

4 row(s) inserted...

Scrivere dati con modifiche

Puoi anche inserire i dati utilizzando le mutazioni.

Puoi inserire i dati utilizzando il metodo connection.CreateInsertCommand(), che crea un nuovo SpannerCommand per inserire righe in una tabella. Il metodo SpannerCommand.ExecuteNonQueryAsync() aggiunge nuove righe alla tabella.

Questo codice mostra come inserire i dati utilizzando le mutazioni:


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

Esegui l'esempio utilizzando l'argomento insertSampleData.

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

Dovresti vedere:

Inserted data.

Esegui query sui dati utilizzando SQL

Spanner supporta un'interfaccia SQL per la lettura dei dati, a cui puoi accedere sulla riga di comando utilizzando Google Cloud CLI o programmaticamente utilizzando la libreria client Spanner per C#.

Nella riga di comando

Esegui la seguente istruzione SQL per leggere i valori di tutte le colonne della tabellaAlbums:

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

Il risultato dovrebbe essere:

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

Utilizzare la libreria client Spanner per C#

Oltre a eseguire un'istruzione SQL sulla riga di comando, puoi emettere la stessa istruzione SQL in modo programmatico utilizzando la libreria client Spanner per C#.

Utilizza ExecuteReaderAsync() per eseguire la query 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;
    }
}

Ecco come eseguire la query e accedere ai dati:

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

Dovresti vedere il seguente risultato:

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

Esegui una query utilizzando un parametro SQL

Se la tua applicazione ha una query eseguita di frequente, puoi migliorarne le prestazioni parametrizzandola. La query parametrica risultante può essere memorizzata nella cache e riutilizzata, il che consente di ridurre i costi di compilazione. Per ulteriori informazioni, consulta Utilizzare parametri di ricerca per velocizzare le query eseguite di frequente.

Ecco un esempio di utilizzo di un parametro nella clausola WHERE per eseguire query sui record contenenti un valore specifico per 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;
    }
}

Ecco come eseguire la query con un parametro e accedere ai dati:

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

Dovresti vedere il seguente risultato:

SingerId : 12 FirstName : Melissa LastName : Garcia

Aggiorna lo schema del database

Supponiamo che tu debba aggiungere una nuova colonna denominata MarketingBudget alla tabella Albums. L'aggiunta di una nuova colonna a una tabella esistente richiede un aggiornamento dello schema del database. Spanner supporta gli aggiornamenti dello schema di un database mentre il database continua a gestire il traffico. Gli aggiornamenti dello schema non richiedono il mettere offline il database e non bloccano intere tabelle o colonne. Puoi continuare a scrivere dati nel database durante l'aggiornamento dello schema. Scopri di più sugli aggiornamenti dello schema supportati e sul rendimento delle modifiche dello schema in Eseguire aggiornamenti dello schema.

Aggiungere una colonna

Puoi aggiungere una colonna sulla riga di comando utilizzando Google Cloud CLI o programmaticamente utilizzando la libreria client Spanner per C#.

Nella riga di comando

Utilizza il seguente comando ALTER TABLE per aggiungere la nuova colonna alla tabella:

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'

Dovresti vedere:

Schema updating...done.

Utilizzare la libreria client Spanner per C#

Utilizza CreateDdlCommand() per modificare lo schema:


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

Esegui l'esempio utilizzando il comando addColumn.

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

Dovresti vedere:

Added the MarketingBudget column.

Scrivere i dati nella nuova colonna

Il codice seguente scrive i dati nella nuova colonna. Imposta MarketingBudget su 100000 per la riga con chiave Albums(1, 1) e su 500000 per la riga con chiave 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;
    }
}

Esegui l'esempio utilizzando il comando writeDataToNewColumn.

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

Dovresti vedere:

Updated data.

Puoi anche eseguire una query SQL per recuperare i valori che hai appena scritto.

Ecco il codice per eseguire la query:


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

Per eseguire questa query, esegui il sample utilizzando l'argomento queryNewColumn.

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

Dovresti vedere:

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 :

Aggiornare i dati

Puoi aggiornare i dati utilizzando la DML in una transazione di lettura/scrittura.

Utilizza il metodo ExecuteNonQueryAsync() per eseguire un'istruzione 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;
    }
}

Esegui l'esempio utilizzando l'argomento writeWithTransactionUsingDml.

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

Dovresti vedere:

Transaction complete.

Utilizzare un indice secondario

Supponiamo che tu voglia recuperare tutte le righe di Albums con valori AlbumTitle in un determinato intervallo. Potresti leggere tutti i valori della colonna AlbumTitle utilizzando un'istruzione SQL o una chiamata di lettura, quindi ignorare le righe che non soddisfano i criteri, ma questa scansione completa della tabella è costosa, soprattutto per le tabelle con molte righe. In alternativa, puoi velocizzare il recupero delle righe quando effettui ricerche in base a colonne di chiavi non principali creando un indice secondario nella tabella.

L'aggiunta di un indice secondario a una tabella esistente richiede un aggiornamento dello schema. Come per gli altri aggiornamenti dello schema, Spanner supporta l'aggiunta di un indice mentre il database continua a gestire il traffico. Spanner esegue automaticamente il backfill dell' indice con i dati esistenti. Il completamento dei backfill potrebbe richiedere alcuni minuti, ma non è necessario mettere offline il database o evitare di scrivere nella tabella indicizzata durante questa procedura. Per maggiori dettagli, consulta Aggiungere un indice secondario.

Dopo aver aggiunto un indice secondario, Spanner lo utilizza automaticamente per le query SQL che potrebbero essere eseguite più velocemente con l'indice. Se utilizzi l'interfaccia read, devi specificare l'indice che vuoi utilizzare.

Aggiungere un indice secondario

Puoi aggiungere un indice sulla riga di comando utilizzando la gcloud CLI o programmaticamente utilizzando la libreria client Spanner per C#.

Nella riga di comando

Utilizza il seguente comando CREATE INDEX per aggiungere un indice al database:

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

Dovresti vedere:

Schema updating...done.

Utilizzo della libreria client Spanner per C#

Usa CreateDdlCommand() per aggiungere un indice:


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

Esegui l'esempio utilizzando il comando addIndex.

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

L'aggiunta di un indice può richiedere alcuni minuti. Dopo aver aggiunto l'indice, dovresti vedere:

  Added the AlbumsByAlbumTitle index.

Aggiungere un indice per le letture solo dell'indice

Potresti aver notato che l'esempio di lettura precedente non include la lettura della colonna MarketingBudget. Questo perché l'interfaccia di lettura di Spanner non supporta la possibilità di unire un indice a una tabella di dati per cercare valori che non sono archiviati nell'indice.

Crea una definizione alternativa di AlbumsByAlbumTitle che memorizzi una copia di MarketingBudget nell'indice.

Nella riga di comando

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)

L'aggiunta di un indice può richiedere alcuni minuti. Dopo aver aggiunto l'indice, dovresti vedere:

Schema updating...done.

Utilizzo della libreria client Spanner per C#

Utilizza CreateDdlCommand() per aggiungere un indice con una clausola 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.");
    }
}

Esegui l'esempio utilizzando il comando addStoringIndex.

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

Dovresti vedere:

Added the AlbumsByAlbumTitle2 index.

Ora puoi eseguire una lettura che recupera tutte le colonne AlbumId, AlbumTitle e MarketingBudget dall'indice AlbumsByAlbumTitle2:

Leggi i dati utilizzando l'indice di archiviazione che hai creato eseguendo una query che specifica esplicitamente l'indice:


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

Esegui l'esempio utilizzando il comando queryDataWithStoringIndex.

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

Dovresti visualizzare un output simile al seguente:

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

Recuperare i dati utilizzando le transazioni di sola lettura

Supponiamo che tu voglia eseguire più letture con lo stesso timestamp. Le transazioni di sola lettura osservano un prefisso coerente della cronologia dei commit delle transazioni, in modo che l'applicazione riceva sempre dati coerenti. Utilizza TransactionScope() di .NET Framework insieme a OpenAsReadOnlyAsync() per eseguire transazioni di sola lettura.

Di seguito viene mostrato come eseguire una query ed eseguire una lettura nella stessa transazione di sola lettura:

.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, 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;
    }
}

Esegui l'esempio utilizzando il comando queryDataWithTransaction.

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

Dovresti visualizzare un output simile al seguente:

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

Esegui la pulizia

Per evitare che al tuo account di fatturazione Cloud vengano addebitati costi aggiuntivi per le risorse utilizzate in questo tutorial, elimina il database ed elimina l'istanza che hai creato.

Elimina il database

Se elimini un'istanza, tutti i database al suo interno vengono eliminati automaticamente. Questo passaggio mostra come eliminare un database senza eliminare un'istanza (per l'istanza verranno comunque addebitati costi).

Nella riga di comando

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

Utilizzo della console Google Cloud

  1. Vai alla pagina Istanze Spanner nella console Google Cloud .

    Vai alla pagina Istanze

  2. Fai clic sull'istanza.

  3. Fai clic sul database che vuoi eliminare.

  4. Nella pagina Dettagli database, fai clic su Elimina.

  5. Conferma di voler eliminare il database e fai clic su Elimina.

Elimina l'istanza

L'eliminazione di un'istanza comporta automaticamente l'eliminazione di tutti i database creati in quell'istanza.

Nella riga di comando

gcloud spanner instances delete test-instance

Utilizzo della console Google Cloud

  1. Vai alla pagina Istanze Spanner nella console Google Cloud .

    Vai alla pagina Istanze

  2. Fai clic sull'istanza.

  3. Fai clic su Elimina.

  4. Conferma di voler eliminare l'istanza e fai clic su Elimina.

Passaggi successivi