EntityFramework and ReadOnlyCollection

In EF Core, you can encapsulate collections and achieve true domain modeling by using backing fields. So, you can define your collection as a private field and expose it as a public readonly property like below as _parents and Parents.

class Person
{
    public long Id { get;set; }
    public string Name { get;set; }
    private List<Person> _parents = new List<Person>();
    public IReadOnlyCollection<Person> Parents => _parents.AsReadOnly();
    public void AddParent(Parent parent){
        _parents.Add(parent); 
    }
}

As you can see, Parents is a read-only collection and consumers are not allowed to modify it.

Note that _parents is discovered as a backing-field by ef core's convention.


You can expose private collection properties to EF, allowing for mapping and querying, while still keeping your domain object's members and relationships properly encapsulated. It's a bit messy, but it works:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }

    public IEnumerable<Order> Orders
    {
        get { return _orders.AsEnumerable(); }
    }

    private List<Order> _orders { get; set; }

    public Customer()
    {
        _orders = new List<Order>();
    }

    public static Expression<Func<Customer, ICollection<Order>>> OrderMapping
    {
        get { return c => c._orders; }
    }
}

Mapping then uses:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Customer>().HasMany(Customer.OrderMapping);
}

This approach is described further here: http://ardalis.com/exposing-private-collection-properties-to-entity-framework


Short answer: no. And it would be strange that you could that (well, NHibernate can set private class fields and this means that you can expose it using a public property encapsulating the field as read-only collection... well, you can workaround this situation in EF too: Entity Framework Many to many through containing object. BTW, I wouldn't suggest you this approach, because how could you add new parents if it's a private property?)

Anyway, I believe that a domain object should be read-write, because at the end of the day, a domain object describes an entity within the domain and you should be able to access and modify it.

An alternate solution is designing an interface to expose Parents as IReadOnlyList<Person>, and also an IPerson with all Person members excepting Parents, and return Person as IPerson:

public interface IHasParents
{
    IReadOnlyList<Person> Parents { get; }
}

public interface IPerson : IHasParents
{
    long Id { get; set; }
    string Name { get; set; }
}

And implement IPerson implicitly on Person excepting Parents that would be implemented explicitly. When you need to return a Person somewhere, you return IPerson instead of Person:

public IPerson CreatePerson(string name, IEnumerable<Persons> parents)
{
    Person person = new Person { Name = name, Parents = parents };

    // Persistence stuff

    return person;
}

You can argue that you may be able to downcast IPerson to Person, but at this point I would answer telling you that you need to follow your own coding conventions: if you defined that you never return Person but IPerson then I would do it this way in the entire code base, and if you need a write-capable Parents property, then you return Person instead (and you avoid a cast later!).


Well, there is a way. It ain't pretty as it adds extra stuff on your domain model, but I just checked and it works.

All credits go to Owen Craig.

http://owencraig.com/mapping-but-not-exposing-icollections-in-entity-framework/

Example in a nut shell

Imagine we have existing model with Organization => Employees already set up.

To apply this technique, we need to change Organization model a bit:

// this is the main collection that will be persisted, mark it as protected
protected virtual ICollection<Employee> EmployeesInternal { get; private set; } = new List<Employee>();

// this will expose collection contents to public, seemingly unneccessary `Skip` statement will prevent casting back to Collection
public IEnumerable<Employee> Employees => EmployeesInternal.Skip(0);

// this is property accessor that will be used to define model and/or in `Include` statements, could be marked as internal if your domain/persistance/services are in the same assembly
public static Expression<Func<Organization, ICollection<Employee>>> EmployeeAccessor = f => f.EmployeesInternal;

Change fluent config in your database context:

modelBuilder.Entity<Organization>().HasMany(Organization.EmployeeAccessor).WithRequired();

Change any Include statements in case you are not using LazyLoading

var organizations = organizationRepository.GetAll().Include(Organization.EmployeeAccessor)

Happy DDD!