6

我的具有一对多映射的域类通常采用以下形式(未经测试的代码):

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 等方法)似乎不是特别干净,也不是我的默认方法。

更新

发现这篇文章表明它在 5 月是不可能的。当然,微软的海报确实说他们正在“强烈考虑实施”它(我希望如此)而且我们已经过去了半年,所以也许已经取得了一些进展?

4

3 回答 3

1

我发现无论做什么,EF 都要求ICollection<T>公开。我认为这是因为当从数据库加载对象时,映射会查找集合属性,获取集合,然后调用集合的Add方法来添加每个子对象。

我想确保添加是通过父对象上的方法完成的,因此创建了一个包装集合、捕获添加并将其定向到我首选的添加方法的解决方案。

无法扩展 aList和其他集合类型,因为该Add方法不是虚拟的。一种选择是扩展Collection类并覆盖该InsertItem方法。

我只关注界面的AddRemoveClear功能,ICollection<T>因为它们是可以修改集合的功能。

首先,是我的实现接口的基本集合包装器ICollection<T>默认行为是普通集合的行为。但是,调用者可以指定要调用的替代Add方法。此外,调用者可以通过将替代设置为 来强制Add, Remove,Clear操作是不允许的nullNotSupportedException如果有人尝试使用该方法,这将导致被抛出。

抛出异常不如一开始就阻止访问。但是,应该对代码进行测试(单元测试),并且会很快发现异常并进行适当的代码更改。

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) 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 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) 并将 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();
于 2011-06-04T13:40:38.023 回答
1

实现此目的的另一种方法是为每个 POCO 创建一个关联的接口,以仅在持久性/域层之外公开您想要的内容。您还可以连接您的 DbContext 类来隐藏和控制对 DbSet 集合的访问。事实证明,可以保护 DbSet 属性,并且模型构建器会在创建表时获取它们,但是当您尝试访问集合时,它们将为空。可以使用工厂方法(在我的示例中为 CreateNewContext)而不是构造函数来获取接口 DbContext 以隐藏 DbSet 集合。

在编码方面有相当多的额外工作,但如果在 POCO 中隐藏实现细节很重要,这将起作用。

更新:事实证明,如果 DBSet 受到保护,您可以填充它们,但不能直接在 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

}
于 2011-12-28T17:53:32.613 回答
0

如果您将_orders集合的名称更改为数据库中订单表的名称,这应该可以工作。EF 按照约定将表/字段名称映射到集合/属性。如果您想使用不同的名称,您可以编辑 edmx 文件中的映射。

AFAIK,您可以保留私有修饰符原样。集合不需要公开。

于 2010-11-03T13:10:06.757 回答