6

Mis clases de dominio que tienen asignaciones de uno a muchos, por lo general toman la forma siguiente (código no probado):¿Puedo ocultar mis campos de ICollection <T> cuando tengo una asignación de uno a muchos en el código EF4 solamente?

public Customer Customer 
{ 
    // Public methods. 

    public Order AddOrder(Order order) 
    { 
     _orders.Add(order); 
    } 

    public Order GetOrder(long id) 
    { 
     return _orders.Where(x => x.Id).Single(); 
    } 

    // etc. 

    // Private fields. 

    private ICollection<Order> _orders = new List<Order>(); 
} 

El EF4 code-only samples que he visto exponer un ICollection pública cuando se trata de relaciones de uno a muchos.

¿Hay alguna manera de conservar y restaurar mis colecciones al exponerlas? De lo contrario, parecería que los objetos de mi dominio estarán diseñados para cumplir con los requisitos del ORM, lo que parece ir en contra del espíritu de la empresa. Exponer un ICollection (con sus métodos Agregar, etc.) no parece particularmente limpio, y no sería mi enfoque predeterminado.

actualización

encontrado this post que sugiere que no era posible en mayo. Por supuesto, el póster de Microsoft dijo que estaban "considerando seriamente implementarlo" (espero que sí) y que estamos medio año encendidos, así que tal vez haya habido algún progreso.

Respuesta

1

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(); 
0

Si cambia el nombre de su colección _orders al nombre de la tabla de pedidos en su base de datos, esto debería funcionar. EF asigna nombres de tabla/campo a colecciones/propiedades por convención. Si desea usar un nombre diferente, puede editar las asignaciones en el archivo edmx.

AFAIK puede simplemente dejar el modificador privado tal como está. Las colecciones no necesitan ser públicas.

+0

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

+0

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

1

Otra manera de lograr esto sería la creación de una interfaz asociada a cada uno de sus POCOs para exponer sólo lo que quieres fuera de las capas de persistencia/dominio. También puede interconectar su clase DbContext para también ocultar y controlar el acceso a las colecciones DbSet. Como resultado, las propiedades de DbSet pueden protegerse, y el generador de modelos las recogerá cuando cree tablas, pero cuando intente acceder a las colecciones serán nulas. Se puede utilizar un método de fábrica (en mi ejemplo, CreateNewContext) en lugar del constructor para obtener DbContext con interfaz para ocultar las colecciones de DbSet.

Hay un poco de esfuerzo extra en la codificación, pero si ocultar los detalles de implementación dentro de las POCO es importante, esto funcionará.

ACTUALIZACIÓN: Resulta que PUEDE llenar DBSets si están protegidos, pero no directamente en el DBContext. No pueden ser raíces agregadas (es decir, la accesibilidad de la entidad tiene que ser a través de una colección en una de las entidades públicas de DBSet). Si ocultar la implementación de DBSet es importante, el patrón de interfaz que he descrito sigue siendo relevante.

public interface ICustomer 
{ 
    void AddOrder(IOrder order); 
    IOrder GetOrder(long id); 
} 

public Customer : ICustomer 
{ 
    // Exposed methods: 
    void ICustomer.AddOrder(IOrder order) 
    { 
     if (order is Order) 
     orders.Add((Order)order); 
     else 
     throw new Exception("Hey! Not a mapped type!"); 
    } 

    IOrder ICustomer.GetOrder(long id) 
    { 
     return orders.Where(x => x.Id).Single(); 
    } 

    // public collection for EF 
    // The Order class definition would follow the same interface pattern illustrated 
    // here for the Customer class. 
    public ICollection<Order> orders = new List<Order>(); 
} 

public interface IMyContext 
{ 
    IEnumerable<ICustomer> GetCustomers(); 
    void AddCustomer(ICustomer customerObject); 
    ICustomer CreateNewCustomer() 
} 


public class MyContext : DbContext, IMyContext 
{ 
    public static IMyContext CreateNewContext() { return new MyContext(); } 

    public DbSet<Customer> Customers {get;set;} 
    public DbSet<Order> Orders {get;set;} 

    public IEnumerable<ICustomer> GetCustomers() 
    { 
     return Customers; 
    } 

    public void AddCustomer(ICustomer customerObject) 
    { 
     if (customerObject is Customer) 
     Customers.Add((Customer)customerObject); 
     else 
     throw new Exception("Hey! Not a mapped type"); 
    } 

    public ICustomer CreateNewCustomer() 
    { 
     return Customers.Create(); 
    } 

    // wrap the Removes, Finds, etc as necessary. Remember to add these to the 
    // DbContext's interface 

    // Follow this pattern also for Order/IOrder 

} 
Cuestiones relacionadas