Is there a better way to model a many-to-many relationship on the same table with Entity Framework?

The context

We’re building a web application using Entity Framework 5.0. One of the requirements is that it should be possible for the administrators to link related products so that when someone browses to a product we can render a list “You might also like these products:“.

Since a product can be linked to many products we need a many-to-many relationship for this.

The table in the database could look something like this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> _________________________
| LinkedProducts |
|-------------------------|
| ProductId1 | ProductId2 |
|------------|------------|
| 1 | 2 |
|------------|------------|
| 1 | 3 |
|------------|------------|
| 2 | 4 |
|------------|------------|
</code>
<code> _________________________ | LinkedProducts | |-------------------------| | ProductId1 | ProductId2 | |------------|------------| | 1 | 2 | |------------|------------| | 1 | 3 | |------------|------------| | 2 | 4 | |------------|------------| </code>
 _________________________
| LinkedProducts          |
|-------------------------|
| ProductId1 | ProductId2 |
|------------|------------|
|     1      |     2      |
|------------|------------|
|     1      |     3      |
|------------|------------|
|     2      |     4      |
|------------|------------|

An additional requirement is that the linking should be bidirectional. This means that with the sample data from the table above, when you browse to product 2, you should get product 1 and 4 in the list. So for a given ProductId, you should be able to get its linked products from column ProductId1 and ProductId2.

What I’ve come up with

The product entity:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> References { get; set; }
public virtual ICollection<Product> ReferencedBy { get; set; }
public Product()
{
References = new List<Product>();
ReferencedBy = new List<Product>();
}
}
</code>
<code>public class Product { public int ProductId { get; set; } public string Name { get; set; } public virtual ICollection<Product> References { get; set; } public virtual ICollection<Product> ReferencedBy { get; set; } public Product() { References = new List<Product>(); ReferencedBy = new List<Product>(); } } </code>
public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Product> References { get; set; }
    public virtual ICollection<Product> ReferencedBy { get; set; }

    public Product()
    {
        References = new List<Product>();
        ReferencedBy = new List<Product>();
    }
}

The product mapping:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class ProductMapping : EntityTypeConfiguration<Product>
{
public ProductMapping()
{
HasKey(t => t.ProductId);
Property(t => t.Name).IsRequired().HasMaxLength(150);
ToTable("Products");
Property(t => t.ProductId).HasColumnName("ProductId");
Property(t => t.Name).HasColumnName("Name");
HasMany(x => x.References).WithMany(x => x.ReferencedBy).Map(map =>
{
map.ToTable("LinkedProducts");
map.MapLeftKey("ProductId1");
map.MapRightKey("ProductId2");
});
}
}
</code>
<code>public class ProductMapping : EntityTypeConfiguration<Product> { public ProductMapping() { HasKey(t => t.ProductId); Property(t => t.Name).IsRequired().HasMaxLength(150); ToTable("Products"); Property(t => t.ProductId).HasColumnName("ProductId"); Property(t => t.Name).HasColumnName("Name"); HasMany(x => x.References).WithMany(x => x.ReferencedBy).Map(map => { map.ToTable("LinkedProducts"); map.MapLeftKey("ProductId1"); map.MapRightKey("ProductId2"); }); } } </code>
public class ProductMapping : EntityTypeConfiguration<Product>
{
    public ProductMapping()
    {
        HasKey(t => t.ProductId);

        Property(t => t.Name).IsRequired().HasMaxLength(150);

        ToTable("Products");
        Property(t => t.ProductId).HasColumnName("ProductId");
        Property(t => t.Name).HasColumnName("Name");

        HasMany(x => x.References).WithMany(x => x.ReferencedBy).Map(map =>
            {
                map.ToTable("LinkedProducts");
                map.MapLeftKey("ProductId1");
                map.MapRightKey("ProductId2");
            });
    }
}

This all works. To display the list of linked products for a certain product, I can just get the union of References and ReferencedBy:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>var product = db.Find(2);
var linkedProducts = product.References.Union(product.ReferencedBy);
</code>
<code>var product = db.Find(2); var linkedProducts = product.References.Union(product.ReferencedBy); </code>
var product = db.Find(2);
var linkedProducts = product.References.Union(product.ReferencedBy);

The problem

As I said, this works, but I don’t really like it and I was wondering if there is a better way to deal with a situation like this when working with Entity Framework.

The solution

I liked Ryathal’s suggestion to add two records to the database when linking products. So, when linking product 1 to product 2, I now insert two records: { 1, 2 } and { 2, 1 }.

This also allows me to remove the ReferencedBy collection in the Product class so that only one collections remains: References.

To link to products:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>product1.References.Add(product2);
product2.References.Add(product1);
</code>
<code>product1.References.Add(product2); product2.References.Add(product1); </code>
product1.References.Add(product2);
product2.References.Add(product1);

To remove the link between products:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>product1.References.Remove(product2);
product2.References.Remove(product1);
</code>
<code>product1.References.Remove(product2); product2.References.Remove(product1); </code>
product1.References.Remove(product2);
product2.References.Remove(product1);

Here is my new Product class with the mapping:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> References { get; set; }
public Product()
{
References = new List<Product>();
}
}
</code>
<code>public class Product { public int ProductId { get; set; } public string Name { get; set; } public virtual ICollection<Product> References { get; set; } public Product() { References = new List<Product>(); } } </code>
public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Product> References { get; set; }

    public Product()
    {
        References = new List<Product>();
    }
}

The product mapping:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class ProductMapping : EntityTypeConfiguration<Product>
{
public ProductMapping()
{
HasKey(t => t.ProductId);
Property(t => t.Name).IsRequired().HasMaxLength(150);
ToTable("Products");
Property(t => t.ProductId).HasColumnName("ProductId");
Property(t => t.Name).HasColumnName("Name");
HasMany(x => x.References).WithMany().Map(map =>
{
map.ToTable("LinkedProducts");
map.MapLeftKey("ProductId1");
map.MapRightKey("ProductId2");
});
}
}
</code>
<code>public class ProductMapping : EntityTypeConfiguration<Product> { public ProductMapping() { HasKey(t => t.ProductId); Property(t => t.Name).IsRequired().HasMaxLength(150); ToTable("Products"); Property(t => t.ProductId).HasColumnName("ProductId"); Property(t => t.Name).HasColumnName("Name"); HasMany(x => x.References).WithMany().Map(map => { map.ToTable("LinkedProducts"); map.MapLeftKey("ProductId1"); map.MapRightKey("ProductId2"); }); } } </code>
public class ProductMapping : EntityTypeConfiguration<Product>
{
    public ProductMapping()
    {
        HasKey(t => t.ProductId);

        Property(t => t.Name).IsRequired().HasMaxLength(150);

        ToTable("Products");
        Property(t => t.ProductId).HasColumnName("ProductId");
        Property(t => t.Name).HasColumnName("Name");

        HasMany(x => x.References).WithMany().Map(map =>
            {
                map.ToTable("LinkedProducts");
                map.MapLeftKey("ProductId1");
                map.MapRightKey("ProductId2");
            });
    }
}

2

Follow good database design and your problems will start getting easier, don’t think about entity framework when designing your database structures. Create your Product table and create you related product table with source product and related product ids. The requirement for linking to be bi directional is just a business rule to be handled with the application on creating related products to always insert two rows into your mapping table. Everything stays nice and simple this way.

4

I have always felt weird for creating explicit entities for my many-to-many tables. EF tries to associate many-to-many tables with some parent table. However, this usually wasn’t “good enough”. Either it didn’t make sense for properties to appear in the parent entity or my many-to-many tables were entities in their own right.

I was constantly fighting EF, having to removing associations every time I updated my model based on the database. However, I think it was worth it.

This might be a case where you making the ProductaMapping table an explicit entity is good design.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật