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.
Association is a relation between two separate classes which establishes through their Objects. Association can be one-to-one, one-to-many, many-to-one, many-to-many.
Explanation: Relationships are needed to increase the use of features of one class into the other classes i.e. increasing the re-usability of codes and increasing communication 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:
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 #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;
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With