c# entity-framework ef-code-first readonly-collection

c# - Entity Framework solo leer colecciones



entity-framework ef-code-first (2)

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

Tendrías que hacer tu propia implementación para ISet pero el principio es el mismo.

Al ocultar cualquier método que modifique la lista y marcarlos como obsoletos, usted obtiene efectivamente una ReadOnlyCollection, pero EF aún podrá modificarla cuando esté desempacada como Colección. En mi versión, he agregado una conversión de operador implícita para la Lista, por lo que no tenemos que desempaquetar la colección al agregar elementos:

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

Dónde

public virtual EntityCollection<MyEntity> ListProperty { get; protected set; }

y aquí está el EntityCollection:

public class EntityCollection<T> : Collection<T> { [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<T>(List<T> source) { var target = new EntityCollection<T>(); foreach (var item in source) ((Collection<T>) target).Add(item); // unbox return target; } }

De esta manera, aún puede ejecutar su Linq como de costumbre, pero recibirá una advertencia de uso cuando intente modificar la propiedad de Colección. Desembalarlo en una colección sería la única manera:

((Collection<MyEntity>)ListProperty).Add(entity);

Considere un dominio en el que un Cliente, Compañía, Empleado, etc. tenga una propiedad ContactInfo que a su vez contenga un conjunto de Direcciones, Teléfono (s), Correo (s), etc., etc.

Aquí está mi información de contacto abreviada:

public class ContactInfo : Entity<int> { public ContactInfo() { Addresses = new HashSet<Address>(); } public virtual ISet<Address> 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 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 es la principal ...

Me gustaría (si es posible) evitar la adición de 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á con Entity Framework (v5)?

¿Cómo voy a hacer esto?


Una forma es hacer que la propiedad ICollection esté protegida y crear una nueva propiedad de IEnumerable que simplemente devuelva la lista de la propiedad ICollection.

El inconveniente de esto es que no puede consultar direcciones a través de ContactInfo, como obtener toda la información de contacto que vive 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<int> { public ContactInfo() { Addresses = new HashSet<Address>(); } protected virtual ISet<Address> AddressesCollection { get ; private set; } public IEnumerable<Address> 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); } }