Mappings
An entity mapping is Wallaby's unit of routing: it declares how one entity type is captured, transformed and delivered to the destination in a sink. Mappings are declared per sink inside WithMappings(...):
sink.Map<Product>()
.ToDestination("products")
.WithBackfillVersion("v1")
.UsingTransform(/* ... */);A mapping's core components:
- Entity:
Map<T>()declares the backing table for capture and routes its changes to the enclosing sink. The same entity may be mapped under several sinks as each mapping runs its own transform. - Destination:
ToDestination(...)names where documents land within the sink: a search index, a table, an endpoint route - whatever the sink maps it to. - Transform:
UsingTransform(...)specifies how a batch of entity changes into the destination documents. See below. - Backfill version:
WithBackfillVersion(...)re-snapshots the table when the version changes, so destinations are rebuilt whenever the output shape changes. See Backfill. - Provider extensions:
DependsOn(...)re-emits an entity when a related table changes, and per-row scoping routes each row through tenant-specific contexts and destinations.
Transforms
A transform turns the changes for one entity type into the documents you want in a destination. For trivial shaping, pass a lambda:
sink.Map<Product>()
.ToDestination("products")
.UsingTransform((_, changes, _) =>
{
var docs = new Dictionary<DocumentKey, WallabyDocument?>(changes.Count);
foreach (var c in changes)
docs[c.Key] = new WallabyDocument { ["name"] = c.Entity!.Name };
return Task.FromResult<IReadOnlyDictionary<DocumentKey, WallabyDocument?>>(docs);
});For more complex transforms, or anything with dependencies, implement your provider's transform interface as a class - IWallabyEfTransform<T> (EF Core) or IWallabyMartenTransform<T> (Marten) - and register it with UsingTransform<TEntity, TTransform>(); the class is resolved from the container.
Mapping classes
Inline mappings grow the AddWallaby callback by a block per entity per sink and can make your Program.cs unwieldy. Move each mapping into a class implementing IWallabyEntityMapping<TEntity>. This is also convient if you want to store your mappings alongside the transform it wires up:
public sealed class ProductSearchMapping : IWallabyEntityMapping<Product>
{
public void Configure(EntityMapBuilder<Product> map) => map
.ToDestination("products")
.WithBackfillVersion("v1")
.UsingTransform<Product, ProductSearchTransform>();
}Apply it by type:
.WithMappings(sink => sink
.Apply<ProductSearchMapping>()
.Apply<CategorySearchMapping>());For a mapping that needs constructor arguments, pass a configured instance: sink.Apply(new ProductSearchMapping(indexName)).
Internals
Transforms are batch-invoked: you receive all the insert/update/read changes for the entity in a commit (or a backfill chunk) and return one document per source key. This lets you resolve many keys in a single round-trip. Return a null document (or simply omit a key) to delete that key from the sink.
TIP
Deletes never reach a transform as the row is already gone. The engine deletes by key directly, using the mapping's id rule. Your transform only sees inserts, updates, and backfill reads.
Documents
A document is a WallabyDocument, simply a field bag keyed by destination field name. It derives from Dictionary<string, object?>, so it supports the usual initializer syntax alongside a slightly more fluent one:
var doc = new WallabyDocument { ["name"] = product.Name, ["price"] = product.Price };
// or fluent:
var doc2 = new WallabyDocument().Set("name", product.Name).Set("price", product.Price);Sinks consume the document as an IReadOnlyDictionary<string, object?>.
The change event
Each ChangeEvent<TEntity> exposes:
| Member | Description |
|---|---|
Entity | The current row materialized as TEntity (non-null for insert/update/read). |
Record | Current column values keyed by EF property name. |
Changes | Previous values of changed columns (updates), subject to REPLICA IDENTITY. |
PrimaryKey / Key | The source primary key, and its DocumentKey. |
GetPrimaryKey<TKey>() | The single-column key cast to TKey. |
Metadata | Action, IsBackfill, CommitTimestamp, CommitLsn, table name. |
Per-row scoping
When the enrichment context or the destination depends on the row's own data (e.g. a TenantId), see multi-tenancy for EF Core (ScopedBy / UseScopedDbContext / ScopedDestination) or Marten (ScopedByTenant / UseTenantSessions).