Exposing Private Collection Properties to Entity Framework

When following good object-oriented design principles and domain-driven design fundamentals, one should avoid exposing collection properties directly from the domain model. This can be a difficult goal to achieve with Entity Framework 6.x, since its collection properties generally must be of type ICollection, which includes methods that can manipulate the collection without the knowledge of the object exposing the collection property. Ideally, collections should be exposed as either read-only collections or even simply as enumerations (IEnumerable<T>), with care taken to ensure that client code cannot (willfully or otherwise) convert the type into a Collection or List that can then be manipulated.

Jimmy Bogard has written about how EF lacks support for encapsulated collections and aย rather convoluted workaround, but I have a somewhat simpler approach that some may find useful (not to take anything away from Jimmy – I’m a huge fan). My approach has the advantage of simplicity but the disadvantage of breaking Persistence Ignorance on the domain entities. The trick is to expose to EF an expression that it can use to perform its mapping. EF’s code first mapping methods expect these expressions to be of type Expression<Func<EntityType, ICollection<CollectionItemType>>>. For instance, a Customer entity with an Orders collection would be mapped by EF as an Expression<Func<Customer, ICollection<Order>>>. In order to properly encapsulate your collection to avoid giving other code the ability to manipulate it without the owning object’s knowledge, you can expose it publicly like so:

Another option is to expose an IReadOnlyCollection, as this example demonstrates:

The above code ensures that calling code cannot access the underlying List<Order> by guessing its type and casting to it (either on purpose, which is evil, or by accident perhaps due to a convention within a framework).

In both of the above cases, entity framework cannot map the public-facing Orders property, since it doesn’t implement ICollection. However, access to the appropriate expression can be provided to EF via another property:

Now a mapping for Orders can be added to EF using the OrderMapping:

I don’t much care for having OrderMapping as a public property, especially if there are potentially multiple different collection properties on the entity. Thus, I would encapsulate these mappings into an internal class with a name I would use consistently between all of my entities (perhaps defined in a common entity base class):

Note, the collection name may be necessary when using .Include() with EF queries, hence its inclusion here to allow us to follow the DRY principle. Also, the comment first line is some C# 6 syntax that will let us get rid of even the single magic string defining the name of the private collection, using the nameof operator.

Now the EF code to map the column is simply:

I think the above code is very clear in its intent, and it should be quite unlikely that some unsuspecting developer will do something with the ORMappings property without realizing its intended use. The ORMappings class has nothing to do with the domain responsibilities of Customer, and as such would be a good candidate for placing into a partial class.

Domain entities should be responsible for managing their state. Blindly exposing properties with automatic getters and setters breaks encapsulation, but so does exposing collection properties even if the collection itself cannot be set. Client code can easily add and remove items to such collection properties, or even clear their contents, all without the owning object’s knowledge. It’s important to protect against such direct access to collection state by exposing only the operations that make sense from the owning domain entity. Unfortunately, this can make working with some ORMs difficult, but with the workaround shown here, EF can be made to work with properly encapsulated collection properties in .NET. Try it out and let me know what you think.

See Also:

  • jsmunroe

    I make my EF context encapsolated by exposing it through a thin wrapper with get, create, delete, and update methods. This is a holdover from the DataReader days before EF, and I do it because I am a masochist, but it works well. All of my code modules then deal with the wrapper rather than the context directly.

    • ardalis

      You should almost never deal with the context directly if you’re writing code with well-factored dependencies. In my non-trivial/CRUD applications this means that EF is only a dependency within my Infrastructure project, where Repository implementations know about EF, but the rest of the application only deals with IRepository interfaces. The repository is essentially the thin wrapper you describe, but the dependencies are inverted such that unit testing of the core model and the UI can be done without the database or other infrastructure getting in the way.

      • jsmunroe

        That is awesome. I do call my wrappers ISuchAndSuchRepository. I do not grok DDD quite yet, but I am fascinated.

  • Amadeo Gallardo

    Hi Steve,
    In a scenario like this: how would you perform Linq to Entities queries that involve the Orders Navigation property? Considering that it’s not public, and that attempting to use the IEnumerable property would throw an exception such as “The specified type member is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.”.
    Any tips greatly appreciated.

    Thanks

    • Abhijeet Bhadani

      Hi Steve, Will you recommend using .Include in DDD? I believe you had advised in Pluralsight course – to fetch necessary collections in a separate repository & then pass it as dependency to the class. i.e In above example we should inject List into Customer object correct?

  • Abhijeet Bhadani

    Hi Steve, What is the use of specifying collection name – public const string OrderCollectionName = “_orders”;

    • ardalis

      For use with some string-based EF methods, like .Include(“_orders”) can avoid the magic string and use .Include(Customer.ORMappings.OrderCollectionName)

      • balazs hideghety

        It’s just a hack. It’s from 2016. I did not needed any hacks from 2006 with NH. NH is open source. Has flexible 3rd party libraries. So question is: what was microsoft doing wrong for a decade, that both in features as in flexibility EF6/7 is still behind?

        [Ok, shadow props and so are cool, but if I can’t make a solid O-O (ddd) model, they’re won’t be good for anything…]

  • Furqan Safdar

    Hi Steve, what if i need to bind my collection in WPF app and where i need to manipulate my collection say, move items up and down, add, remove etc. How to handle this scenario gracefully?

    • ardalis

      I’m not sure what you’re asking. If you have a specific troubleshooting question, perhaps post it on StackOverflow.com, and leave a link to it here (if you don’t quickly get an answer)?

      • Furqan Safdar

        In short, I am looking for an appropriate design decision to achieve few goals in WPF application using Entity Framework (EF):

        1. Ability to encapsulate domain model collections
        2. Exposing a collection via ViewModel to be manipulated visually on screen
        3. To keep both these collections in sync automatically (Model and ViewModel)

  • lindvalll

    Very interesting article! Been struggling with EF and DDD regarding Collections. I ended up interfacing all entities to hide ICollections (i.e. UserRepository returns IUser), but this example looks very good!

    Wondering how you solve nested Select in Include(), like Include(u => u.FooList.Select(f => f.Foo))??

    • balazs hideghety

      I got to the same point. Although those conventions (depite they come with EF) are far beyond the possibilities of Fluent NH – wonder if it’s the architecture behind. Anyhow I managed to use protected virtual properties with underscore prefix in EF6, but the issue with includes remained.

      Simply EF6 despite all the “hacks” never will allow us to have a acceptable DDD model.

      Btw. what matters the most: no real documentation, so could not solve how to turn off EF6/EF7 auto mapping feature. It’s eidiculous I can’t add new calculated proerties (get/set) without altering the mapping and telling this bulky frameworks that I don’t wanna map those fields! If anyone can help with this, please, help ๐Ÿ˜€

      • lindvalll

        I’ve managed to do everything by hiding User behind IUser, and writing a fluent DomainQueryFactory(Func include) that behind the scenes casts IUser to User and calls EntityFrameworks .Include() therefor can use Include all the way ๐Ÿ™‚

        DomainQueryFactory still returns IUser so the ICollections are all hidden.

        • balazs hideghety

          Yeah, saw some similar solution from Jimmy Bogard also. But Include is one problem, then we got all the queries, projections we want to execute inside the database, not locally.

          • lindvalll

            Well, that’s still possible with my “special made factory”. Though my queries arent especially complicated it still works. The factory still work with IQueryable until I call something in the factory that executes the query agains the DB.

            Wheres an example…

            return _fooRepository
            .QueryDomain()
            .Include(c => c.UserAccessor)
            .Where(c => c.FooAccessor.Any(cr => cr.UserAccessor.Id == FooInt))
            .Select(c => c.UserAccessor)
            .Distinct()
            .OrderByDescending(c => c.UserName)
            .ToPaginatedResult(criteria)
            .Transform(u => new UserSummary(u));

            In this example .ToPagingatedResult is the one pushing the execute button to the db.

            This is my ApplicationLayer that has no knowledge of EntityFramework what so ever…

            Not sure it this was the issue you addressed through ๐Ÿ™‚ Its still a hazzered getting around the problems regarding EF and DDD.

  • balazs hideghety

    Actually this is not a big deal, similar mapping without the bulky hack is possible from 2007 in NHibernate…and it is open-source too ๐Ÿ˜€