我可以隐藏我的 ICollection当我仅在 EF4 代码中进行一对多映射时,字段如何?

发布于 2024-09-30 14:20:47 字数 743 浏览 10 评论 0原文

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

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

更新

发现

My domain classes that have one-to-many mappings generally take the following form (untested code):

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

The EF4 code-only samples I've seen expose a public ICollection when dealing with one-to-many relationships.

Is there a way to persist and restore my collections with exposing them? If not, it would appear that my domain objects will be designed to meet the requirements of the ORM, which seems to go against the spirit of the endeavour. Exposing an ICollection (with it's Add, etc. methods) doesn't seem particularly clean, and wouldn't be my default approach.

Update

Found this post that suggests it wasn't possible in May. Of course, the Microsoft poster did say that they were "strongly considering implementing" it (I'd hope so) and we're half a year on, so maybe there's been some progress?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

你是年少的欢喜 2024-10-07 14:20:47

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

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

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

我只关注了 ICollection 接口的 AddRemoveClear 函数,因为是可以修改集合的。

首先,是我的基本集合包装器,它实现了 ICollection接口
默认行为是普通集合的行为。但是,调用者可以指定要调用的替代 Add 方法。此外,调用者可以通过将替代项设置为 null 来强制不允许进行 AddRemoveClear 操作代码>.如果有人尝试使用该方法,这会导致引发 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。然后您的 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();

I found that whatever was done, EF requires the ICollection<T> to be public. I think this is because when the objects are loaded from the database, the mapping looks for a collection property, gets the collection and then calls the Add method of the collection to add each of the child objects.

I wanted to ensure that the addition was done through a method on the parent object so created a solution of wrapping the collection, catching the add and directing it to my preferred method of addition.

Extending a List and other collection types was not possible because the Add method is not virtual. One option is to extend Collection class and override the InsertItem method.

I have only focussed on the Add, Remove, and Clear functions of the ICollection<T> interface as those are the ones that can modify the collection.

First, is my base collection wrapper which implements the ICollection<T> interface
The default behaviour is that of a normal collection. However, the caller can specify an alternative Add method to be called. In addition, the caller can enforce that the Add, Remove, Clear operations are not permitted by setting the alternatives to null. This results in NotSupportedException being thrown if anyone tries to use the method.

The throwing of an exception is not as good as preventing access in the first place. However, code should be tested (unit tested) and an exception will be found very quickly and a suitable code change made.

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

}

Given that base class we can use it in two ways. Examples are using the original post objects.

1) Create a specific type of wrapped collection (For example, 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
    }

This can then be used:

 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) Give a collection to be wrapped using

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

which can be used as follows:

{
public ICollection Orders {get { return _wrappedOrders; } }
// Public methods.

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

There are some other ways to call the WrappedCollection constructors
For example, to override add but keep remove and clear as normal

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

I agree that it would be best if EF would not require the collection to be public but this solution allows me to control the modification of my collection.

For the problem of preventing access to the collection for querying you can use approach 2) above and set the WrappedCollection GetEnumerator method to throw a NotSupportedException. Then your GetOrder method can stay as it is. A neater method however may be to expose the wrapped collection. For example:

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

Then the call in the GetOrder method would become

_orders.InnerCollection.Where(x => x.Id == id).Single();
违心° 2024-10-07 14:20:47

实现此目的的另一种方法是为每个 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

}

Another way to accomplish this would be to create an associated interface for each of your POCOs to expose only what you want outside of the persistence/domain layers. You can also interface your DbContext class to also hide and control access to the DbSet collections. As it turns out, the DbSet properties can be protected, and the model builder will pick them up when it's creating tables, but when you try to access the collections they will be null. A factory method (in my example, CreateNewContext) can be used instead of the constructor to get the interfaced DbContext to conceal the DbSet collections.

There's quite a bit of extra effort in coding, but if hiding implementation details within the POCOs is important, this will work.

UPDATE: It turns out you CAN populate DBSets if they are protected, but not directly in the DBContext. They can't be aggregate roots (i.e. accessibility of the entity has to be through a collection in one of the public DBSet entities). If hiding the implementation of DBSet is important, the interface pattern I've described is still relevant.

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

}
凉世弥音 2024-10-07 14:20:47

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

AFAIK 你可以保留 private 修饰符不变。集合不需要公开。

If you change the name of your _orders collection to the name of the orders table in your database, this should work. EF maps table/field names to collections/properties by convention. If you want to use a different name you could edit the mappings in the edmx file.

AFAIK you can just leave the private modifier as it is. Collections do not need to be public.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文