Source Generator
DataLinq's source generator is not only a boilerplate reducer. In current DataLinq it is part of the runtime contract.
Generated database models implement IDatabaseModel<TDatabase>, provide complete generated metadata, install generated metadata back into the database type, and generate table/model helpers that the runtime expects to exist. Stale generated output should fail early rather than silently falling back to reflection-heavy startup.
Generated Surface
For a configured database, the generator produces:
- a partial database model implementing
IDatabaseModel<TDatabase> GetDataLinqGeneratedMetadata()returning a generated metadata draftSetDataLinqGeneratedMetadata(...)for binding finalized runtime metadata back to the generated type- immutable model classes
- mutable model classes
- relation accessors and mutation helpers
- generated static primary-key lookup helpers
- provider-key row-store accessors for scalar and composite primary keys
- generated
DataLinqPrimaryKeystructs for composite primary-key shapes - indexed value, mutable, and relation handles where those avoid runtime lookup
The practical result is simple: normal runtime startup should consume generated metadata, not rediscover model shape from ordinary runtime reflection.
Runtime Startup Contract
flowchart TD
A["Generated database type"] --> B["GetDataLinqGeneratedMetadata()"]
B --> C["MetadataDefinitionFactory"]
C --> D["Frozen DatabaseDefinition"]
D --> E["SetDataLinqGeneratedMetadata(...)"]
E --> F["Provider startup and runtime execution"]
Provider construction calls the generated metadata hook, builds a finalized DatabaseDefinition through MetadataDefinitionFactory, and binds that finalized metadata back to the generated database model.
If the generated type is missing the complete metadata hook, returns invalid metadata, or cannot bind the finalized metadata, DataLinq reports a model failure. That behavior is intentional. Hiding stale generated output behind a compatibility fallback makes package upgrades and AOT/trimming claims much harder to trust.
Generation Workflow
1. Model Discovery
The generator uses Roslyn to find candidate model declarations. DataLinq models are source-defined abstract types that describe tables and views through model interfaces and attributes.
The generator collects:
- database model declarations
- table and view model declarations
- scalar value properties
- relation properties
- attributes such as table names, column names, keys, defaults, comments, checks, cache settings, and relations
- using directives and C# type information needed to emit valid generated code
2. Metadata Construction
Source metadata is converted into typed metadata drafts and then validated through MetadataDefinitionFactory.
That factory boundary matters. It centralizes construction, validation, finalization, and freezing of runtime metadata instead of letting scattered mutable metadata objects become the source of truth.
The finalized metadata model carries:
- database, table, view, column, index, relation, and cache policy metadata
- provider/model key shape details
- frozen lookup maps for common table and column resolution
- generated declaration metadata
- provider-key row-store accessor hooks
- scalar-converter slots reserved for future converter work
3. Source Emission
After metadata construction, the generator emits the database file and one generated file per table/view model.
Generated source starts with the same stable DataLinq banner and nullable directive used by CLI-written generated files. Source-generator output is deterministic: it does not include timestamps or CLI version stamps.
Generated model code is responsible for the repetitive but important parts:
- immutable and mutable property surfaces
- constructor and factory paths
- relation properties
Mutate,Save,Insert, andUpdatehelper methods- generated direct
Get(...)methods for primary-key lookup - provider-key extraction from row data, readers, dynamic keys, and model instances
- generated metadata drafts for runtime startup
The generator follows nullable context from source declarations. A model file with #nullable enable produces generated output with nullable reference annotations and #nullable enable, even if the project default is nullable-disabled. A model file with #nullable disable opts that generated table output out. When no declaration-level context is available, generation falls back through the database declaration and then the project nullable setting.
Diagnostics and Partial Output
Metadata and rendering failures are reported as focused diagnostics where possible. Aggregate failures are flattened into individual diagnostics so users can fix more than one independent source issue per compile.
The generator still emits valid generated table files for unaffected database/table scopes when it can do so without lying about the runtime metadata graph. If a table render fails, the generator suppresses the database metadata bootstrap for that database, because a partial bootstrap would make runtime startup look complete when it is not.
4. Runtime Use
Runtime systems use generated hooks instead of guessing:
- providers build metadata through the generated database contract
InstanceFactoryuses generated metadata and instance hooks- row caches use generated provider-key accessors where available
- relation traversal uses generated relation/key handles
- query materialization reads provider primary-key values and asks the relevant table cache for rows
Platform Boundary
Roslyn and source parsing belong at build time. Runtime packages should not carry compiler assemblies as runtime dependencies. Current public compatibility wording depends on that split.
This does not make every DataLinq query AOT-safe. The query pipeline still uses Remotion.Linq, and the public AOT/WebAssembly claim remains the narrow generated SQLite smoke boundary described in Platform Compatibility.
Maintenance Rule
When a runtime feature needs model shape, prefer a generated hook or finalized metadata handle over new runtime reflection. Reflection-heavy discovery in normal provider startup is a regression unless it is explicitly a diagnostic or tooling-only path.