Condividi tramite


Persistenza dei grani

Ai grani possono essere associati più oggetti dati persistenti denominati. Questi oggetti di stato vengono caricati dall'archiviazione durante l'attivazione granulare, in modo che siano disponibili durante le richieste. La persistenza granulare usa un modello di plug-in estendibile, consentendo di usare provider di archiviazione per qualsiasi database. Questo modello di persistenza è progettato per semplicità e non è progettato per coprire tutti i modelli di accesso ai dati. I grani possono anche accedere direttamente ai database senza usare il modello di persistenza granulare.

Diagramma di persistenza granulare

Nel diagramma precedente UserGrain ha uno stato profilo e uno stato carrello , ognuno archiviato in un sistema di archiviazione separato.

Obiettivi

  1. Supporta più oggetti dati persistenti denominati per ogni grain.
  2. Consentire più provider di archiviazione configurati, ognuno con una configurazione diversa e supportato da un sistema di archiviazione diverso.
  3. Abilitare la community a sviluppare e pubblicare fornitori di archiviazione.
  4. Fornire ai provider di archiviazione il controllo completo su come archiviare i dati sullo stato delle istanze nella memoria persistente. Corollario: Orleans non offre una soluzione di archiviazione ORM completa, ma consente ai provider di archiviazione personalizzati di supportare requisiti ORM specifici secondo necessità.

Pacchetti

È possibile trovare fornitori di servizi di archiviazione dei dati su Orleans. I pacchetti gestiti ufficialmente includono:

API (Interfaccia di Programmazione delle Applicazioni)

I grani interagiscono con il relativo stato persistente usando IPersistentState<TState>, dove TState è il tipo di stato serializzabile:

public interface IPersistentState<TState> : IStorage<TState>
{
}

public interface IStorage<TState> : IStorage
{
    TState State { get; set; }
}

public interface IStorage
{
    string Etag { get; }

    bool RecordExists { get; }

    Task ClearStateAsync();

    Task WriteStateAsync();

    Task ReadStateAsync();
}
public interface IPersistentState<TState> where TState : new()
{
    TState State { get; set; }

    string Etag { get; }

    Task ClearStateAsync();

    Task WriteStateAsync();

    Task ReadStateAsync();
}

Orleans inserisce istanze di IPersistentState<TState> nel grain come parametri per il costruttore. È possibile annotare questi parametri con un PersistentStateAttribute attributo per identificare il nome dello stato inserito e il nome del provider di archiviazione che lo fornisce. Nell'esempio seguente viene illustrato inserendo due stati denominati nel UserGrain costruttore:

public class UserGrain : Grain, IUserGrain
{
    private readonly IPersistentState<ProfileState> _profile;
    private readonly IPersistentState<CartState> _cart;

    public UserGrain(
        [PersistentState("profile", "profileStore")] IPersistentState<ProfileState> profile,
        [PersistentState("cart", "cartStore")] IPersistentState<CartState> cart)
    {
        _profile = profile;
        _cart = cart;
    }

    public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);

    public async Task SetNameAsync(string name)
    {
        _profile.State.Name = name;
        await _profile.WriteStateAsync();
    }
}

Tipi di grani diversi possono utilizzare diversi provider di archiviazione configurati, anche se entrambi sono dello stesso tipo (ad esempio, due diverse istanze del provider di Azure Table Storage connesse a diversi account di Azure Storage).

Stato di lettura

Lo stato del grano viene letto automaticamente quando il grano si attiva, ma i grani sono responsabili di attivare esplicitamente la scrittura per qualsiasi stato del grano modificato quando necessario.

Se un grain desidera rileggere in modo esplicito il suo stato più recente dalla memoria di backup, deve chiamare il metodo ReadStateAsync. Questo ricarica lo stato granulare dall'archivio permanente tramite il provider di archiviazione. La precedente copia in memoria dello stato del grano viene sovrascritta e sostituita al completamento di Task da ReadStateAsync.

Accedere al valore dello stato usando la State proprietà . Ad esempio, il metodo seguente accede allo stato del profilo dichiarato nel codice precedente:

public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);

Non è necessario chiamare ReadStateAsync durante il normale funzionamento. Orleans Carica automaticamente lo stato durante l'attivazione. Tuttavia, è possibile usare ReadStateAsync per aggiornare lo stato modificato esternamente.

Per informazioni dettagliate sui meccanismi di gestione degli errori, vedere la sezione Modalità di errore di seguito.

Scrivere stato

È possibile modificare lo stato tramite la State proprietà . Lo stato modificato non viene salvato automaticamente in modo permanente. Si decide invece quando rendere persistente lo stato chiamando il WriteStateAsync metodo . Ad esempio, il metodo seguente aggiorna una proprietà in State e mantiene lo stato aggiornato:

public async Task SetNameAsync(string name)
{
    _profile.State.Name = name;
    await _profile.WriteStateAsync();
}

Concettualmente, il Orleans runtime esegue una copia completa dell'oggetto dati dello stato granulare per il relativo utilizzo durante qualsiasi operazione di scrittura. Sotto le quinte, il runtime potrebbe usare regole di ottimizzazione e euristica per evitare di eseguire alcune o tutte le copie complete in determinate circostanze, a condizione che la semantica di isolamento logico prevista venga mantenuta.

Per informazioni dettagliate sui meccanismi di gestione degli errori, vedere la sezione Modalità di errore di seguito.

Cancellare lo stato

Il metodo ClearStateAsync elimina lo stato del grano nell'archiviazione. A seconda del provider, questa operazione potrebbe facoltativamente eliminare completamente lo stato di granularità.

Inizia subito

Prima che un grain possa usare la persistenza, è necessario configurare un provider di archiviazione nel silo.

Prima di tutto, configurare i provider di archiviazione, uno per lo stato del profilo e uno per lo stato del carrello:

L'uso di DefaultAzureCredential con un endpoint URI è l'approccio consigliato per gli ambienti di produzione.

var tableEndpoint = new Uri(configuration["AZURE_TABLE_STORAGE_ENDPOINT"]!);
var blobEndpoint = new Uri(configuration["AZURE_BLOB_STORAGE_ENDPOINT"]!);
var credential = new DefaultAzureCredential();

var builder = Host.CreateApplicationBuilder();
builder.UseOrleans(siloBuilder =>
{
    siloBuilder.AddAzureTableGrainStorage(
        name: "profileStore",
        configureOptions: options =>
        {
            options.TableServiceClient = new TableServiceClient(tableEndpoint, credential);
        })
        .AddAzureBlobGrainStorage(
            name: "cartStore",
            configureOptions: options =>
            {
                options.BlobServiceClient = new BlobServiceClient(blobEndpoint, credential);
            });
});

using var host = builder.Build();
var host = new HostBuilder()
    .UseOrleans(siloBuilder =>
    {
        siloBuilder.AddAzureTableGrainStorage(
            name: "profileStore",
            configureOptions: options =>
            {
                // Use JSON for serializing the state in storage
                options.UseJson = true;

                // Configure the storage connection key
                options.ConnectionString =
                    "DefaultEndpointsProtocol=https;AccountName=data1;AccountKey=SOMETHING1";
            })
            .AddAzureBlobGrainStorage(
                name: "cartStore",
                configureOptions: options =>
                {
                    // Use JSON for serializing the state in storage
                    options.UseJson = true;

                    // Configure the storage connection key
                    options.ConnectionString =
                        "DefaultEndpointsProtocol=https;AccountName=data2;AccountKey=SOMETHING2";
                });
    })
    .Build();

Importante

Microsoft consiglia di usare il flusso di autenticazione più sicuro disponibile. Se ci si connette ad Azure SQL, le Identità gestite per le risorse Azure sono il metodo di autenticazione consigliato.

Ora che è stato configurato un provider di archiviazione denominato "profileStore", è possibile accedere a questo provider da una granularità.

È possibile aggiungere uno stato permanente a un livello di granularità in due modi principali:

  1. Inserendo IPersistentState<TState> nel costruttore del grain.
  2. Ereditando da Grain<TGrainState>.

Il modo consigliato per aggiungere archiviazione a un grano è iniettando IPersistentState<TState> nel costruttore del grano con un attributo associato [PersistentState("stateName", "providerName")]. Per informazioni dettagliate su Grain<TGrainState>, vedere Using Grain<TGrainState> to add storage to a grain di seguito. L'uso Grain<TGrainState> è ancora supportato, ma considerato un approccio legacy.

Dichiarare una classe per mantenere lo stato della granularità:

[Serializable]
public class ProfileState
{
    public string Name { get; set; }

    public Date DateOfBirth { get; set; }
}

Inserire IPersistentState<TState> nel costruttore del grano:

public class UserGrainSimple : Grain, IUserGrain
{
    private readonly IPersistentState<ProfileState> _profile;

    public UserGrainSimple(
        [PersistentState("profile", "profileStore")]
        IPersistentState<ProfileState> profile)
    {
        _profile = profile;
    }

    public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);

    public async Task SetNameAsync(string name)
    {
        _profile.State.Name = name;
        await _profile.WriteStateAsync();
    }
}

Importante

Lo stato del profilo non verrà caricato al momento dell'inserimento nel costruttore, quindi l'accesso non è valido in quel momento. Lo stato verrà caricato prima che OnActivateAsync venga chiamato.

Ora che la granularità ha uno stato persistente, è possibile aggiungere metodi per leggere e scrivere lo stato:

public class UserGrainComplete : Grain, IUserGrain
{
    private readonly IPersistentState<ProfileState> _profile;

    public UserGrainComplete(
        [PersistentState("profile", "profileStore")]
        IPersistentState<ProfileState> profile)
    {
        _profile = profile;
    }

    public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);

    public async Task SetNameAsync(string name)
    {
        _profile.State.Name = name;
        await _profile.WriteStateAsync();
    }
}

Modalità di errore per le operazioni di persistenza

Modalità di errore per le operazioni di lettura

Gli errori restituiti dal provider di archiviazione durante la lettura iniziale dei dati sullo stato per un particolare granularità non riescono a eseguire l'operazione di attivazione per tale granularità. In questi casi, non verrà eseguita alcuna chiamata al metodo di callback del ciclo di vita di OnActivateAsync. La richiesta originale al grain che ha causato i guasti di attivazione viene restituita al chiamante indietro, proprio come qualsiasi altro fallimento durante l'attivazione del grain. Gli errori riscontrati dal provider di archiviazione durante la lettura dei dati sullo stato per un particolare grano generano un'eccezione da parte di ReadStateAsyncTask. Il grano può scegliere di maneggiare o ignorare l'eccezione Task, esattamente come qualsiasi altra Task in Orleans.

Qualsiasi tentativo di inviare un messaggio a un grano che non è riuscito a caricarsi all'avvio del silo per una configurazione del provider di archiviazione mancante o non valida restituisce l'errore permanente BadProviderConfigException.

Modalità di errore per le operazioni di scrittura

Gli errori riscontrati dal provider di archiviazione durante la scrittura dei dati di stato per un particolare grano generano un'eccezione WriteStateAsyncTask. In genere, ciò significa che l'eccezione chiamata grain viene rilanciata al chiamante client, a condizione che l'oggetto WriteStateAsyncTask sia concatenato correttamente nella restituzione Task finale per questo metodo grain. In alcuni scenari avanzati, tuttavia, è possibile scrivere codice granulare per gestire in modo specifico tali errori di scrittura, proprio come la gestione di qualsiasi altro errore Task.

I grain che eseguono la gestione degli errori o il codice di ripristino devono intercettare eccezioni o errori in errore e non rigenerarli, segnalando che hanno gestito correttamente l'errore di scrittura.

Consigli

Usare la serializzazione JSON o un altro formato di serializzazione a tolleranza di versione

Il codice si evolve e spesso include tipi di archiviazione. Per supportare queste modifiche, configurare un serializzatore appropriato. Orleans A partire dalla versione 7.0, è possibile configurare il serializzatore di archiviazione granulare usando l'interfaccia IGrainStorageSerializer . Per impostazione predefinita, lo stato granulare serializza usando JSON (Newtonsoft.Json). Assicurarsi che durante l'evoluzione dei contratti dati, i dati già archiviati possano comunque essere caricati. Per altre informazioni, vedere Serializzatori di archiviazione granulari.

Il codice si evolve e spesso include tipi di archiviazione. Per supportare queste modifiche, configurare un serializzatore appropriato. Per la maggior parte dei provider di archiviazione, è disponibile un'opzione UseJson o un formato simile per l'uso di JSON come formato di serializzazione. Assicurarsi che durante l'evoluzione dei contratti dati, i dati già archiviati possano comunque essere caricati.

Uso Grain<TGrainState> per aggiungere spazio di archiviazione a un grano

Importante

L'uso di Grain<TGrainState> per aggiungere spazio di archiviazione a un grain è considerato una funzionalità legacy. Aggiungere lo stoccaggio del grano usando IPersistentState<TState> come descritto in precedenza.

Le classi granulari che ereditano da Grain<TGrainState> (dove T è un tipo di dati di stato specifico dell'applicazione che richiede la persistenza) hanno il relativo stato caricato automaticamente dalla risorsa di archiviazione specificata.

Contrassegnare tali grani con una StorageProviderAttribute che specifica un'istanza denominata di un provider di archiviazione da usare per la lettura e scrittura dello stato per questo grano.

[StorageProvider(ProviderName = "store1")]
public class MyGrain : Grain<MyGrainState>, IMyGrain
{
    public Task DoSomethingAsync() => Task.CompletedTask;
}

La Grain<TGrainState> classe base definisce i metodi seguenti per le sottoclassi da chiamare:

public abstract class GrainBaseExample<TState>
{
    protected virtual Task ReadStateAsync() { return Task.CompletedTask; }
    protected virtual Task WriteStateAsync() { return Task.CompletedTask; }
    protected virtual Task ClearStateAsync() { return Task.CompletedTask; }
}

Il comportamento di questi metodi corrisponde alle loro controparti su IPersistentState<TState> definiti in precedenza.

Creare un provider di archiviazione

Esistono due parti delle API di persistenza dello stato: l'API esposta alla granularità tramite IPersistentState<TState> o Grain<TGrainState>e l'API del provider di archiviazione, centrata intorno IGrainStorage, i provider di archiviazione dell'interfaccia devono implementare:

/// <summary>
/// Interface to be implemented for a storage able to read and write Orleans grain state data.
/// </summary>
public interface ICustomGrainStorage
{
    /// <summary>Read data function for this storage instance.</summary>
    /// <param name="stateName">Name of the state for this grain</param>
    /// <param name="grainId">Grain ID</param>
    /// <param name="grainState">State data object to be populated for this grain.</param>
    /// <typeparam name="T">The grain state type.</typeparam>
    /// <returns>Completion promise for the Read operation on the specified grain.</returns>
    Task ReadStateAsync<T>(
        string stateName, GrainId grainId, IGrainState<T> grainState);

    /// <summary>Write data function for this storage instance.</summary>
    /// <param name="stateName">Name of the state for this grain</param>
    /// <param name="grainId">Grain ID</param>
    /// <param name="grainState">State data object to be written for this grain.</param>
    /// <typeparam name="T">The grain state type.</typeparam>
    /// <returns>Completion promise for the Write operation on the specified grain.</returns>
    Task WriteStateAsync<T>(
        string stateName, GrainId grainId, IGrainState<T> grainState);

    /// <summary>Delete / Clear data function for this storage instance.</summary>
    /// <param name="stateName">Name of the state for this grain</param>
    /// <param name="grainId">Grain ID</param>
    /// <param name="grainState">Copy of last-known state data object for this grain.</param>
    /// <typeparam name="T">The grain state type.</typeparam>
    /// <returns>Completion promise for the Delete operation on the specified grain.</returns>
    Task ClearStateAsync<T>(
        string stateName, GrainId grainId, IGrainState<T> grainState);
}
/// <summary>
/// Interface to be implemented for a storage able to read and write Orleans grain state data.
/// </summary>
public interface IGrainStorage
{
    /// <summary>Read data function for this storage instance.</summary>
    /// <param name="grainType">Type of this grain [fully qualified class name]</param>
    /// <param name="grainReference">Grain reference object for this grain.</param>
    /// <param name="grainState">State data object to be populated for this grain.</param>
    /// <returns>Completion promise for the Read operation on the specified grain.</returns>
    Task ReadStateAsync(
        string grainType, GrainReference grainReference, IGrainState grainState);

    /// <summary>Write data function for this storage instance.</summary>
    /// <param name="grainType">Type of this grain [fully qualified class name]</param>
    /// <param name="grainReference">Grain reference object for this grain.</param>
    /// <param name="grainState">State data object to be written for this grain.</param>
    /// <returns>Completion promise for the Write operation on the specified grain.</returns>
    Task WriteStateAsync(
        string grainType, GrainReference grainReference, IGrainState grainState);

    /// <summary>Delete / Clear data function for this storage instance.</summary>
    /// <param name="grainType">Type of this grain [fully qualified class name]</param>
    /// <param name="grainReference">Grain reference object for this grain.</param>
    /// <param name="grainState">Copy of last-known state data object for this grain.</param>
    /// <returns>Completion promise for the Delete operation on the specified grain.</returns>
    Task ClearStateAsync(
        string grainType, GrainReference grainReference, IGrainState grainState);
}

Creare un provider di archiviazione personalizzato implementando questa interfaccia e registrando tale implementazione . Per un esempio di implementazione di un provider di archiviazione esistente, vedere AzureBlobGrainStorage.

Semantica del provider di archiviazione

Un valore opaco specifico del provider (Etag) (string) può essere impostato da un provider di archiviazione come parte dei metadati dello stato del "grain", popolati quando lo stato è stato letto. Alcuni provider possono scegliere di lasciarlo come null se non usano Etag.

Qualsiasi tentativo di eseguire un'operazione di scrittura quando il provider di archiviazione rileva una Etag violazione del vincolo dovrebbe causare che la scrittura Task vada in errore con un errore InconsistentStateException temporaneo, avvolgendo l'eccezione di archiviazione sottostante.

public class InconsistentStateException : OrleansException
{
    public InconsistentStateException(
    string message,
    string storedEtag,
    string currentEtag,
    Exception storageException)
        : base(message, storageException)
    {
        StoredEtag = storedEtag;
        CurrentEtag = currentEtag;
    }

    public InconsistentStateException(
        string storedEtag,
        string currentEtag,
        Exception storageException)
        : this(storageException.Message, storedEtag, currentEtag, storageException)
    {
    }

    /// <summary>The Etag value currently held in persistent storage.</summary>
    public string StoredEtag { get; }

    /// <summary>The Etag value currently held in memory, and attempting to be updated.</summary>
    public string CurrentEtag { get; }
}

Qualsiasi altra condizione di errore di un'operazione di archiviazione deve causare l'interruzione dell'oggetto restituito Task con un'eccezione che indica il problema di archiviazione sottostante. In molti casi, questa eccezione potrebbe essere generata nuovamente al chiamante che ha attivato l'operazione di archiviazione chiamando un metodo sulla granularità. È importante considerare se il chiamante può deserializzare questa eccezione. Ad esempio, il client potrebbe non aver caricato la libreria di persistenza specifica contenente il tipo di eccezione. Per questo motivo, è consigliabile convertire le eccezioni in eccezioni che possono propagarsi al chiamante.

Mappatura dei dati

I singoli provider di archiviazione devono decidere il modo migliore per archiviare lo stato di granularità: BLOB (vari formati/moduli serializzati) o colonne per campo sono scelte ovvie.

Registrare un provider di archiviazione

Il runtime Orleans risolve un provider di archiviazione dal provider di servizi IServiceProvider quando viene creato un granello. Il runtime risolve un'istanza di IGrainStorage. Se il provider di archiviazione è denominato (ad esempio, tramite l'attributo [PersistentState(stateName, storageName)]), viene risolta un'istanza denominata di IGrainStorage.

Per registrare un'istanza denominata di IGrainStorage, usare il AddSingletonNamedService metodo di estensione, seguendo l'esempio del provider AzureTableGrainStorage qui.

Persistenza dei grain Redis

Redis è un archivio dati in memoria molto diffuso che può essere usato per la persistenza granulare. Microsoft.Orleans. Il pacchetto Persistence.Redis offre un provider di archiviazione granulare supportato da Redis.

Configurare l'archiviazione granulare di Redis

Configurare l'archiviazione dei grain di Redis usando il metodo di estensione AddRedisGrainStorage:

var builder = Host.CreateApplicationBuilder();
builder.UseOrleans(siloBuilder =>
{
    siloBuilder.AddRedisGrainStorage(
        name: "redis",
        configureOptions: options =>
        {
            options.ConfigurationOptions = new ConfigurationOptions
            {
                EndPoints = { "localhost:6379" },
                AbortOnConnectFail = false
            };
        });
});

using var host = builder.Build();

Per configurare Redis come provider di archiviazione granulare predefinito, usare AddRedisGrainStorageAsDefault:

siloBuilder.AddRedisGrainStorageAsDefault(options =>
{
    options.ConfigurationOptions = new ConfigurationOptions
    {
        EndPoints = { "localhost:6379" }
    };
});

Opzioni di archiviazione di Redis

La RedisStorageOptions classe fornisce le opzioni di configurazione seguenti:

Proprietà TIPO Description
ConfigurationOptions ConfigurationOptions Configurazione del client StackExchange.Redis. Obbligatorio.
DeleteStateOnClear bool Indica se eliminare lo stato da Redis quando ClearStateAsync viene chiamato. Il valore predefinito è false.
EntryExpiry TimeSpan? Scadenza facoltativa per gli elementi. Impostarlo solo per gli ambienti temporanei, ad esempio il test, in quanto può causare attivazioni duplicate. Il valore predefinito è null.
GrainStorageSerializer IGrainStorageSerializer Serializzatore da utilizzare per lo stato di granularità. Il valore predefinito usa il Orleans serializzatore.
CreateMultiplexer Func<RedisStorageOptions, Task<IConnectionMultiplexer>> Fabbrica personalizzata per la creazione del multiplexer di connessione Redis.
GetStorageKey Func<string, GrainId, RedisKey> Funzione personalizzata per generare la chiave Redis per un grain. Il formato predefinito è {ServiceId}/state/{grainId}/{grainType}.

integrazione di .NET Aspire

Quando si usa .NET Aspire, è possibile integrare lo storage di grain di Redis con la risorsa Redis gestita da Aspire.

// In your AppHost project
var redis = builder.AddRedis("orleans-redis");

var orleans = builder.AddOrleans("cluster")
    .WithGrainStorage("Default", redis);

builder.AddProject<Projects.OrleansServer>("silo")
    .WithReference(orleans)
    .WaitFor(redis);
// Register the Redis client with keyed services.
// Orleans providers look up resources by their keyed service name.
// builder.AddKeyedRedisClient("orleans-redis");

builder.UseOrleans(siloBuilder =>
{
    siloBuilder.AddRedisGrainStorage(
        name: "redis",
        configureOptions: options =>
        {
            // Use the Aspire-provided connection string
            var connectionString = builder.Configuration.GetConnectionString("orleans-redis");
            options.ConfigurationOptions = ConfigurationOptions.Parse(connectionString!);
        });
});

Per scenari più avanzati, è possibile iniettare direttamente usando il delegato :

// Register the Redis client with keyed services.
// Orleans providers look up resources by their keyed service name.
// builder.AddKeyedRedisClient("orleans-redis");

siloBuilder.AddRedisGrainStorage("redis");
siloBuilder.Services.AddOptions<Orleans.Persistence.RedisStorageOptions>("redis")
    .Configure<IServiceProvider>((options, sp) =>
    {
        options.CreateMultiplexer = _ =>
        {
            // Resolve the IConnectionMultiplexer from DI (provided by Aspire)
            return Task.FromResult(sp.GetRequiredService<IConnectionMultiplexer>());
        };
    });

Persistenza granulare di Azure Cosmos DB

Azure Cosmos DB è un database relazionale e NoSQL completamente gestito per lo sviluppo di app moderne. Il Microsoft.Orleans.Persistence.Cosmos pacchetto fornisce un provider di archiviazione granulare supportato da Cosmos DB.

Configurare l'archiviazione granulare di Cosmos DB

Installare il pacchetto NuGet Orleans:

dotnet add package Microsoft.Orleans.Persistence.Cosmos

Configurare l'archiviazione granulare di Cosmos DB usando il AddCosmosGrainStorage metodo di estensione:

var builder = Host.CreateApplicationBuilder();
builder.UseOrleans(siloBuilder =>
{
    siloBuilder.AddCosmosGrainStorage(
        name: "cosmos",
        configureOptions: options =>
        {
            options.ConfigureCosmosClient(
                "https://myaccount.documents.azure.com:443/",
                new DefaultAzureCredential());
            options.DatabaseName = "Orleans";
            options.ContainerName = "OrleansStorage";
            options.IsResourceCreationEnabled = true;
        });
});

Per configurare Cosmos DB come provider di archiviazione granulare predefinito, usare AddCosmosGrainStorageAsDefault:

siloBuilder.AddCosmosGrainStorageAsDefault(options =>
{
    options.ConfigureCosmosClient(
        "https://myaccount.documents.azure.com:443/",
        new DefaultAzureCredential());
    options.IsResourceCreationEnabled = true;
});

Opzioni di archiviazione di Cosmos DB

La CosmosGrainStorageOptions classe fornisce le opzioni di configurazione seguenti:

Proprietà TIPO Predefinito Description
DatabaseName string "Orleans" Nome del database Cosmos DB.
ContainerName string "OrleansStorage" Nome del contenitore per i dati sullo stato di granularità.
IsResourceCreationEnabled bool false Quando true, crea automaticamente il database e il contenitore se non esistono.
DeleteStateOnClear bool false Indica se eliminare lo stato da Cosmos DB quando ClearStateAsync viene chiamato.
InitStage int ServiceLifecycleStage.ApplicationServices Fase del ciclo di vita del silo in cui viene inizializzata l'archiviazione.
StateFieldsToIndex List<string> Vuoto Percorsi JSON delle proprietà di stato da includere nell'indice Cosmos DB.
PartitionKeyPath string "/PartitionKey" Percorso JSON per la chiave di partizione nel contenitore.
DatabaseThroughput int? null Capacità di throughput fornita per il database. Se null, usa la modalità serverless.
ContainerThroughputProperties ThroughputProperties? null Proprietà della velocità effettiva per il contenitore.
ClientOptions CosmosClientOptions new() Opzioni passate al client Cosmos DB.

Provider personalizzato di chiavi di partizione

Per impostazione predefinita, Orleans usa l'ID granulare come chiave di partizione. Per scenari avanzati, è possibile implementare un provider di chiavi di partizione personalizzato:

public class MyPartitionKeyProvider : IPartitionKeyProvider
{
    public ValueTask<string> GetPartitionKey(string grainType, GrainId grainId)
    {
        // Custom logic to determine partition key
        return new ValueTask<string>(grainId.Key.ToString()!);
    }
}

Registrare il fornitore di chiavi di partizione personalizzate.

// Register with custom partition key provider
siloBuilder.AddCosmosGrainStorage<MyPartitionKeyProvider>(
    name: "cosmos",
    configureOptions: options =>
    {
        options.ConfigureCosmosClient("https://myaccount.documents.azure.com:443/", new DefaultAzureCredential());
    });