维持类之间的双向关系

在具有ORM的应用程序中,在类之间进行双向映射是很常见的。 像这样:

public class Product { private List HistoricPrices { get; private set;} } public class Price { private Product Product { get; set; } } 

在代码中是否有一种可接受的方式来维护这种关系? 那么当我向产品添加价格时,Product属性会自动设置吗?

理想情况下,我正在寻找一个易于重复使用的解决方案。 向集合添加内容然后手动设置相反的关系似乎是错误的。

请注意,这不是关于如何建模产品和价格的问题,这是一个如何建立双向关系的问题。 在很多情况下这是完全合理的。

首先,我认为你现在的例子令人困惑 – 像Price这样的东西被建模为一个对象或者引用有价格的实体是不常见的。 但我认为这个问题是合法的 – 在ORM世界中,这有时被称为图形一致性。 据我所知,没有一种明确的方法来解决这个问题,有几种方法。

让我们从稍微改变示例开始:

 public class Product { private Manufacturer Manufacturer { get; private set;} } public class Manufacturer { private List Products { get; set; } } 

因此,每个产品都有一个制造商,每个制造商都可以有一个产品列表。 该模型面临的挑战是,如果Product类和Manufacturer类保持彼此断开的引用,则更新一个可以使另一个无效。

有几种方法可以解决此问题:

  1. 消除循环引用。 这解决了问题,但使对象模型表现力降低,难以使用。

  2. 更改代码,以便制造商的产品和产品列表中的制造商参考是自反的 。 换句话说,改变一个会影响另一个。 这通常需要一些代码,setter和集合拦截变化并将它们相互反映

  3. 根据另一个管理一个财产。 因此,不是在Product中存储对制造商的引用,而是通过搜索所有制造商来计算它,直到找到拥有您的制造商为止。 相反,您可以在Product类中保留对Manufacturer的引用,并动态构建Products列表。 在这种方法中,您通常会将关系的一侧设为只读。 顺便说一下,这是标准的关系数据库方法 – 实体通过在一个地方管理的外键相互引用。

  4. 外化两个类的关系并在单独的对象中管理它(通常在ORM中称为数据上下文)。 当Product想要返回其制造商时,它会询问DataContext。 当制造商想要返回产品列表时,它会做同样的事情。 在内部,有许多方法可以实现数据上下文,一组双向字典并不罕见。

最后,我要提一下,您应该考虑使用ORM工具(如NHibernate或CSLA)来帮助您管理图形一致性。 这通常不是一个容易解决的问题 – 一旦你开始探索多对多关系,一对一关系和对象的延迟加载等情况,它很容易变得非常复杂。 您最好使用现有的库或产品,而不是发明自己的机制。

下面是一些关于NHibernate中双向关联的 链接 ,您可能会发现它们很有用。

下面是使用方法#3直接管理关系的代码示例 – 通常是最简单的方法。 请注意,只有关系的一侧是可编辑的(在本例中为制造商) – 外部消费者无法直接设置产品的制造商。

 public class Product { private Manufacturer m_manufacturer; private Manufacturer Manufacturer { get { return m_manufacturer;} internal set { m_manufacturer = value; } } } public class Manufacturer { private List m_Products = new List(); public IEnumerable Products { get { return m_Products.AsReadOnly(); } } public void AddProduct( Product p ) { if( !m_Products.Contains( p ) ) { m_Products.Add( p ); p.Manufacturer = this; } } public void RemoveProduct( Product p ) { m_Products.Remove( p ); p.Manufacturer = null; } } 

这不是建模此问题的正确方法。

Product应该有PricePrice不应该有Product

 public class Product { public Price CurrentPrice {get; private set; } public IList HistoricPrices { get; private set;} } public class Price { } 

在您的特定设置中, Price具有Product意味着什么? 在上面创建的类中,您将能够处理Product类本身内的所有定价。

在过去,我添加了“链接”方法,即,不是“设置”产品的价格,而是链接产品和价格。

 public void linkPrice(Price toLink) { this.price = toLink; toLink.setProduct(this); } 

(如果使用setPrice来执行此操作,那么setProduct也会这样做,并且它们将永远在第二行中相互调用,因此我创建了一个显式链接方法,而不是使用setter和getter。实际上,setter可以是包保护的。

YMMV,你的情况可能会有所不同。

我的第一个想法是,在用于添加价格的函数/属性中,添加一行代码,如下所示:

 public void addPrice(Price p) { //code to add price goes here p.Product = this; } 

在过去,当我需要维持这种关系时,我已经做过类似的事情……

 public class Owner { public List OwnedList { get; set; } } public class Owned { private Owner _owner; public Owner ParentOwner { get { if ( _owner == null ) _owner = GetParentOwner(); // GetParentOwner() can be any method for getting the parent object return _owner; } } } 

我现在正在研究类似的问题,我在LBushkin的回答中使用了#3。

我有一个数据存储对象,其中包含所有数据类的列表。 数据类中有id用于引用其他类,我回调数据存储类以获取引用项。

我发现这非常有用,因为我需要能够过滤数据,并在从数据存储请求数据时完成。