---
title: "Cross-tree atomic writes - Lattice Public API Reference"
url: "https://nsta1.github.io/Orleans.Lattice/docs/lattice/api/cross-tree-atomic-writes.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/docs/lattice/api.md?plain=1#L1821-L1887"
package: "Orleans.Lattice"
version: "9.9.0"
documents: "Orleans.Lattice 9.9.0 (release line 9.9)"
built: "2026-10-04"
all-pages: "https://nsta1.github.io/Orleans.Lattice/llms.txt"
bundle: "https://nsta1.github.io/Orleans.Lattice/docs/lattice/llms-full.txt"
---
# Cross-tree atomic writes

Part of [Lattice Public API Reference](../api.md).

`IGrainFactory` / cluster-client extension methods
(`LatticeCrossTreeAtomicWriteExtensions`) that commit a batch spanning two or
more distinct `ILattice` trees all-or-nothing, with the same atomic-visibility
guarantee `SetManyAtomicAsync` gives within a single tree. A stable
`operationId` is **required** (no auto-generated overload) because a cross-tree
saga touches multiple registries and a stable idempotency key is mandatory for
safe retry; it must not be null, empty, or whitespace and must not contain `'/'`.

| Method | Signature |
|--------|-----------|
| `SetManyAtomicAsync` | `Task<CrossTreeAtomicWriteOutcome> SetManyAtomicAsync(this IGrainFactory factory, IReadOnlyList<LatticeTreeBatch> batches, string operationId, CancellationToken = default)` |
| `BeginAtomicWrite` | `LatticeAtomicWriteBuilder BeginAtomicWrite(this IGrainFactory factory, string operationId)` |

The fluent builder `LatticeAtomicWriteBuilder` accumulates per-tree slices and
commits them as one cross-tree saga:

| Method | Signature |
|--------|-----------|
| `ForTree` | `LatticeAtomicWriteBuilder ForTree(string treeId)` |
| `Set` | `LatticeAtomicWriteBuilder Set(string key, byte[] value)` |
| `Set<T>` | `LatticeAtomicWriteBuilder Set<T>(string key, T value, ILatticeSerializer<T> serializer)` |
| `Set<T>` (default serializer) | `LatticeAtomicWriteBuilder Set<T>(string key, T value)` |
| `Set` (staged CRDT) | `LatticeAtomicWriteBuilder Set(LatticeStagedCrdtWrite staged)` |
| `SetMany` (staged CRDT batch) | `LatticeAtomicWriteBuilder SetMany(IEnumerable<LatticeStagedCrdtWrite> staged)` |
| `SetWhere<T>` | `LatticeAtomicWriteBuilder SetWhere<T>(string key, T value, Expression<Func<T, bool>> predicate, ILatticeSerializer<T> serializer)` |
| `SetWhere<T>` (default serializer) | `LatticeAtomicWriteBuilder SetWhere<T>(string key, T value, Expression<Func<T, bool>> predicate)` |
| `Delete` | `LatticeAtomicWriteBuilder Delete(string key)` |
| `CommitAsync` | `Task<CrossTreeAtomicWriteOutcome> CommitAsync(CancellationToken = default)` |

`CommitAsync` (and `SetManyAtomicAsync`) returns
`CrossTreeAtomicWriteOutcome.Committed` when every tree's optional guard passed
and all writes committed, or `CrossTreeAtomicWriteOutcome.PreconditionFailed`
when a guard failed and nothing committed on any tree. Before anything is staged it throws `ArgumentException` when an entry exceeds its tree's `LatticeOptions.MaxKeyLength` or `MaxValueSizeBytes`, and `LatticeQuotaExceededException` when a tree whose slice carries an upsert has reached an enforcing `MaxLiveKeys` or `MaxEstimatedBytes` cap. It throws
`InvalidOperationException` if a write fails (after the saga compensates), or
`LatticeIdempotencyKeyMismatchException` (a subclass of `InvalidOperationException`) if
the same `operationId` is re-submitted with a different tree-set or key-set. A saga state write that loses an optimistic-concurrency (ETag) check makes the affected saga reload its durable state on a fresh activation; the call re-attaches by `operationId` up to three attempts (after 1 s and 2 s) and, if the conflict persists, throws `LatticeStateWriteFailedException` with `Conflict` set - re-submitting the same batch under the same `operationId` resumes the saga, and its durable decision is never lost or applied twice. When a slice targets a tree declared for cross-cluster replication, that slice must use the tree's declared shape - a plain `Set`/`Delete` for an `LwwRegister` tree, or a staged CRDT `Set(LatticeStagedCrdtWrite)` for a CRDT-mode tree (see [Replication modes - Single shape per tree](../../lattice.replication/replication-modes.md#single-shape-per-tree)).
Supporting public types: `LatticeTreeBatch` (per-tree slice: `TreeId`,
`Entries`, optional `Predicate`, optional `EntryDeltas`, optional
`EntryDeletes`), the `CrossTreeAtomicWriteOutcome` enum
(`Committed`, `PreconditionFailed`), and `LatticeStagedCrdtWrite` (the
client-side staging token a CRDT accessor's `Stage*` method returns; see
[CRDT value-surface accessors](crdt-value-surface-accessors.md)). See
[Atomic Writes - Cross-tree atomic writes](../atomic-writes.md#cross-tree-multi-tree-atomic-writes).

The `Delete(key)` builder method stages a retraction (tombstone) delete that
rides the all-or-nothing cross-tree batch alongside any sibling upserts, so a
re-key projection (a row moving from one view key to another) flips the upsert
at the new key and the delete at the old key as a single atomic visibility
change. On `LatticeTreeBatch` the optional `EntryDeletes` list (when non-null)
is aligned 1:1 with `Entries`: a `true` slot marks that entry a tombstone
delete; the whole list is `null` for an upsert-only slice.

The `Set(LatticeStagedCrdtWrite)` overload couples a typed CRDT mutation
(prepared by a CRDT accessor's `Stage*` method) into the cross-tree saga
so it commits all-or-nothing alongside sibling LWW writes. The
`ForTree(...)` it is added under must be the same CRDT-mode tree the
accessor was obtained from. The staged merged state is stored locally and
replicated through the prepared/terminal path, which now also carries the
staged typed delta and merge mode to the receiver: the receiver folds the
delta into its current visible state on the saga's terminal commit, so
concurrent same-key writes from multiple clusters converge by the
per-replica typed-delta union (a `+5` and a `+3` reach `8` on both
clusters), identical to the live accessor path. See
[Atomic Writes - Coupling a CRDT mutation into an atomic write](../atomic-writes.md#coupling-a-crdt-mutation-into-an-atomic-write).

Previous: [Operator tooling: orphaned-leaf repair](operator-tooling-orphaned-leaf-repair.md). Next: [CRDT value-surface accessors](crdt-value-surface-accessors.md). Contents: [Lattice Public API Reference](../api.md).
