Могу ли я скрыть свои поля ICollection <T>, когда у меня есть отображение «один ко многим» только в коде EF4? - PullRequest
6 голосов
/ 03 ноября 2010

Мои классы доменов, которые имеют сопоставления один-ко-многим, обычно принимают следующую форму (непроверенный код):

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 только для кода Я видел открытую коллекцию ICollection при работе с отношениями один-ко-многим.

Есть ли способ сохранить и восстановить мои коллекции, разоблачая их? В противном случае может показаться, что мои доменные объекты будут разработаны в соответствии с требованиями ORM, что, похоже, противоречит духу усилий. Представление ICollection (с его методами Add и т. Д.) Не выглядит особенно чистым и не будет моим подходом по умолчанию.

Обновление

Найден этот пост , который предполагает, что это было невозможно в мае. Конечно, на постере Microsoft действительно говорилось, что они «решительно рассматривают возможность его реализации» (я надеюсь, что это так), и у нас уже полгода, так что, может быть, был достигнут определенный прогресс?

Ответы [ 3 ]

1 голос
/ 04 июня 2011

Я обнаружил, что что бы ни было сделано, 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();
1 голос
/ 28 декабря 2011

Еще один способ сделать это - создать связанный интерфейс для каждого из ваших POCO, чтобы отображать только то, что вы хотите, за пределами уровней персистентности / домена. Вы также можете связать свой класс 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

}
0 голосов
/ 03 ноября 2010

Если вы измените имя вашей коллекции _orders на имя таблицы заказов в вашей базе данных, это должно сработать. EF сопоставляет имена таблиц / полей с коллекциями / свойствами по соглашению. Если вы хотите использовать другое имя, вы можете отредактировать сопоставления в файле edmx.

AFAIK, вы можете просто оставить приватный модификатор как есть. Коллекции не должны быть публичными.

...