Maintaining a two way relationship between classes

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:

  1. Eliminate the circular reference. This solves the problem but makes the object model less expressive and harder to use.

  2. 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.

  3. 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.

  4. 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 #2 - 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;

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

In the past I've added "link" methods, i.e., instead of "setting" the Price on a Product, you link the product and the price.

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

(if you use setPrice to do this, then setProduct would also do this, and they would forever call each other in the second line, hence I create an explicit link method instead of using the setters and getters. Indeed the setter could be package protected.

YMMV, your situation could be different.


This is not the correct way to model this problem.

A Product ought to have a Price, a Price ought not to have a Product:

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

public class Price { }

In your particular setup, what does it mean to for a Price to have a Product? In the class I created above you would be able to handle all pricing within the Product class itself.

Tags:

C#