6

有一個一對多的映射我的域類一般採取以下形式(未經測試的代碼):當我在EF4代碼中進行一對多映射時,可以隱藏我的ICollection <T>字段嗎?

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

EF4 code-only samples一個一對多的關係打交道時,我已經看到了公開公共的ICollection。

有沒有一種方法來堅持和恢復我的收藏與暴露他們?如果不是的話,那麼我的域名對象就會被設計爲符合ORM的要求,這似乎違背了努力的精神。公開ICollection(使用它的Add等方法)似乎並不特別乾淨,而且不會成爲我的默認方法。

更新

發現this post這表明它在五月是不可能的。當然,微軟的海報確實表示他們正在「強烈地考慮實施」它(我希望如此),而我們已經有半年了,所以也許有一些進展?

回答

1

我發現無論做了什麼,EF都要求ICollection<T>公開。我認爲這是因爲當從數據庫加載對象時,映射查找集合屬性,獲取集合,然後調用集合的方法來添加每個子對象。

我想確保添加是通過父對象上的方法完成的,因此創建了一個包裝集合的方法,捕獲添加並將其指向我的首選添加方法。

擴展List和其他集合類型是不可能的,因爲Add方法不是虛擬的。一種選擇是擴展Collection類並覆蓋InsertItem方法。

我只專注於ICollection<T>接口的Add,RemoveClear函數,因爲那些函數可以修改集合。

首先,是我的基本集合包裝器,它實現ICollection<T>接口 默認行爲是普通集合的默認行爲。但是,調用者可以指定要調用的替代方法Add。此外,呼叫者可以強制執行Add,Remove,Clear操作,但不允許通過設置null的替代方法。如果有人試圖使用該方法,則會導致NotSupportedException被拋出。

拋出一個異常不如一開始就阻止訪問。但是,代碼應該被測試(單元測試),並且會很快發現異常,並進行適當的代碼更改。

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

} 

鑑於該基類,我們可以通過兩種方式使用它。例子是使用原始的post對象。

1)創建特定類型的包裝集合(例如,List) 公共類WrappedListCollection:WrappedCollectionBase,IList的 { 私人列表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 
    } 

這可以被用來:

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)給出的集合,以利用被包裹

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

其可以如下使用:

{ 公共ICollection訂單{get {return _wrappedOrders; }} //公共方法。

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

還有一些其他的方式來調用WrappedCollection構造 例如,覆蓋增加,但保持撈出清晰爲正常

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

我同意,這將是最好的,如果EF不會要求該集合被公開,但是這個解決方案允許我控制我的收藏的修改。

對於防止訪問集合進行查詢的問題,您可以使用上面的方法2)並將WrappedCollection GetEnumerator方法設置爲拋出NotSupportedException。那麼你的方法可以保持原樣。一個整潔的方法可能會暴露包裝的集合。例如:

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

然後在GetOrder方法的調用將成爲

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

如果您將_orders集合的名稱更改爲數據庫中orders表的名稱,則應該可以工作。按照慣例,EF將表/字段名稱映射到集合/屬性。如果您想使用其他名稱,則可以編輯edmx文件中的映射。

AFAIK你可以離開私人修改器,因爲它是。收藏品不需要公開。

+0

如果我將收集的訪問修飾符設置爲任何東西,但公衆它不是在讀值。從「保護」回「大衆」一個簡單的開關,例如,使得它所有的工作。此外,它似乎也沒有映射簡單的(例如字符串)非公共屬性。 – dommer 2010-11-03 13:34:53

+0

另一種方法是添加一個T4文件並對其進行編輯以顯示集合的IEnumerable 的ap屬性,並使默認的ICollection屬性受保護。 – Michael 2011-05-05 17:39:59

1

來完成,這將是創建一個相關的接口爲每個波蘇斯的只有你想外面什麼暴露的另一種方式持久層/域層。您還可以將您的DbContext類接口也隱藏並控制對DbSet集合的訪問。事實證明,DbSet屬性可以被保護,並且模型構建器會在創建表時選擇它們,但是當您嘗試訪問集合時,它們將爲空。可以使用工廠方法(在我的示例中爲CreateNewContext)代替構造函數來獲取接口DbContext來隱藏DbSet集合。

在編碼方面有相當多的額外努力,但是如果隱藏POCO中的實現細節很重要,這將起作用。

更新:事實證明,你可以填充DBSets,如果他們受到保護,但不直接在DBContext中。它們不能是聚合根源(即實體的可訪問性必須通過公共DBSet實體之一的集合)。如果隱藏DBSet的實現很重要,我所描述的接口模式仍然相關。

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 

} 
相關問題