6

mie classi di dominio che dispongono di mappature uno-a-molti prendono generalmente il seguente modulo (codice non testato):Posso nascondere i campi ICollection <T> quando ho una mappatura uno-a-molti solo in codice EF4?

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

Il EF4 code-only samples ho visto esporre un ICollection pubblico quando si tratta di relazioni uno-a-molti.

C'è un modo per mantenere e ripristinare le mie collezioni esponendole? In caso contrario, sembrerebbe che i miei oggetti di dominio saranno progettati per soddisfare i requisiti dell'ORM, che sembra andare contro lo spirito dell'impegno. Esporre un ICollection (con i suoi metodi Add, etc.) non sembra particolarmente pulito e non sarebbe il mio approccio predefinito.

Aggiornamento

Trovato this post che suggerisce che non era possibile in maggio. Certo, il poster di Microsoft ha detto che stavano "considerando seriamente l'implementazione" (lo spero) e siamo a metà anno, quindi forse ci sono stati dei progressi?

risposta

1

Ho scoperto che qualsiasi cosa fosse stata eseguita, EF richiede che lo ICollection<T> sia pubblico. Penso che questo sia dovuto al fatto che quando gli oggetti vengono caricati dal database, la mappatura cerca una proprietà di raccolta, ottiene la raccolta e quindi chiama il metodo Add della raccolta per aggiungere ciascuno degli oggetti figlio.

Volevo assicurarmi che l'aggiunta fosse fatta attraverso un metodo sull'oggetto padre, così ho creato una soluzione di avvolgere la collezione, catturare l'aggiunta e indirizzarla al mio metodo preferito di aggiunta.

Estendere un e altri tipi di raccolta non era possibile perché il metodo Add non è virtuale. Un'opzione è di estendere la classe Collection e sovrascrivere il metodo InsertItem.

ho focalizzato solo sulle Add, Remove, e Clear funzioni dell'interfaccia ICollection<T> come questi sono quelli che possono modificare la raccolta.

In primo luogo, è il mio involucro di raccolta di base che implementa l'interfaccia ICollection<T> Il comportamento predefinito è quello di una raccolta normale. Tuttavia, il chiamante può specificare un metodo alternativo Add da chiamare. Inoltre, il chiamante può far rispettare le operazioni Add, Remove, Clear impostando le alternative su null. Ciò comporta il lancio di NotSupportedException se qualcuno tenta di utilizzare il metodo.

Il lancio di un'eccezione non è buono come impedire l'accesso in primo luogo. Tuttavia, il codice dovrebbe essere testato (unità testata) e un'eccezione verrà trovata molto rapidamente e verrà apportata una modifica del codice adeguata.

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

} 

Dato che la classe base possiamo usarla in due modi. Gli esempi stanno usando gli oggetti post originali.

1) Creare un tipo specifico di raccolta avvolto (ad esempio, List) WrappedListCollection public class: WrappedCollectionBase, IList { privata Lista 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 
    } 

Questo può quindi essere utilizzato:

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) Dare una collezione ad essere avvolto utilizzando

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; 
    } 
} 

che può essere utilizzato come segue:

{ pubblico Ordini di ICollection {get {return _wrappedOrders; }} // Metodi pubblici.

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); 
} 

Non ci sono altri modi per chiamare i WrappedCollection costruttori Ad esempio, per eseguire l'override aggiungere ma mantenere remove e chiaro come normale

private WrappedListCollection<Order> _orders = new WrappedListCollection(this.AddOrder, (Order o) => _orders.RemoveFromInnerCollection(o),() => _orders.ClearInnerCollection()); 

Sono d'accordo che sarebbe stato meglio se EF non richiederebbe la raccolta è pubblica ma questa soluzione mi consente di controllare la modifica della mia collezione.

Per il problema di impedire l'accesso alla raccolta per l'interrogazione è possibile utilizzare l'approccio 2) sopra e impostare il metodo WrappedCollection GetEnumerator per generare un NotSupportedException. Quindi il tuo metodo GetOrder può rimanere così com'è. Tuttavia, un metodo più ordinato potrebbe essere quello di esporre la raccolta incartata. Per esempio:

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; 
    } 
} 

Poi la chiamata nel metodo GetOrder sarebbe diventato

_orders.InnerCollection.Where(x => x.Id == id).Single(); 
0

Se si modifica il nome della propria collezione _orders nel nome della tabella degli ordini nel proprio database, questo dovrebbe funzionare. EF associa i nomi delle tabelle/campi alle collezioni/proprietà per convenzione. Se si desidera utilizzare un nome diverso, è possibile modificare i mapping nel file edmx.

AFAIK è possibile lasciare il modificatore privato così com'è. Le raccolte non devono essere pubbliche.

+0

Se ho impostato il modificatore di accesso della mia collezione a nulla, ma pubblico non è la lettura dei valori. Un semplice passaggio da "protetta" torna a "pubblico", per esempio, rende tutto il lavoro. Inoltre, non sembra mappare le proprietà non pubbliche semplici (ad esempio stringa). – dommer

+0

Un altro approccio consiste nell'aggiungere un file T4 e modificarlo per esporre la proprietà ap di IEnumerable per le raccolte e rendere protetta la proprietà predefinita di ICollection. – Michael

1

Un altro modo per raggiungere questo obiettivo sarebbe quello di creare un'interfaccia associato a ognuno dei vostri pocos per esporre solo quello che vuoi al di fuori dei livelli di persistenza/dominio. È inoltre possibile interfacciare la classe DbContext per nascondere e controllare l'accesso alle raccolte DbSet. A quanto pare, le proprietà DbSet possono essere protette e il builder del modello le preleverà quando sta creando le tabelle, ma quando proverai ad accedere alle raccolte saranno nulle. Un metodo factory (nel mio esempio, CreateNewContext) può essere utilizzato al posto del costruttore per ottenere DbContext interfacciato per nascondere le raccolte DbSet.

C'è ancora un po 'di sforzo in più nella codifica, ma se nascondere i dettagli di implementazione all'interno dei POCO è importante, funzionerà.

AGGIORNAMENTO: Risulta che è possibile popolare DBSets se sono protetti, ma non direttamente in DBContext. Non possono essere radicati in termini aggregati (cioè l'accessibilità dell'entità deve avvenire attraverso una raccolta in una delle entità pubbliche di DBSet). Se nascondere l'implementazione di DBSet è importante, lo schema di interfaccia che ho descritto è ancora pertinente.

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 

} 
Problemi correlati