EntityFramework and ReadOnlyCollection

I am using EntityFramewotk and the first code approach. So, I describe my model as follows:

class Person { public long Id { get;set; } public string Name { get;set; } public ICollection<Person> Parents { get;set; } } 

But my domain logic does not allow changing the parent collection (add, delete), it should be read-only (for example only). EntityFramework requires that all collections have an ICollection<T> interface, and it has an Add method (to materialize the results) and a Remove method and others. I can create my own collection with an explicit implementation of the interface:

 public class ParentsCollection : ICollection<Person> { private readonly HashSet<Person> _collection = new HashSet<Person>(); void ICollection<Person>.Add(Person item) { _collection.Add(item); } bool ICollection<Person>.Remove(Person item) { return _collection.Remove(item); } //...and others } 

This hides the Add and Remove methods, but does not protect them at all. Because I can always give ICollection and call the forbidden method.

So my question is:

  • Is there a way to work with read-only collections in EntityFramework?
+8
c # entity-framework
source share
4 answers

The short answer is no . And it would be strange that you could do this (well, NHibernate can set private class classes, which means that you can expose it using a public property encapsulating the field as a read-only collection ... well, you can get around this situation in EF too: Entity Framework Many for many using the object . By the way, I would not offer you this approach, because how could you add new parents if this is a private property? )

In any case, I believe that the domain object should be read-write, because at the end of the day, the domain object describes the object in the domain, and you should have access to it and modify it.

An alternative solution is an interface to display Parents as IReadOnlyList<Person> , as well as IPerson with all Person members except Parents , and returns 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 , with the exception of Parents , which will be implemented explicitly. When you need to return 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; } 

It can be argued that you can disable IPerson before Person , but at that moment I would say that you need to follow your own encoding rules: if you determined that you never return Person but IPerson , then I would do it this way in the whole database code, and if you need the Parents property to write, then you will return Person instead (and you will avoid throwing later!).

+1
source share

You can provide EF properties for a private collection, which allows you to display and query, while maintaining the correct encapsulation of members and domain objects of your domain. This is a little 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; } } } 

Then the mapping 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

+6
source share

Well, there is a way. This is not very good as it adds extra things to your domain model, but I just checked and it works.

All loans go to Owen Craig.

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

Walnut shell example

Imagine that we have an existing model with an organization => Employees already configured.

To apply this method, we need to slightly change the organization model:

 // 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 the current configuration in the database context:

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

Change any Include statements if you are not using LazyLoading

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

Happy DDD!

+1
source share

In EF Core, you can encapsulate collections and achieve true domain modeling using auxiliary fields . Thus, you can define your collection as a private field and present it as a read-only public property, as shown 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 cannot modify it.

Note that _parents is detected as a base field in accordance with the basic convention.

+1
source share

All Articles