---
title: "OR-Map (Observed-Remove Map)"
url: "https://nsta1.github.io/Orleans.Lattice/docs/crdt/ormap.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/docs/crdt/ormap.md"
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/crdt/llms-full.txt"
---
# OR-Map (Observed-Remove Map)

Part of the [CRDTs documentation](readme.md).

`tree.OrMap<TKey,TValue>(key)` -> `OrMapAccessor<TKey,TValue>`, merge mode `LatticeMergeMode.OrMap`.

## Semantics

An **OR-Map** is a dictionary that is a CRDT twice over. Its **keys** follow
add-wins [OR-Set](orset.md) semantics, and each **value** is itself a CRDT that
is merged **recursively** per key. So when two replicas write the same map key
concurrently, the values are not overwritten - they are folded together through
the value type's own merge.

`TValue` must implement `ICrdt<TValue>` and have a parameterless constructor;
use any built-in primitive or your own. Because the wire shape is generic, the
map's `(TKey, TValue)` pair must be registered on the host via `AddOrMapShape`
before the silo starts - once for each tree that holds OR-Maps, and every OR-Map
in that tree shares that one shape.

Use it for: per-user scores, per-shard aggregates, per-item metadata - a keyed
collection where each entry must converge, not just the key set.

## Behaviour

Figure: Two concurrent OR-Map writes to one key, merged value by value. An order diagram shaped like a diamond. At the bottom the map is empty. Cluster A increments the PN-Counter under the key alice, so its entry holds P[A]=1. Cluster B, at the same time, increments the same key's counter, so its entry holds P[B]=1. Neither state is above the other. Merging keeps the key, add-wins, and merges the two counters as a PN-Counter, so both paths rise to the same top state: alice holds P[A]=1 and P[B]=1, value 2.

Both clusters hold alice with P[A]=1 and P[B]=1: value 2. The values were folded together through the counter's own merge, not overwritten.

Keys merge with add-wins OR-Set semantics, and each value merges recursively through its own CRDT.

```mermaid
graph TD
    subgraph "Cluster A map"
      A["alice -> PnCounter(1)"]
    end
    subgraph "Cluster B map"
      B["alice -> PnCounter(1)"]
    end
    A -->|"key merge: add-wins"| M["alice -> merge(values)"]
    B -->|"value merge: recursive ICrdt"| M
    M --> R["alice -> PnCounter(2)"]
```

## Example

First, register the map's shape on the host for the tree that will hold it (once,
at startup):

```csharp verify
// One-time host wiring: OR-Maps stored in the "polls" tree map string keys to
// PN-Counter values.
siloBuilder.AddOrMapShape<string, PnCounter>("polls");
```

If this registration is missing, an OR-Map write to the tree raises
`LatticeCrdtShapeNotRegisteredException` (a subclass of `InvalidOperationException`)
rather than silently mis-dispatching; the API bindings surface it as a
client-error precondition (for example gRPC `FailedPrecondition`). The closed-shape
primitives never need this - they resolve through the global registry fallback.

Then read and write it through the typed accessor:

```csharp verify
// A map of per-candidate vote tallies, stored under one key of the "polls" tree;
// each value is itself a PN-Counter.
var polls = grainFactory.GetGrain<ILattice>("polls");
var votes = polls.OrMap<string, PnCounter>("election-2026");

// Cluster A records a vote for "alice" by advancing a PN-Counter value.
var tallyA = new PnCounter();
tallyA.Increment("cluster-A");
await votes.SetAsync("alice", "cluster-A", tallyA, cancellationToken);

// A concurrent vote for the same candidate on another cluster does NOT
// overwrite A's write - the two PN-Counter values merge recursively per key.
// Reading back returns the merged counter for that key.
PnCounter? tally = await votes.GetValueAsync("alice", cancellationToken);
long aliceVotes = tally?.Value ?? 0;
```

See also: the value primitives [PN-Counter](pncounter.md) and
[OR-Set](orset.md), and the [CRDT overview](readme.md).
