问题描述
很常见,特别是在具有ORM的应用程序中,可以在类之间进行双向映射。像这样:
public class Product
{
private List< Price> HistoricPrices {get; private set;}
}
public class Price
{
private Product Product {get;组; }
}
在代码中是否有接受维护这种关系的方法?所以当我向产品添加价格时,产品属性会自动设置?
理想情况下,我正在寻找一种易于重用的解决方案。对于一个集合来说,添加一些东西,然后手动设置相反的关系似乎是错误的。
请注意,这不是关于如何模拟产品和价格的问题,这是一个如何建立2路关系的问题。有很多情况是完全合理的。
首先,我认为你的现在的例子是令人困惑的 - 这是不常见的像价格一样被建模为对象或参考具有价格的实体。但我认为这个问题是合法的 - 在ORM世界中,这有时被称为图形一致性。据我所知,解决这个问题的最终方式是没有一种方法,有几种方法。我们先从略微改变这个例子开始:
public class Product
{
私人制造商制造商{get; private set;}
}
public class制造商
{
private List< Product>产品{get;组; }
}
所以每个产品都有一个制造商,每个制造商可能有一个列表产品。该模型的挑战在于,如果产品类和制造商类保持彼此不连接的引用,更新一个可以使另一个无效。
有几种方法可以解决此问题问题:
-
消除循环引用。这样可以解决问题,但是使对象模型的表现力更低,更难使用。
-
更改代码,使制造商的产品和产品列表中的制造商参考自反。换句话说,改变一个会影响另一个。这通常需要一些代码,设置者和收集来截取更改,并将反映彼此。
-
管理一个属性的另一个所以,而不是在产品中存储对制造商的引用,您可以通过搜索所有制造商进行计算,直到找到拥有您的制造商。相反,您可以在产品类中保留对制造商的引用,并动态构建产品列表。在这种方法中,您通常会将关系的一方置于只读方式。顺便说一句,这是标准的关系数据库方法 - 实体通过一个外部的密钥相互引用。
-
外部化关系来自 类,并在单独的对象(通常在ORM中称为数据上下文)中进行管理。当产品想要返回其制造商时,它会询问DataContext。当制造商想要返回产品列表时,它也是一样的。在内部,有许多实现数据环境的方法,一组双向字典并不罕见。
最后,我将提到,您应该考虑使用可以帮助您管理图形一致性的ORM工具(如NHibernate或CSLA)。一般来说,解决这个问题一般不是一个容易的问题 - 一旦开始探索诸如多对多关系,一对一关系,以及懒惰加载对象的情况,可以很容易地变得非常复杂。您最好使用现有的图书馆或产品,而不是发明自己的机制。
以下是一些,您可能会发现有用的。
以下是使用方法3直接自己管理关系的代码示例 - 通常是最简单的。请注意,只有一方的关系是可编辑的(在这种情况下,制造商) - 外部消费者无法直接设置产品制造商。
public class Product
{
private Manufacturer m_manufacturer;
私人制造商制造商
{
get {return m_manufacturer;}
internal set {m_manufacturer = value; }
}
}
public class制造商
{
private List< Product> m_Products = new List< Product>();
public IEnumerable< Product>产品{get {return m_Products.AsReadOnly(); }}
public void AddProduct(Product p)
{
if(!m_Products.Contains(p))
{
m_Products.Add );
p.Manufacturer = this;
}
}
public void RemoveProduct(Product p)
{
m_Products.Remove(p);
p.Manufacturer = null;
}
}
It is pretty common, especially in applications with an ORM, to have a two way mapping between classes. Like this:
public class Product
{
private List<Price> HistoricPrices { get; private set;}
}
public class Price
{
private Product Product { get; set; }
}
Is there an accepted way of maintaining this relationship in code? So that when I add a price to a product the Product property gets set automatically?
Ideally I am looking for an easily reusable solution. It seems wrong to have to add something to a collection and then set the opposite relations manually.
Please note that this is not a question about how to model products and prices, It is a question of how to model a 2 way relationship. There are plenty of situations where this is perfectly reasonable.
First, I think the example your present is confusing - it's uncommon for something like a Price to be modeled as an object or to have reference to the entities that would have a price. But I think the question is legitimate - in the ORM world this is sometimes referred to as graph consistency. To my knowledge there isn't one definitive way to tackle this problem, there are several ways.
Let's start by changing the example slightly:
public class Product
{
private Manufacturer Manufacturer { get; private set;}
}
public class Manufacturer
{
private List<Product> Products { get; set; }
}
So every Product has one Manufacturer, and each Manufacturer could have a list of products. The challenge with the model is that if the Product class and Manufacturer class maintain disconnected references to one another, updating one can invalidate the other.
There are several ways to address this issue:
Eliminate the circular reference. This solves the problem but makes the object model less expressive and harder to use.
Change the code so that the Manufacturer reference in Product and Products list in Manufacturer are reflexive. In other words, changing one affects the other. This generally requires some code the setter and the collection to intercept changes and reflect them into one another.
Manage one property in terms of the other. So, rather than storing a reference to a manufacturer within Product, you compute it by search through all Manufacturers until you find the one that owns you. Conversely, you could keep a reference to the Manufacturer in the Product class and build the list of Products dynamically. In this approach, you would generally make one side of the relationship read-only. This, by the way, is the standard relational database approach - entities refer to each other through a foreign key which is managed in one place.
Externalize the relationship from both classes and manage it in a separate object (often called a data context in ORM). When Product wants to return its manufacturer it asks the DataContext. When the Manufacturer want to return a list of Products it does the same. Internally, there are many ways to implement a data context, a set of bi-directional dictionaries is not uncommon.
Finally, I will mention, that you should consider using an ORM tool (like NHibernate or CSLA) that can help you manage graph consistency. This is generally not an easy problem to solve correctly - and it can easily become very complicated once you start exploring cases like many-to-many relationships, one-to-one relationships, and lazy loading of objects. You are better of using an existing library or product, rather than inventing a mechanism of your own.
Here are some links that talk about bidirectional associations in NHibernate that you may find useful.
Here's a code example of managing the relationships directly yourself using method #3 - which is typically the simplest. Note that only one side of the relationship is editable (in this case, the Manufacturer) - external consumers cannot directly set the Manufacturer of a Product.
public class Product
{
private Manufacturer m_manufacturer;
private Manufacturer Manufacturer
{
get { return m_manufacturer;}
internal 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;
}
}
这篇关于维持班级之间的双向关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!