维持类之间的双向关系

时间:2009-06-04 14:00:35

标签: c#

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

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

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

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

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

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

6 个答案:

答案 0 :(得分:18)

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

让我们先稍微改变一下这个例子:

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

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

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

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

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

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

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

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

  5. 最后,我要提一下,您应该考虑使用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应该有PricePrice不应该有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用于引用其他类,我回调数据存储类以获取引用项。

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