在具有ORM的应用程序中,在类之间进行双向映射是很常见的。像这样:
public class Product
{
private List<Price> HistoricPrices { get; private set;}
}
public class Price
{
private Product Product { get; set; }
}
在代码中是否存在维护此关系的可接受方式?那么当我向产品添加价格时,Product属性会自动设置吗?
理想情况下,我正在寻找一种易于重复使用的解决方案。似乎错误的是必须向集合中添加内容然后手动设置相反的关系。
请注意,这不是关于如何为产品和价格建模的问题,而是如何建立双向关系的问题。在很多情况下,这是非常合理的。
答案 0 :(得分:18)
首先,我认为你的礼物的例子令人困惑 - 像Price这样的东西被建模为一个对象或引用有价格的实体是不常见的。但我认为这个问题是合法的 - 在ORM世界中,这有时被称为图形一致性。据我所知,没有一种解决这个问题的明确方法,有几种方法。
让我们先稍微改变一下这个例子:
public class Product
{
private Manufacturer Manufacturer { get; private set; }
}
public class Manufacturer
{
private List<Product> Products { get; set; }
}
因此每个产品都有一个制造商,每个制造商都有一个产品清单。该模型面临的挑战是,如果Product类和Manufacturer类保持彼此断开的引用,则更新一个可以使另一个无效。
有几种方法可以解决这个问题:
消除循环引用。这解决了问题,但使对象模型的表现力降低,难以使用。
更改代码,以便制造商的产品和产品列表中的制造商参考 reflexive 。换句话说,改变一个会影响另一个。这通常需要一些代码,setter和集合拦截更改并将它们反映到彼此中。
根据另一个属性管理一个属性。因此,不是在Product中存储对制造商的引用,而是通过搜索所有制造商来计算它,直到找到拥有您的制造商为止。相反,您可以在Product类中保留对Manufacturer的引用,并动态构建Products列表。在这种方法中,您通常会将关系的一侧设为只读。顺便说一句,这是标准的关系数据库方法 - 实体通过在一个地方管理的外键相互引用。
将来自两个类的关系外部化,并在单独的对象(通常称为ORM中的数据上下文)中进行管理。当Product想要返回其制造商时,它会询问DataContext。当制造商想要返回产品列表时,它会做同样的事情。在内部,有很多方法可以实现数据上下文,一组双向字典并不罕见。
最后,我要提一下,您应该考虑使用ORM工具(如NHibernate或CSLA)来帮助您管理图形一致性。这通常不是一个容易解决的问题 - 一旦你开始探索多对多关系,一对一关系和对象的延迟加载等情况,它很容易变得非常复杂。您最好使用现有的库或产品,而不是发明自己的机制。
以下是一些links在NHibernate中讨论bidirectional associations您可能会觉得有用的内容。
以下是使用方法#2直接管理关系的代码示例 - 通常是最简单的方法。请注意,只有关系的一面是可编辑的(在本例中为制造商) - 外部消费者无法直接设置产品的制造商。
public class Product
{
private Manufacturer m_manufacturer;
internal Manufacturer Manufacturer
{
get { return m_manufacturer; }
set { m_manufacturer = value; }
}
}
public class Manufacturer
{
private List<Product> m_Products = new List<Product>();
public IEnumerable<Product> 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;
}
}
答案 1 :(得分:3)
这不是建模此问题的正确方法。
Product
应该有Price
,Price
不应该有Product
:
public class Product
{
public Price CurrentPrice {get; private set; }
public IList<Price> HistoricPrices { get; private set;}
}
public class Price { }
在您的特定设置中,Price
拥有Product
意味着什么?在上面创建的课程中,您将能够处理Product
类本身内的所有定价。
答案 2 :(得分:1)
过去我添加了“链接”方法,即不是“设置”产品的价格,而是链接产品和价格。
public void linkPrice(Price toLink) {
this.price = toLink;
toLink.setProduct(this);
}
(如果使用setPrice执行此操作,那么setProduct也会这样做,并且它们将永远在第二行中相互调用,因此我创建了一个显式链接方法,而不是使用setter和getter。实际上setter可以受到包裹保护。
YMMV,你的情况可能会有所不同。
答案 3 :(得分:0)
我的第一个想法是,在用于添加价格的函数/属性中,添加一行代码,如下所示:
public void addPrice(Price p) {
//code to add price goes here
p.Product = this;
}
答案 4 :(得分:0)
过去,当我需要维持这种关系时,我做过类似的事情......
public class Owner
{
public List<Owned> 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;
}
}
}
答案 5 :(得分:0)
我现在正在研究类似的问题,我在LBushkin的回答中使用了#3。
我有一个数据存储对象,其中包含所有数据类的列表。数据类中有id用于引用其他类,我回调数据存储类以获取引用项。
我发现这非常有用,因为我需要能够过滤数据,并在从数据存储请求数据时完成。