Skip to main content

Creating a dictionary

:::info Status Outline — expand into a full step-by-step walkthrough with a real worked example (e.g. adding a new flat dictionary and a new hierarchical dictionary to KandraWms). :::

:::tip Use the shipped skill If you're working in a scaffolded configuration with Claude Code, prefer invoking the create-dictionary skill (ships in your repo's .claude/skills/) over hand-rolling the four pieces below — it does the mechanical work for you, flat or hierarchical. Turning an existing flat dictionary into a hierarchical one later, without losing its data, is a separate skill: convert-dictionary-to-hierarchical. This page is the conceptual background the skill assumes. :::

A Dictionary is reference/master data with no posting behavior: items, warehouses, counterparties, price types. There are two shapes:

  • Flat — a plain list (e.g. Counterparty).
  • Hierarchical — supports a folder/parent structure (e.g. an ItemNode tree). Hierarchy is opt-in per dictionary, not a separate base type family.

The four pieces

(Named here after KandraWms, the platform team's own reference configuration — in your own scaffolded repo these are Acme.Domain/Acme.Forms/Acme.Application, whatever -n you scaffolded with.)

  1. *Base : DictionaryBase in KandraWms.Domain, [KandraDictionaryEntity].
  2. *Dto in KandraWms.Forms, [KandraDictionaryForm(ValidatorType=...)], redeclaring Code/Name as override.
  3. *Behavior in KandraWms.Application (OnNewAsync/OnOpenAsync/OnBeforeSaveAsync/ OnSaveAsync — dictionaries have no submit/unsubmit).
  4. *Validator in KandraWms.Forms, mandatory even if it only calls base.Validate(...).

What the generators give you for free

Controller, client DI (Refit client + service wrapper), EF Core IEntityTypeConfiguration<T>, DI registration blocks, Equals/GetHashCode/ToString. See Source Generators for which generator does which.

Gotchas worth knowing before you start

  • [Search]/[Dropdown] on a hierarchical dictionary field must target the abstract root Dto, not the leaf subclass — pointing at a leaf silently drops the field from the generated UI, and referencing the root doesn't loosen the node-selection default (still leaf-only unless you say otherwise).
  • Computed get-only properties are excluded from field discovery — if a property needs to show up in the generated UI, it needs a real backing setter path, not just a getter.

See also