Encapsulated Collections in Entity Framework Core

Encapsulated Collections in Entity Framework Core

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<T>, which means any user of the class can add or remove items from its properties (or even Clear it). If you’re writing good code that encapsulates business behavior in an entity model, you don’t want to allow this. Instead you want to expose an interface that controls how and when your property can be manipulated, and what kinds of behavior should occur when it is. This isn’t just good domain-driven design; it’s good object-oriented design as well.

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<GuestbookEntry> property. However, when a new entry is added, notifications need to be sent to authors of previous entries. With proper encapsulation, this can easily be achieved by putting an AddEntry() method on Guestbook and making it responsible for adding the entry and firing off the notification (or raising a domain event that can be handled elsewhere to perform the notification).

Unfortunately, exposing a List means that there are two ways to add an 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<T>. Note that even if you expose your private collection as an IEnumerable, client code can still cast it back to an ICollection or IList, and manipulate it (if the underlying type matches). To protect against this, make a copy of the list when you provide the enumerable:

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. It should work fine in most apps, but should be tuned if performance is critical and you’ve measured this code contributing to a performance problem.

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:

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<GuestbookEntry> property, but now it exposes a single interface for adding new entries, so behavior tied to this activity will be consistent throughout the application.

Learn more about this feature from Arthur Vickers’ article on the subject.

 

  • Instead of copying the list via ToList() you could use AsReadOnly(); instead.
    This will create a read only wrapper around your private list. It’s much cheaper because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance). As an addition, the read only collection will reflect all changes made to the underlying collection.

    • ardalis

      Agreed. I was on the fence about which approach to show here. Arthur’s post uses .ToList() while my previous post uses AsReadOnly(). I think you’ve convinced me that AsReadOnly() remains the better way to go.

  • What’s the difference between the same class having a collection and methods to manipulate it, and two separate classes for each of the tasks. And how SRP is doing here? I see it’s violation, also the God Object anti-pattern: the same class configures the model, hold various collections and manipulates them. And who knows what else.

    • ardalis

      If there’s no logic that ever needs to occur when the collection is updated, it won’t be (shouldn’t be) a problem to just expose the collection property. But as soon as you have a requirement that either restricts what client code should be able to do (for example, maybe it should only be additive, not delete/clear-able) or that says “when this change to the collection happens, this other thing should also happen”, you’ll have problems. At that point, being able to have your own interface for manipulating the collection becomes important, and restricting access to the collection directly via the property is one way to achieve this.

      I don’t see how in the simple example shown here there is much if any violation of SRP or anything close to a God class. The class is only responsible for its own state. It has a field, 2 properties, and one method and is under 10 lines of code. That doesn’t scream God Class to me. What responsibility would you strip from the Guestbook class?

      • 100% agree with Steve. An entity class with just data is an Anemic-Domain-Model: https://martinfowler.com/bliki/AnemicDomainModel.html
        Having entity class with just data works ok for CRUD/Data-Driven apps, but not for “ever-changing business rules” apps that are better shaped with Domain-Driven Design. Then, by having the behavior as part or the Domain Entity and Aggregate-Root you can keep consistency of all the entities within the Aggregate in a much better controlled way.

  • You are adding a GuestBookEntry object as a parameter of the “public void AddEntry(GuestbookEntry entry)” in the Guestbook Aggregate-Root… Since the GuestbookEntry is a child entity.. Do you think is right that you are allowing to create child entity objects (with a “new GuestbookEntry()”) from outside the AggregateRoot? (from the Application layer, CommandHandler, etc.)
    Wouldn’t it have further control if any creation (“new GuestbookEntry()”) is performed also from within the AddEntry() method from the AggregateRoot so you control validations and aggregate’s consistency always through and within the AggregateRoot?
    In your actual code, you’d need to do a “new GuestbookEntry()” of that child entity from outside the Aggregate..
    However, this is a discussion/fight that I myself have.. Sometimes is pretty convenient if you pass a single argument/parameter/object instead of every field needed in order to create the GuestbookEntry object…
    What is your thought about it?

    • ardalis

      It’s worth discussing. What I don’t want to happen, which is an easy trap to fall into, is to have my Aggregate Root become a God object that violates SRP and does *everything* related to the Aggregate. I see this a lot when developers think “the aggregate root is responsible for ensuring consistency; therefore all business logic belongs directly in the aggregate root.” The aggregate is still allowed to delegate to its children, and well-designed code should allow the entities to coordinate as needed. A pattern I favor for this approach is the use of a special kind of domain events that I call aggregate events (because they’re only used for intra-aggregate communication). It’s on my backlog to write more about this.

      Ideally, you should be able to instantiate entities anywhere. This makes them easy to create and unit test. You shouldn’t be able to fetch them from persistence, though, except through an aggregate. That’s because it’s important during persistence operations (and very likely in other situations as well) that the aggregate’s invariants be maintained/validated. So, my default preference is to not try to lock down where or how entities, even non-root entities within aggregates, are able to be instantiated.

      That said, I would follow Pain Driven Development (http://deviq.com/pain-driven-development/). If allowing the child entity to be created outside of the aggregate is causing pain in the design, then consider refactoring as you suggest, and have the AddThing() method on the root be responsible for instantiating the Thing, rather than just having one passed in.

      • Exactly, you described really well “my internal fight” that I have sometimes.. 😉
        However, there are two different things here.

        1. I always try to delegate logic/invariants/validations to the child entities. Whatever is just about the child entity should be handled by its own constructor and methods. Logic in the Aggregate-root should only be related to the Aggregate-Root itself (Root entity) or when related to consistency between multiple entities, so there’s no a Aggregate Root = God object, because you have logic delegate to the child entities.

        2. The other thing is if it is a “good / bad / doesn’t matter” practice that child entities can be instantiated with “new MyChildEntity()” out of the Aggregate, like at the Application Layer or CommandHandlers… That’s my fight.. 😉 After this discussion I’m beginning to think that it shouldn’t matter as long as its addition to the aggregate is controlled by the Aggregate-Root and its persistence is also controlled by a Repository which has to be unique per Aggregate, too.
        Probably, if it is just about a few parameters, I’d do the “new MyChildEntity()”. If it is about many fields coming from somewhere, it is probably a clearer code to pass that child-entity as a parameter, as long as the invariants/validations/logic related just to that entity is performed by itself within its constructor or methods.

        Good discussion! Keep it on with DDD! 🙂