The Premise
You describe what your domain model is. The compiler produces how EF Core configures it.
Entity.Dsl is an attribute-based DSL that decorates POCO classes to define EF Core entities, relationships, and lifecycle semantics. A Roslyn Source Generator reads the attributes at compile time and emits production-ready EF Core configuration, typed repositories, unit-of-work classes, and DI registration — with zero runtime reflection.
This series builds a complete online marketplace domain — stores, products, categories, orders, customers, payments — one concept at a time, showing every attribute and every line of generated code.
Part I: The Pitch
The problem with hand-written EF Core configurations. The Entity.Dsl answer: decorate POCOs, generate everything. The Generation Gap pattern. A single Product entity and its complete generated output — 15 lines in, 250 lines out.
Part II: Source Generators 101
How Entity.Dsl works under the hood. IIncrementalGenerator, ForAttributeWithMetadataName, the 4-stage discovery pipeline, emit models as DTOs, the emitter pattern, incremental caching, diagnostic reporting, and debugging techniques.
Part III: Aggregates and Composition
DDD aggregate boundaries mapped to EF Core delete behavior. [Composition] = Cascade, [Aggregation] = Restrict, [Association] = NoAction. The Order aggregate, Customer references, and Store with its Settings — lifecycle ownership expressed in one attribute.
Part IV: Value Objects and Owned Types
Three mapping strategies for non-entity types: [Owned] (OwnsOne, separate table, JSON column), [ValueObject]/[ComplexType] (ComplexProperty), and regular [Entity]. Address, Money, enums, computed columns, backing fields, and the decision matrix.
Part V: Associations and Self-References
Many-to-many done right. [AssociationClass] with payload, auto-generated composite keys, skip navigations, and IAssociationRepository. Self-referencing categories with [SelfReference] for tree structures.
Part VI: Behaviors
Cross-cutting concerns as composable attributes — Doctrine-style. [Timestampable], [SoftDeletable], [Blameable], [Sluggable], [Versionable]. Generated partial properties, SaveChanges hooks, and query filters. Zero boilerplate.
Part VII: Inheritance
One domain model, three persistence strategies. The Payment hierarchy configured as TPH, TPT, and TPC — same C# classes, one attribute change. Trade-off matrix: query performance, storage efficiency, migration complexity.
Part VIII: Customization and Escape Hatches
When the generator is not enough. PreConfigure/PostConfigure hooks, per-property overrides, partial repository extensions, IEntityListener, custom RepositoryBase hierarchy, DbContext options, multi-DbContext, the Specification pattern, and when to drop to raw Fluent API.
Part IX: The Full Domain
Everything assembled. All 14 entities with all attributes, relationships, behaviors, and inheritance. The complete Mermaid class diagram. Generated file inventory (42+ files). The DbContext, UnitOfWork, and DI wiring. End-to-end usage. Stats: 180 lines in, 2,800 lines out.
Part X: Comparison
Entity.Dsl vs hand-written Fluent API, EF Core conventions, Data Annotations, NHibernate, Dapper, and other source generators. Decision matrix: when Entity.Dsl shines, when it is overkill, and the honest cost of abstraction.
How to Read This Series
Architects evaluating the approach should read Parts I, III, IX, and X — the pitch, aggregate boundaries, the full domain, and the comparison.
Developers adopting Entity.Dsl should read Parts I and II for the fundamentals, then follow the domain build-up from III through IX.
Source Generator enthusiasts should read Part II standalone — it explains the incremental generator pipeline with real production code.
Anyone in a hurry should read Part I (the before/after) and Part IX (the assembled result).
Prerequisites
- Familiarity with C# and .NET (examples use .NET 8+)
- Basic understanding of Entity Framework Core (DbContext, Fluent API, migrations)
- Familiarity with DDD concepts (aggregates, value objects, bounded contexts) is helpful but not required — the series explains them as they arise
The Domain: Online Marketplace
Throughout this series, we build a marketplace platform with these aggregate boundaries:
| Aggregate | Entities | Key Relationships |
|---|---|---|
| Store | Store, StoreSettings | Composition (Store owns Settings) |
| Product | Product, ProductVariant | Composition (Product owns Variants), Aggregation to Store |
| Category | Category | Self-reference (Parent/Children tree) |
| Product-Category | ProductCategory | Association class (M:N with payload) |
| Customer | Customer, Address | Composition (Customer owns Address) |
| Order | Order, OrderItem | Composition (Order owns Items), Aggregation to Customer |
| Payment | Payment, CreditCardPayment, BankTransferPayment, WalletPayment | Inheritance (TPH) |
Related Posts
- Contention over Convention over Configuration over Code — the philosophical framework: why attributes beat conventions
- Injectable and DDD — how
[Injectable]integrates with generated repositories - DDD — Domain-Driven Design with the type system
- Meta-Metamodeling — M2/M3 theory behind attribute-driven generation
- From Mud to DDD — brownfield DDD migration
- CMF: The DDD DSL — the broader Content Management Framework DSL family