tags:

views:

421

answers:

6

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.

+2  A: 

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.

Andrew Hare
It would be correct way if price would be named "ProductPrice". Anyway - i guess it's not the point of his question.
Arnis L.
A: 

My first thought is, in your function/property used to add prices, add a line of code like so:

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

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 #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;
    }
}
LBushkin
Thanks for a clear answer. To repond to what you said about allowing an ORM to manage it this is what I do already. Currently using nhibernate. However I wanted to know how to manage it myself because I have tests that fail unless the object is persisted and read back from the repository. before saving the relationship is one way, after saving it is 2 way. This doesn't seem right to me.
Jack Ryan
Don't you need to set the Manufacturer property on Product p in AddProduct (and conversely unset it in RemoveProduct)? Also - the Contains() method call in AddProduct is O(n) - if you don't want duplicates, then use a Set.
Michael Hart
Yes Michael, I missed setting the manufacturer property - that's been fixed. As for the O(n) call to Contains, I agree with you ... but this was meant as a quick sample. The code has other issues, it should be more defensive about checking for nulls, it should probably override Equals on Product so that we can compare actual instance values and not just references, etc. Optimizing the collection would also be advised, as you point out.
LBushkin
+1  A: 

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.

JeeBee
A: 

In the past, I've done something like this when I needed to maintain this type of relationship...

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;
     }
    }
}
Steve Dignan
A: 

I am just now working on a problem similar to this and I am using #3 in LBushkin's answer.

I have a data storage object with lists for all of my data classes. The data classes have ids in them for references to the other classes and I call back to the data storage class to get the reference item.

I have found this really useful because I need to be able to filter the data and that is done when requesting the data from the data storage.

Robin Robinson