Я обнаружил, что что бы ни было сделано, EF требует, чтобы ICollection<T>
был публичным. Я думаю, это потому, что когда объекты загружаются из базы данных, отображение ищет свойство коллекции, получает коллекцию и затем вызывает метод Add
коллекции, чтобы добавить каждый из дочерних объектов.
Я хотел убедиться, что добавление было сделано с помощью метода для родительского объекта, поэтому создал решение обернуть коллекцию, перехватить добавление и направить его на мой предпочтительный метод добавления.
Расширение List
и других типов коллекций было невозможно, поскольку метод 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();
}
}
Учитывая тот базовый класс, мы можем использовать его двумя способами. Примеры использования оригинальных почтовых объектов.
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;
}
}
, который можно использовать следующим образом:
{
public ICollection Orders {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
. Тогда ваш 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();