Encontré que sea lo que sea que se haya hecho, EF requiere que el ICollection<T>
sea público. Creo que esto se debe a que cuando los objetos se cargan desde la base de datos, la asignación busca una propiedad de colección, obtiene la colección y luego llama al método Add
de la colección para agregar cada uno de los objetos secundarios.
Quería asegurarme de que la adición se realizara a través de un método en el objeto principal, de modo que creé una solución de envoltura de la colección, capturando el complemento y dirigiéndolo a mi método preferido de adición.
Extendiendo un List
y otros tipos de colección no fue posible porque el método Add
no es virtual. Una opción es extender la clase Collection
y anular el método InsertItem
.
Sólo he centrado en los Add
, Remove
y Clear
funciones de la interfaz de ICollection<T>
como esos son los que pueden modificar la colección.
En primer lugar, es mi contenedor de colección base que implementa la interfaz ICollection<T>
El comportamiento predeterminado es el de una colección normal. Sin embargo, la persona que llama puede especificar un método alternativo Add
para llamar. Además, la persona que llama puede exigir que las operaciones Add
, Remove
, Clear
no estén permitidas al establecer las alternativas a null
. Esto da como resultado NotSupportedException
que se arroja si alguien intenta utilizar el método.
Lanzar una excepción no es tan bueno como evitar el acceso en primer lugar. Sin embargo, el código debe probarse (unidad probada) y se encontrará una excepción muy rápidamente y se realizará un cambio de código adecuado.
public abstract class WrappedCollectionBase<T> : ICollection<T>
{
private ICollection<T> InnerCollection { get { return GetWrappedCollection(); } }
private Action<T> addItemFunction;
private Func<T, bool> removeItemFunction;
private Action clearFunction;
/// <summary>
/// Default behaviour is to be like a normal collection
/// </summary>
public WrappedCollectionBase()
{
this.addItemFunction = this.AddToInnerCollection;
this.removeItemFunction = this.RemoveFromInnerCollection;
this.clearFunction = this.ClearInnerCollection;
}
public WrappedCollectionBase(Action<T> addItemFunction, Func<T, bool> removeItemFunction, Action clearFunction) : this()
{
this.addItemFunction = addItemFunction;
this.removeItemFunction = removeItemFunction;
this.clearFunction = clearFunction;
}
protected abstract ICollection<T> GetWrappedCollection();
public void Add(T item)
{
if (this.addItemFunction != null)
{
this.addItemFunction(item);
}
else
{
throw new NotSupportedException("Direct addition to this collection is not permitted");
}
}
public void AddToInnerCollection(T item)
{
this.InnerCollection.Add(item);
}
public bool Remove(T item)
{
if (removeItemFunction != null)
{
return removeItemFunction(item);
}
else
{
throw new NotSupportedException("Direct removal from this collection is not permitted");
}
}
public bool RemoveFromInnerCollection(T item)
{
return this.InnerCollection.Remove(item);
}
public void Clear()
{
if (this.clearFunction != null)
{
this.clearFunction();
}
else
{
throw new NotSupportedException("Clearing of this collection is not permitted");
}
}
public void ClearInnerCollection()
{
this.InnerCollection.Clear();
}
public bool Contains(T item)
{
return InnerCollection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
InnerCollection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return InnerCollection.Count; }
}
public bool IsReadOnly
{
get { return ((ICollection<T>)this.InnerCollection).IsReadOnly; }
}
public IEnumerator<T> GetEnumerator()
{
return InnerCollection.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return InnerCollection.GetEnumerator();
}
}
Dado que la clase base podemos usarla de dos maneras. Los ejemplos están usando los objetos de publicación originales.
1) Crear un tipo específico de recogida envuelta (Por ejemplo, List
) WrappedListCollection clase pública: WrappedCollectionBase, IList { Lista privada InnerList;
public WrappedListCollection(Action<T> addItemFunction, Func<T, bool> removeItemFunction, Action clearFunction)
: base(addItemFunction, removeItemFunction, clearFunction)
{
this.innerList = new List<T>();
}
protected override ICollection<T> GetWrappedCollection()
{
return this.innerList;
}
<...snip....> // fill in implementation of IList if important or don't implement IList
}
Esto entonces se puede utilizar:
public Customer Customer
{
public ICollection<Order> Orders {get { return _orders; } }
// Public methods.
public void AddOrder(Order order)
{
_orders.AddToInnerCollection(order);
}
// Private fields.
private WrappedListCollection<Order> _orders = new WrappedListCollection<Order>(this.AddOrder, null, null);
}
2) Dale una colección para ser envuelto usando
public class WrappedCollection<T> : WrappedCollectionBase<T>
{
private ICollection<T> wrappedCollection;
public WrappedCollection(ICollection<T> collectionToWrap, Action<T> addItemFunction, Func<T, bool> removeItemFunction, Action clearFunction)
: base(addItemFunction, removeItemFunction, clearFunction)
{
this.wrappedCollection = collectionToWrap;
}
protected override ICollection<T> GetWrappedCollection()
{
return this.wrappedCollection;
}
}
que puede ser utilizado de la siguiente manera:
{ público ICollection Orders {get {return _wrappedOrders; }} // Métodos públicos.
public void AddOrder(Order order)
{
_orders.Add(order);
}
// Private fields.
private ICollection<Order> _orders = new List<Order>();
private WrappedCollection<Order> _wrappedOrders = new WrappedCollection<Order>(_orders, this.AddOrder, null, null);
}
hay algunas otras formas de llamar a los WrappedCollection
constructores Por ejemplo, para anular añadir pero tenga quitar y limpiar de forma normal
private WrappedListCollection<Order> _orders = new WrappedListCollection(this.AddOrder, (Order o) => _orders.RemoveFromInnerCollection(o),() => _orders.ClearInnerCollection());
Estoy de acuerdo en que sería mejor si EF no requeriría la colección es pública pero esta solución me permite controlar la modificación de mi colección.
Para el problema de impedir el acceso a la colección para consultar puede utilizar el método 2) anterior y establecer el método WrappedCollection GetEnumerator
para arrojar un NotSupportedException
. Entonces su método GetOrder
puede permanecer como está. Sin embargo, un método más limpio puede ser exponer la colección envuelta. Por ejemplo:
public class WrappedCollection<T> : WrappedCollectionBase<T>
{
public ICollection<T> InnerCollection { get; private set; }
public WrappedCollection(ICollection<T> collectionToWrap, Action<T> addItemFunction, Func<T, bool> removeItemFunction, Action clearFunction)
: base(addItemFunction, removeItemFunction, clearFunction)
{
this.InnerCollection = collectionToWrap;
}
protected override ICollection<T> GetWrappedCollection()
{
return this.InnerCollection;
}
}
entonces la llamada en el método GetOrder
se convertiría en
_orders.InnerCollection.Where(x => x.Id == id).Single();
Si fijo el modificador de acceso de mi colección a cualquier cosa menos pública que no es la lectura de los valores en. Un simple cambio de "protegido" de nuevo a "público", por ejemplo, hace que todo funcione. Además, tampoco parece asignar propiedades no públicas simples (por ejemplo, cadenas). – dommer
Otro enfoque es agregar un archivo T4 y editarlo para exponer una propiedad ap de IEnumerable para las colecciones y proteger la propiedad ICollection predeterminada. –
Michael