API cheat sheet
Use this page when you know the scenario you want and need to find the API shape. For the reasoning and worked examples, follow the corresponding guide.
You want / you write
| You want | Write |
|---|---|
| Start a test | TestBiome.Start(out var seed) / TestBiome.Start(_db, out var seed, time) |
| Reuse a baseline recipe per test | Start and Add common parents in the instance constructor; extend the instance builder in each test. See Constructor setup. |
| Root blueprint | Blueprint.For<TSeed, T>(ctx => new() { … }) |
| Child blueprint | Blueprint.ChildOf<TSeed, TParent, T>((parent, ctx) => new() { … }) |
| Generated Add methods | [BiomeGraft] catalog → {Catalog}Grafts; [Graft("Name", Plural = "Names")] / [NoGraft] |
| Root built from an input | Blueprint.FromInput<TSeed, TInput, T>(() => new(), (input, ctx) => …) |
| Child built from an input | Blueprint.ChildFromInput<TSeed, TParent, TInput, T>(() => new(), (parent, input, ctx) => …) |
| Variant | bp.With(trait) / bp.With((e, ctx) => …) |
| Derived default (fill-only) | bp.Derive(e => e.Prop, (e, ctx) => …) |
| Sequence | Sequence.Of(1, n => n + 1) + .PerSession() / .PerParent() / .Per<T>(); bp.Derive(e => e.Prop, seq) |
| Never save | bp.AsTransient() (blueprint) / .Transient() (most recent step) |
| One / many | AddOne(bp, out var x[, c]) / Add(bp, out var xs[, n | c1, c2…]) |
| Jump back | .Under(entity) / .Under(array) |
| Context | ctx.Seed, ctx.Now, ctx.Ago(t), ctx.FromNow(t), ctx.NewId("p"), ctx.Index, ctx.Next(seq), ctx.Ancestor<T>(), ctx.All<T>(), ctx.Last<T>() |
| Child context | + ctx.Parent, ctx.ParentIndex |
| Inspect | biome.All<T>(), biome.Last<T>() (keep var biome = TestBiome.Start(…)) |
| Options | SeedEntities, Default<TBase, TProp>, BeforeSave, AfterSave, TimePrecision, Ids |
| Save | await biome.BuildAsync(ct) once; stores: NullStore, EfCoreStore, your IBiomeStore |
Entry point
| API | Notes |
|---|---|
Biome.Start<TSeed>(seed, options, store = null, time = null) | Returns BiomeBuilder<TSeed, TSeed>. Freezes the options. null store = NullStore; null time = frozen clock. |
BiomeBuilder<TSeed, TAnchor>
| Member | Meaning |
|---|---|
Add(bp, out T[] created) | One default entity. |
Add(bp, out T[] created, int count) | count default entities (per parent for children). |
Add(bp, out T[] created, params Action<T>[] customize) | One entity per customizer. Comma = new entity, + = stacked traits. |
Add(bp, out T[] created, params Action<T, BiomeContext<TSeed>>[] customize) | Context-aware form. Children receive ChildContext<TSeed, TParent>. |
AddOne(bp, out T created, Action<T>? customize = null) | Exactly one. A child AddOne needs exactly one parent. |
AddOne(bp, out T created, Action<T, ctx> customize) | Exactly one, context-aware. |
Under(parent) / Under(parents) | Re-anchor to an earlier output or seed row. Adds nothing. |
Transient() | Exclude the most recent Add step from the save. |
BuildAsync(ct = default) | Save once. A second call throws. |
All<T>(), Last<T>() | Inspect entities created so far (include transients and aggregate wrappers, exclude seed rows). |
Session, Anchor | The shared session and the current anchor. |
All Add overloads exist for Blueprint, ChildBlueprint, InputBlueprint and ChildInputBlueprint. For the input forms, customizers act on TInput, while out and the new anchor hold the output T. Every Add returns BiomeBuilder<TSeed, T[]>.
Generated grafts
Mark a supported catalog with [BiomeGraft]. The generator emits a sibling {Catalog}Grafts static class in the same namespace, with the catalog's accessibility. The catalog must be a top-level, non-generic, non-file-local static class; partial is not required. [Graft] and [NoGraft] apply to fields or properties. Eligible members are public, internal, or protected-internal non-const static readonly fields and static getter-only properties with no setter, whose type is one of the four blueprint types. Traits, sequences, private members, mutable fields, and unrelated types are skipped. Generated method accessibility follows the member.
Each eligible member with valid names gets three methods:
| Generated method | Behavior |
|---|---|
AddName(out T value, Action<T>? customize = null) | One output; the optional callback customizes it. |
AddNames(out T[] values, int count) | The requested number of default outputs. |
AddNames(out T[] values, params Action<T>[] customize) | One output per callback; with no callbacks, one default output. |
For InputBlueprint and ChildInputBlueprint, Action<T> above is Action<TInput>; out and the returned builder still use output type T. Root and root-input methods are generic in TAnchor and work after any anchor with the same seed; their count or customizer list applies once at the current anchor. Child and child-input methods accept only BiomeBuilder<TSeed, TParent[]> and fan out for each parent. A child count or customizer applies to each parent. All generated calls take out, return BiomeBuilder<TSeed, T[]>, and re-anchor on the output array. Singular child calls require exactly one parent at runtime; a plural call with no customizers creates one default per parent (or one for a root).
By default the catalog member name supplies the singular suffix and a simple pluralization rule supplies the plural suffix. [Graft("Name", Plural = "Names")] overrides either suffix, and [NoGraft] excludes a field or property. The Graft values must be valid C# identifiers. Grafts do not generate context-aware callbacks: use core Add / AddOne with the blueprint for a one-off callback that needs context.
| Diagnostic | Severity | Meaning |
|---|---|---|
BIOME001 | Warning | Catalog members produce overlapping graft signatures on the same builder; rename one with [Graft("Name")]. |
BIOME002 | Warning | The singular and plural suffixes match; set a distinct Plural. |
BIOME003 | Error | The annotated catalog is not a supported top-level static class. |
BIOME004 | Error | A singular or plural suffix is not a valid C# identifier. |
FromInput or ChildFromInput; this page does not identify a public release for those APIs. See Input blueprints.Blueprints
| Type | Members |
|---|---|
Blueprint (static) | For, ChildOf, FromInput, ChildFromInput |
Blueprint<TSeed, T> | With(Action<T>), With(Action<T, BiomeContext<TSeed>>), Derive(prop, value), Derive(prop, sequence), AsTransient() |
ChildBlueprint<TSeed, TParent, T> | Same, with Action<T, ChildContext<TSeed, TParent>>. |
InputBlueprint<TSeed, TInput, T> | Same, over TInput (Action<TInput> / Action<TInput, BiomeContext<TSeed>>). T is any reference type; an IBiomeAggregate output is saved as its parts. |
ChildInputBlueprint<TSeed, TParent, TInput, T> | Same, over TInput, with Action<TInput, ChildContext<TSeed, TParent>>. The factory receives (parent, input, ctx). |
IBiomeAggregate | IEnumerable<object> GetEntities(): the parts to save, in order. May return nested aggregates. |
Inputs are construction data. They are not recorded, saved, or usable with Under, and All<TInput>() is empty.
Sequences
| API | Meaning |
|---|---|
Sequence.Of(start, next) | Session-scoped sequence. |
.PerSession() | One counter per test (default). |
.PerParent() | One counter per immediate parent (by reference). |
.Per<TAncestor>() | One counter per nearest ancestor of that type (by reference). |
BiomeOptions<TSeed>
| Member | Meaning |
|---|---|
SeedEntities(seed => IEnumerable<object>) | Rows to save before created entities. They count as owned, so .Under(seedRow) works. |
Default<TBase, TProp>(e => e.Prop, (e, ctx) => value) | Fill-only default on entities matching a class or interface. |
BeforeSave((save, ct) => ValueTask) | See and change the final row list. |
AfterSave((save, ct) => ValueTask) | Runs after a successful save. |
TimePrecision(TimeSpan) | Default 1 µs. TimeSpan.Zero disables rounding. |
Ids(prefix => string) | Override ctx.NewId. Default prefix_<uuidv7>. |
Options are fluent, safe to share across tests, and frozen on first Biome.Start: later changes throw InvalidOperationException.
Stores
| Type | Package | Meaning |
|---|---|---|
IBiomeStore | Zeeq.Biome | ValueTask SaveAsync(IReadOnlyList<object> entities, CancellationToken) |
NullStore.Instance | Zeeq.Biome | Saves nothing. The default. |
EfCoreStore(DbContext) | Zeeq.Biome.EntityFrameworkCore | AddRange in order, one SaveChangesAsync. |
db.AsBiomeStore() | Zeeq.Biome.EntityFrameworkCore | Extension that returns new EfCoreStore(db). |
Pipeline order
For each entity in an Add step:
- Factory creates it (IDs here).
- Traits (
.With), in declaration order. - Call-site customizers.
Derives, fill-only, in declaration order. Sequences first reserve every explicit value in the step.- Global defaults (
options.Default), fill-only. - Entity, parent link and step are recorded.
For an input blueprint, the order is: every input is created and gets its traits and call-site customizers; Derive(prop, sequence) reserves explicit values across the whole step; then, for each input in order, its Derives run and the factory builds the output; finally steps 5 and 6 apply to each output.
BuildAsync then:
- Takes the seed rows, followed by created entities in creation order.
- Expands aggregates.
- Drops transients and removes duplicate references.
- Runs
BeforeSave. - Calls
IBiomeStore.SaveAsync. - Runs
AfterSave, if the save succeeded.
Common errors
| Symptom | Cause | Fix |
|---|---|---|
CS1929 … requires a receiver of type 'BiomeBuilder<…, Parent[]>' | The anchor isn't the child's parent type. | Add the parent first, or .Under(parent). Use a scenario helper. |
CS1503: cannot convert from 'lambda expression' to 'out X' | Missing out. | AddX(out _, x => …) |
CS1593: Delegate 'Action<X>' does not take 2 arguments | Mixed lambda arities, or a (x, ctx) lambda passed to a graft that has no context overload. | Write all in (x, ctx) form and use the core Add / AddOne, or add a context overload. |
CS8852: Init-only property … | The property is init. | Customize an input blueprint instead, or make the property settable. |
A singular child AddOne requires exactly one parent | The anchor holds several (or zero) parents. | Use the plural Add, or .Under(repos[0]). |
No ancestor of type X exists… | The chain skipped X. | Build the full chain or use a scenario helper. |
BuildAsync may only be called once… | Two builds, or an Add after the build. | One BuildAsync per session. |
Biome options are frozen… | A test changed shared options. | Configure options once in TestBiome. |
Parent at index N … is not owned by this biome session | .Under got an object Biome didn't build or select. | Use an Add output or a SeedEntities row. |
This biome session is faulted… | An earlier Add threw. | Fix the first error. |
| Duplicate key on a number | An explicit value was set in a later step. | Set it in the same step as the defaults, or earlier. |
| Timestamp differs by ticks after reading back | DateTimeOffset.UtcNow was used. | Use ctx.Now / ctx.Ago, or a shared FakeTimeProvider. |