---
title: "Persisting a Rich Domain Model With EF Core"
url: https://daily.dev/posts/persisting-a-rich-domain-model-with-ef-core-dqvtlqgt5
source_url: https://milanjovanovic.tech/blog/persisting-a-rich-domain-model-with-ef-core
type: article
source: "Milan Jovanović"
published: 2026-08-07T21:18:10.016Z
updated: 2026-08-07T21:18:43.113Z
tags: [".net", "c#", "domain-driven-design"]
reading_time: 8
upvotes: 42
comments: 1
language: 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 work with private constructors and private setters in a domain model?

EF Core materializes entities by calling the private parameterless constructor and writing to properties through their backing fields, bypassing public setters entirely. This means a fully encapsulated aggregate with private setters and a single private `Batch() { }` constructor works without any changes to the domain model. Change tracking also reads backing fields, so `SaveChanges` sees all state changes.

_Developers building DDD aggregates in .NET track EF Core mapping patterns like these on daily.dev._

### How do I map a private backing field with no public property in EF Core?

Use `builder.Property<DateTime?>("_bottledAtUtc").HasColumnName("bottled_at_utc")` in the entity configuration. EF Core maps the field directly by name. The trade-off is that LINQ filtering on that field requires `EF.Property<DateTime?>(entity, "_bottledAtUtc")` in queries. A private setter is preferable for state that queries need to filter on; field-only mapping suits state only the aggregate itself reads.

_Teams enforcing persistence ignorance in .NET find EF Core mapping edge cases like this covered on daily.dev._

### How do I map an encapsulated collection in EF Core so EF writes to the backing field not the public property?

Configure the navigation using the backing field name and set `PropertyAccessMode.Field`: `builder.HasMany<FermentationReading>("_readings").WithOne().HasForeignKey("batch_id")` followed by `builder.Navigation("_readings").UsePropertyAccessMode(PropertyAccessMode.Field).AutoInclude()`. This tells EF to read and write the private list directly, leaving the public `IReadOnlyCollection` view untouched.

_Keeping aggregates safe from partial loads is the kind of EF Core detail .NET developers follow 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.

---

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)
