views:

59

answers:

4
          Users
      /           \  
     /             \ 
  M-T-O           M-T-O 
   /                 \  
  /                   \
Products----M-T-M----Tags

I wonder if there is any documentation on how to create a schema like this with entities. I got stuck at wondering which entity should be responsible for what in the relation.

For example:

Lets say I want to add a tag to a product. I have a method like this in my

product entity:

    public virtual void AddTag(Tag tag)
    {
        this.Tags.Add(tag); // IList<Tag> Tags
        tag.AddProduct(this);
    }

First this adds a the tag object to the list of Tags. Then that tag object adds 'this' product to it's own list of products.

So far so good.

But what if I want to add a product to a tag. I have a method like this in my

tag entity:

    public virtual void AddProduct(Product product)
    {
        this.Products.Add(product); // IList<Product> Products
        // product.AddTag(this);
    }

So first I add the product object to a list of products in my tag object. I could then add 'this' tag to the Product but this is where I got stuck. The method that is commented throws a stackoverflow error because it calls back to AddProduct which calls AddTag and so on and so on.

Not sure if my schema is really correct either. The M-T-O from user to tags is there to make it easy when I want to see what tags a user has.

So I was wondering if anybody could point me into the right direction?

Thanks in advance,

Pickels

A: 

Do not use MTM relationship man, instead of this, you should create a new table Products_Tags with only 2 columns: ProductID and TagID. both foreign keys like this:

          Users
      /           \  
     /             \ 
  M-T-O           M-T-O 
   /                 \  
  /                   \
Products              Tags
  \                    /
   \                  /
    \                /
     \               /
      \Products_Tags/
Tufo
I think they downvoted you because I am talking about entities and not about any database schemas. In my database I will ofcourse have a ProductsTags table.
Pickels
The question is how to model a many-to-many relationship in the domain model, not in the database.
Jamie Ide
+1  A: 

It makes a lot more sense to me to add a Tag to a Product. I would not allow a Product to be added to a Tag.

With many-to-many relationships you need to decide which entity is the primary entity in the relationship and control access to the collection through it. You can control access by marking the Add method on the other entity as internal.

Tag entity:

internal virtual void AddProduct(Product product)
{
    this.Products.Add(product);
}
Jamie Ide
A: 

There is a good practice to check if list already contains this element.

if ( !this.Tags.Contains(tag) ) 
{ 
 this.Tags.Add(tag);
 product.AddTag(this);
}
ikhaldeev
Do I have to be concerned with performance when using Contains?
Pickels
one call to default implementation of .Contains = one loop through the whole list.
ikhaldeev
With Contains you have to be concerned about reference equality: "This method determines equality using the default equality comparer EqualityComparer<(Of <(T>)>)..::.Default for T, the type of values in the list."
Jamie Ide
A: 

Tag.AddProduct should only add to the tag's internal list of products, and Product.AddTag should only add to the Product's internal list of tags. The persisting to the DB should handle the cross-mapping - when you save each product, the mapping table should have a row saved in the mapping table, and when you save each tag the same.

Maintaining this logic in the domain doesn't make sense to me, nor can I see any real benefits. A product has many tags, and a tag has many products - this should be how the domain is structured, with the persistence layer taking care of the many-to-many relationships.

This way, for any product you know what tags it has and vice versa, which is all you really need to know.

class Users
{
  private Tags tags;
  private Products products;
}

class Tags : IList<Tag> {}
class Tag
{
  private Products products;
  public void AddProduct(Product product);
}

class Products : IList<Product> {}
class Product
{
  private Tags tags;
  public void AddTag(Tag tag);
}
Antony Koch
I disagree pretty strongly with this; maintaining both sides of the relationship keeps the in-memory objects consistent. Following this logic, you would have to persist and retrieve your Product and Tag objects for their collections to contain the expected objects.
Jamie Ide
But then surely dependent on the architecture of the system it's plausible the in-memory objects may be out of sync with the DB given the M2M relationship so a load may be prudent to avoid assuming the in-memory state matches that of the DB?
Antony Koch