Table of Contents

Setup

This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at setup.md, and llms.txt lists every page.

Part of Lattice Public API Reference.

Install the NuGet package:

dotnet add package Orleans.Lattice

Import the namespace:

using Orleans.Lattice;

Register Lattice on the silo, providing a grain-storage provider. The callback receives the silo builder and the provider name that Lattice grains will resolve against; register any Orleans grain-storage provider under that name.

In-memory (development / tests):

siloBuilder.AddLattice((silo, storageName) =>
    silo.AddMemoryGrainStorage(storageName));

Azure Table Storage (production). Requires the Microsoft.Orleans.Persistence.AzureStorage and Orleans.Lattice.Storage.AzureTable NuGet packages. The first configures the grain-storage provider for tree state; the second replaces the default in-memory WAL with a durable Azure Table-backed provider:

using Azure.Data.Tables;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Lattice.Storage.AzureTable;

var connectionString = "UseDevelopmentStorage=true";

siloBuilder.AddLattice((services, storageName) =>
{
    services.AddAzureTableGrainStorage(storageName, options =>
    {
        options.TableServiceClient = new TableServiceClient(connectionString);
    });
});

siloBuilder.AddAzureTableWalStorage(o =>
{
    o.ConnectionString = connectionString;
});

Managed-identity deployments can either configure each extension independently against the same storage account, or - for the canonical "one credential, one client" shape - share a single pre-built TableServiceClient between them: AddAzureTableGrainStorage accepts a pre-built TableServiceClient(serviceUri, credential) via its options.TableServiceClient slot, and AddAzureTableWalStorage accepts the same instance via options.ServiceClient. Hosts that prefer per-extension wiring can instead configure AddAzureTableWalStorage with ServiceUri + TokenCredential (e.g. new DefaultAzureCredential()) directly on its options object. See WAL Storage Providers for the full set of WAL authentication modes.

The AddLattice callback configures the grain-storage provider Lattice uses for its tree state. The silo also requires an IWalStorageProvider for the write-ahead log; AddLattice registers the in-memory provider by default (suitable for development and single-process tests), and hosts that need durability across silo restarts must replace it with a persistent provider before going to production. See WAL Storage Providers for the AddAzureTableWalStorage extension shipped by the Orleans.Lattice.Storage.AzureTable package and the AddWalStorage seam for hosting custom providers.

Per-tree options are configured via ConfigureLattice (see Configuration for the full options reference and per-tree override semantics):

siloBuilder.ConfigureLattice("my-tree", o =>
{
    o.CacheTtl = TimeSpan.FromMilliseconds(100);
    o.HotShardOpsPerSecondThreshold = 500;
});

Cross-cluster replication is layered on top of the core library by the Orleans.Lattice.Replication package via AddLatticeReplication. Peer membership (who this silo ships its WAL to) is configured through LatticeReplicationOptions.ReplicationPeers or a custom IReplicationTopology registration - both are documented in Replication drivers: Peer configuration. The core LatticeOptions surface does not carry peer state.

Structural sizing (MaxLeafKeys, MaxInternalChildren, ShardCount) is not configured here. Those values are pinned per-tree in the registry. See Tree Sizing for how to set them on a new or existing tree.

For a runnable end-to-end example, see Samples.

Basic usage

Once Lattice is registered on the silo, resolve an ILattice grain from IGrainFactory using the tree's logical name as the string key:

// Resolve the tree (idempotent - the same name always routes to the same tree).
var tree = grainFactory.GetGrain<ILattice>("my-tree");

// Write a value.
await tree.SetAsync("user:1", "Alice"u8.ToArray());

// Read it back (returns null when absent or tombstoned).
byte[]? value = await tree.GetAsync("user:1");

// Conditional write - insert only if the key is not already present.
byte[]? existing = await tree.GetOrSetAsync("user:1", "Bob"u8.ToArray());

// Delete (tombstones the key; returns true if it was live).
bool deleted = await tree.DeleteAsync("user:1");

// Stream a key range in strict lexicographic order.
await foreach (var key in tree.ScanKeysAsync(startInclusive: "user:", endExclusive: "user;"))
{
    Console.WriteLine(key);
}

Keys are string; values are byte[]. For typed payloads (POCOs, records, DTOs) use the serializer-aware overloads in TypedLatticeExtensions; they accept any T and default to JsonLatticeSerializer<T>:

await tree.SetAsync("user:1", new User("Alice", 30));
var user = await tree.GetAsync<User>("user:1");

For the full set of runtime and maintenance operations, see ILattice below.