<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5" -->

---
title: Persisting a Rich Domain Model With EF Core | daily.dev
description: EF Core does not force anemic domain models — a fully encapsulated aggregate with private constructors, private setters, backing fields, and no public...
canonical: https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Persisting a Rich Domain Model With EF Core | daily.dev
og:description: EF Core does not force anemic domain models — a fully encapsulated aggregate with private constructors, private setters, backing fields, and no public...
og:url: https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5
og:image: https://api.daily.dev/og/posts/DqvtLqGt5.png
og:image:alt: Persisting a Rich Domain Model With EF Core
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Persisting a Rich Domain Model With EF Core

**[Milan Jovanović](https://daily.dev/sources/milanjovanovic)** · 8 min read · 42 upvotes · 1 comments

## Summary

EF Core does not force anemic domain models — a fully encapsulated aggregate with private constructors, private setters, backing fields, and no public parameterless constructor can be persisted cleanly. The post walks through mapping a `Batch` aggregate end to end: strongly typed IDs with value conversions, encapsulated collections via `PropertyAccessMode.Field`, field-only state with no property, value objects as complex types (EF Core 8+) or owned types, enum-to-string conversions, and domain events dispatched via a `SaveChangesInterceptor` without leaking into the schema. All mapping lives in `IEntityTypeConfiguration` classes, keeping the domain model free of any EF Core references.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://milanjovanovic.tech/blog/persisting-a-rich-domain-model-with-ef-core>

## Questions this post answers

### Can EF Core persist an entity with a private constructor and private setters?

Yes. EF Core materializes entities by calling the private parameterless constructor directly and writing to properties through their backing fields, bypassing public setters entirely. It can also bind a parameterized constructor by matching parameter names and types to mapped properties, though navigation properties like collections still require the parameterless constructor since they can't be constructor-bound.

_Developers designing rich domain models can track EF Core mapping techniques like these on daily.dev._

### How do you map a private field with no public property in EF Core, like a bottled date that only the aggregate needs internally?

Use the string-based Property configuration method, for example builder.Property<DateTime?>("_bottledAtUtc").HasColumnName("bottled_at_utc"), which maps the backing field directly without ever exposing a public property. The tradeoff is that filtering on it in LINQ requires EF.Property<DateTime?>(b, "_bottledAtUtc") instead of a normal property reference.

_Modeling invariant-heavy aggregates gets easier when developers compare EF Core mapping patterns on daily.dev._

### What is the difference between complex types and owned types for mapping value objects in EF Core?

Complex types store a value object's members inline in the owner's table with no separate identity or join, and became fully capable in EF Core 10 after complex types debuted with limitations in EF Core 8 (no optional properties or collections). Owned types work on EF Core 8 and 9 too, but carry a hidden shadow key because EF treats them internally as entities pretending to be values.

_Choosing between complex types and owned types is easier when developers weigh EF Core version tradeoffs on daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@agustinbarrientos** · 0 upvotes

> My default would leave `AutoInclude` off unless every invariant truly needs the full collection. Large aggregates can turn a clean model into an expensive query without anyone noticing.

## Similar posts on daily.dev

- [Rich Domains: How to Use DDD for More Sustainable Systems](https://daily.dev/posts/rich-domains-how-to-use-ddd-for-more-sustainable-systems-bufcaincm) · Telerik · 2 upvotes · 0 comments
- [EF Core Bulk Insert: The Complete Guide to Inserting Thousands of Rows Without the Wait - Chris Woody Woodruff](https://daily.dev/posts/ef-core-bulk-insert-the-complete-guide-to-inserting-thousands-of-rows-without-the-wait---chris-wood-y8ogyumi6) · We Are .NET · 0 upvotes · 0 comments
- [Second-Level Caching in EF Core 10: The Complete Guide](https://daily.dev/posts/second-level-caching-in-ef-core-10-the-complete-guide-bvdnj7ehj) · Code with Mukesh · 0 upvotes · 0 comments

---

Tags: [#.net](https://daily.dev/tags/.net), [#c#](https://daily.dev/tags/c#), [#domain-driven-design](https://daily.dev/tags/domain-driven-design)

[View this post on daily.dev](https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Persisting a Rich Domain Model With EF Core","url":"https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5"},"datePublished":"2026-08-07T21:18:10.016Z","dateModified":"2026-09-14T08:32:14.879Z","description":"EF Core does not force anemic domain models — a fully encapsulated aggregate with private constructors, private setters, backing fields, and no public...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/0579fa55275652656123864f81e02b30?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/0579fa55275652656123864f81e02b30?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Milan Jovanović","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Milan Jovanović","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/78d8abfb692746538410dee47f93e566","url":"https://daily.dev/sources/milanjovanovic"},"commentCount":1,"discussionUrl":"https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":42},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":1}],"keywords":".net,c#,domain-driven-design","timeRequired":"PT8M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Milan Jovanović","item":"https://daily.dev/sources/milanjovanovic"},{"@type":"ListItem","position":3,"name":"Persisting a Rich Domain Model With EF Core"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5","comment":[{"@type":"Comment","text":"My default would leave AutoInclude off unless every invariant truly needs the full collection. Large aggregates can turn a clean model into an expensive query without anyone noticing.","datePublished":"2026-08-08T15:56:14.700Z","url":"https://daily.dev/posts/DqvtLqGt5#c-dWTUeSkBA","author":{"@type":"Person","name":"Agustin Barrientos","url":"https://daily.dev/agustinbarrientos","image":"https://media.daily.dev/image/upload/s--5ayxQnqn--/f_auto/v1788281802/avatars/avatar_wQYYVe5Tbj0NJ7C7qPoa8?_a=BAMAMicg0"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5#faq","mainEntity":[{"@type":"Question","name":"Can EF Core persist an entity with a private constructor and private setters?","acceptedAnswer":{"@type":"Answer","text":"Yes. EF Core materializes entities by calling the private parameterless constructor directly and writing to properties through their backing fields, bypassing public setters entirely. It can also bind a parameterized constructor by matching parameter names and types to mapped properties, though navigation properties like collections still require the parameterless constructor since they can't be constructor-bound. Developers designing rich domain models can track EF Core mapping techniques like these on daily.dev."}},{"@type":"Question","name":"How do you map a private field with no public property in EF Core, like a bottled date that only the aggregate needs internally?","acceptedAnswer":{"@type":"Answer","text":"Use the string-based Property configuration method, for example builder.Property<DateTime?>(\"_bottledAtUtc\").HasColumnName(\"bottled_at_utc\"), which maps the backing field directly without ever exposing a public property. The tradeoff is that filtering on it in LINQ requires EF.Property<DateTime?>(b, \"_bottledAtUtc\") instead of a normal property reference. Modeling invariant-heavy aggregates gets easier when developers compare EF Core mapping patterns on daily.dev."}},{"@type":"Question","name":"What is the difference between complex types and owned types for mapping value objects in EF Core?","acceptedAnswer":{"@type":"Answer","text":"Complex types store a value object's members inline in the owner's table with no separate identity or join, and became fully capable in EF Core 10 after complex types debuted with limitations in EF Core 8 (no optional properties or collections). Owned types work on EF Core 8 and 9 too, but carry a hidden shadow key because EF treats them internally as entities pretending to be values. Choosing between complex types and owned types is easier when developers weigh EF Core version tradeoffs on daily.dev."}}]}
```

