Encapsulated Collections in Entity Framework Core
Date Published: 18 January 2017
Starting with Entity Framework Core 1.1, you can now have properly encapsulated collections in your classes that rely on EF for persistence. Previous versions of EF have required collection properties to support ICollection
I have an example I use in my DDD with ASP.NET Core workshops that uses a simple Guestbook entity, which has a collection of GuestbookEntry Entries. Initially, this is modeled as a simple List
Unfortunately, exposing a List means that there are two ways to add an entry:
// the preferred way
guestbook.AddEntry(entry);
// the back door way - bypasses logic in AddEntry()
guestbook.Entries.Add(entry);
I’ve written previously about how to protect collections in EF6– it’s a bit of a pain to achieve. EF Core 1.1 makes it much easier. For one thing, it supports mapping to fields, not just properties, without any hacks. This means you can use a private backing collection while exposing something with less functionality, like IEnumerable
public class Guestbook : BaseEntity {
public string Name { get; set; }
private readonly List<GuestbookEntry> _entries =
new List<GuestbookEntry>();
public IEnumerable<GuestbookEntry> Entries => _entries.ToList();
public void AddEntry(GuestbookEntry entry)
{
_entries.Add(entry);
Events.Add(new EntryAddedEvent(this.Id, entry));
}
}
In the above example, BaseEntity includes the Id property and Events collection. Note that the Entries property doesn’t simply return the _entries field, but rather creates a copy of it. This is safer from an encapsulation perspective, but does use some resources with every access. A slight improvement to it would be to use the AsReadOnly() extension, which doesn’t make a copy of the list’s contents:
public class Guestbook : BaseEntity {
public string Name { get; set; }
private readonly List<GuestbookEntry> _entries =
new List<GuestbookEntry>();
public IEnumerable<GuestbookEntry> Entries => _entries.AsReadOnly();
public void AddEntry(GuestbookEntry entry)
{
_entries.Add(entry);
Events.Add(new EntryAddedEvent(this.Id, entry));
}
}
With that one small change, we now have a solid pattern for encapsulating collection properties in our domain entities when working with Entity Framework Core 1.1 (and above). I recommend the following combination, as shown in the code above, for collection properties:
- Define a private readonly List
as a backing field. This is the entity’s private state store. - Define a public IEnumerable
property as the readonly access to this store. - Specify the value of this property as the private backing field.AsReadOnly();
- Configure EF to use the private backing field (see below)
All by itself, EF Core 1.1 won’t properly map the private _entries field to the data store. You need to configure it in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder) {
var navigation = modelBuilder.Entity<Guestbook>()
.Metadata.FindNavigation(nameof(Guestbook.Entries));
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
}
The above code tells EF Core to access the Entries property through its field, which it finds because I’m following a standard naming convention. With this in place, the Guestbook entity is persisted just as if it had a List
Learn more about this feature from Arthur Vickers’ article on the subject.
Category - Browse all categories
About Ardalis
Software Architect
Steve is an experienced software architect and trainer, focusing on code quality and Domain-Driven Design with .NET.