DDD with EF Core: Best practice for adding child entities to an Aggregate Root
07:42 08 Jan 2026

I’m refactoring a project to use rich domain modeling and DDD by modeling Aggregate Roots (ARs) and child entities. I’m also creating repositories for each AR to prevent database access from leaking into other classes. However, I’m running into some design issues.

I have a ProfileGroup aggregate root, which consists of authorizations that can later be assigned to users. The ProfileGroup AR contains authorizations like this:

private List _authorizations = new();

public IReadOnlyCollection Authorizations => _authorizations;

When a profile group is defined, it can have authorizations that belong to a specific application. The application is held by the profile group, so when a new authorization is added to a profile group, I need to validate that the authorization belongs to the correct application.

Authorizations themselves are not created by users. They are predefined and seeded into the database via migrations. Each profile group can have up to ~100 authorizations. The profile group is responsible for keeping these authorizations in a valid state (e.g., ensuring the authorization belongs to the correct application).

Previously, the authorization-adding logic looked roughly like this (pseudo-code):

  • Check if the profile group exists: _context.ProfileGroups.AnyAsync(x => x.Id == id)

  • Check if the authorization exists: _context.ProfileGroupAuthorizations.FirstOrDefaultAsync(x => x.Id == id) If it does not exist, create it If it exists, update it

  • Add the authorization via: _context.ProfileGroupAuthorizations.Add(auth)

After reading Eric Evans’ Domain-Driven Design, I saw that:

Only AGGREGATE ROOTS can be obtained directly with database queries. All other objects must be found by traversal of associations.

Based on this, I implemented a ProfileGroupRepository that fetches the aggregate root only. I carried CRUD operations to domain itself and encapsulated the logic via methods that modifies private _authorizations list like

public void AddAuthorization()
public void RemoveAuthorization()

This works well in theory. However, I’m unsure about one important point. When fetching the aggregate root, should I always load the entire aggregate and then perform the operation, or is it acceptable to load only the child entities I need, for example:

ProfileGroup? profileGroup = await _context.ProfileGroups .Include(x => x.Authorizations.Where(a => a.Id == authorizationId)) .FirstOrDefaultAsync(x => x.Id == profileGroupId);

To preserve consistency, should I instead always load the full aggregate? The concern is that I’m using the outbox pattern to distribute these changes to N downstream databases, and loading all child entities every time feels like overkill.

To enforce DDD rules I understood that I should not directly query or persist child entities outside the aggregate root. However, for collections that may contain many rows, I’m unsure whether eagerly loading the entire dataset into the aggregate is effective or necessary.

For aggregates with small collections or singular child entities, I have a reasonable approach. But when it comes to larger relationships like this—or many-to-many relationships in general—I struggle to decide:

  • Should the relationship entity itself be an aggregate root?

  • Or should it belong to one of the main aggregates?

  • How should I balance DDD consistency rules with performance and scalability concerns?

Any guidance on this would be greatly appreciated. Thanks!

c# entity-framework domain-driven-design