何を行っても、EF ではICollection<T>
公開する必要があることがわかりました。Add
これは、オブジェクトがデータベースから読み込まれるときに、マッピングがコレクション プロパティを探し、コレクションを取得してから、コレクションのメソッドを呼び出して各子オブジェクトを追加するためだと思います。
追加が親オブジェクトのメソッドを介して行われたことを確認したかったので、コレクションをラップし、追加をキャッチして、好みの追加方法に向けるソリューションを作成しました。
メソッドが仮想ではないList
ため、a およびその他のコレクション型を拡張できませんでした。1 つのオプションは、クラスAdd
を拡張してメソッドをオーバーライドすることです。Collection
InsertItem
これらはコレクションを変更できるものであるため、インターフェイスのAdd
、Remove
、およびClear
関数にのみ焦点を当てました。ICollection<T>
まず、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();
}
}
その基本クラスを考えると、それを 2 つの方法で使用できます。例では、元の投稿オブジェクトを使用しています。
1) 特定のタイプのラップされたコレクションを作成します (例List
: ) public class WrappedListCollection : WrappedCollectionBase, IList { private List 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;
}
}
次のように使用できます。
{ public 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) を使用して、WrappedCollectionGetEnumerator
メソッドをNotSupportedException
. その後、GetOrder
メソッドはそのままにすることができます。ただし、より適切な方法は、ラップされたコレクションを公開することです。例えば:
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();