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
AddLatticecallback configures the grain-storage provider Lattice uses for its tree state. The silo also requires anIWalStorageProviderfor the write-ahead log;AddLatticeregisters 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 theAddAzureTableWalStorageextension shipped by theOrleans.Lattice.Storage.AzureTablepackage and theAddWalStorageseam 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.Replicationpackage viaAddLatticeReplication. Peer membership (who this silo ships its WAL to) is configured throughLatticeReplicationOptions.ReplicationPeersor a customIReplicationTopologyregistration - both are documented in Replication drivers: Peer configuration. The coreLatticeOptionssurface 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.