Entity Framework solo lee colecciones

Considere un dominio donde un Cliente, Empresa, Empleado, etc. tiene una propiedad ContactInfo que a su vez contiene un conjunto de Dirección (es), Teléfono (s), Correo electrónico (s), etc., etc.

Aquí está mi abreviado ContactInfo:

public class ContactInfo : Entity { public ContactInfo() { Addresses = new HashSet
(); } public virtual ISet
Addresses { get ; private set; } public Address PrimaryAddress { get { return Addresses.FirstOrDefault(a => a.IsPrimary); } } public bool AddAddress(Address address) { // insure there is only one primary address in collection if (address.IsPrimary) { if (PrimaryAddress != null) { PrimaryAddress.IsPrimary = false; } } else { // make sure the only address in collection is primary if (!Addresses.Any()) { address.IsPrimary = true; } } return Addresses.Add(address); } }

Algunas notas (no estoy 100% seguro de que estas sean las “mejores prácticas” de EF):

  • la colección de direcciones (es) es virtual para permitir la carga diferida
  • setter privado en la colección prohíbe el reemplazo de la colección
  • collection es un ISet para asegurar que no haya direcciones duplicadas por contacto
  • Usando el método AddAddress , puedo asegurar que siempre haya como máximo 1 dirección que sea primaria ….

Me gustaría (si es posible) evitar agregar direcciones a través del método ContactInfo.Addresses.Add() y forzar el uso de ContactInfo.AddAddress(Address address)

Estoy pensando en exponer el conjunto de direcciones a través de ReadOnlyCollection pero ¿funcionará esto con Entity Framework (v5)?

¿Cómo voy a hacer esto?

Una forma es proteger la propiedad ICollection y crear una nueva propiedad de IEnumerable que simplemente devuelve la lista de la propiedad ICollection.

La desventaja de esto es que no puede consultar direcciones a través de ContactInfo, como obtener todos los contactos que viven en esta ciudad.

¡Esto no es posible!:

 from c in ContactInfos where c.Addresses.Contains(x => x.City == "New York") select c 

Código:

 public class ContactInfo : Entity { public ContactInfo() { Addresses = new HashSet
(); } protected virtual ISet
AddressesCollection { get ; private set; } public IEnumerable
Addresses { get { return AddressesCollection; }} public Address PrimaryAddress { get { return Addresses.FirstOrDefault(a => a.IsPrimary); } } public bool AddAddress(Address address) { // insure there is only one primary address in collection if (address.IsPrimary) { if (PrimaryAddress != null) { PrimaryAddress.IsPrimary = false; } } else { // make sure the only address in collection is primary if (!Addresses.Any()) { address.IsPrimary = true; } } return Addresses.Add(address); } }

Otra opción sugerida por Edo van Asseldonk es crear una colección personalizada que herede su comportamiento de Collection.

Tendría que hacer su propia implementación para ISet, pero el principio es el mismo.

Al ocultar cualquier método que modifique la lista y marcarla como obsoleta, obtienes un valor ReadOnlyCollection, pero EF aún podrá modificarlo cuando se desagrupe como Colección. En mi versión, agregué una conversión de operador implícita para List, por lo que no es necesario que unbox la colección al agregar elementos:

 var list = ListProperty.ToList(); list.Add(entity) ListProperty = list; 

Dónde

 public virtual EntityCollection ListProperty { get; protected set; } 

y aquí está la colección de la entidad:

 public class EntityCollection : Collection { [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)] public new void Add(T item) { } [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)] public new void Clear() { } [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)] public new void Insert(int index, T item) { } [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)] public new void Remove(T item) { } [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)] public new void RemoveAt(int index) { } public static implicit operator EntityCollection(List source) { var target = new EntityCollection(); foreach (var item in source) ((Collection) target).Add(item); // unbox return target; } } 

De esta forma, aún puede ejecutar su Linq como de costumbre, pero recibirá una advertencia adecuada de uso cuando intente modificar la propiedad Collection. Desvincularlo a una colección sería la única forma:

 ((Collection)ListProperty).Add(entity);