EntityFramework同表多对多关系

53
我有一个名为“Products”的表,里面显然包含着产品。但是我需要创建相关的产品。所以我创建了一个联接表,名为“product_related”,它有两个主键。一个来自“Products”表的“ProductID”,另一个也来自“Products”表的“RelatedID”。
我已经使用EF并设置了其他表上的所有内容。如何正确添加此内容以便与产品建立关系,例如:product.Products.Add(product object here)。当然这里的product代表我使用db.Products.FirstOr...从数据库中获取的产品对象。
我应该如何正确地做到这一点?让一个表与多个表进行多对多的关联?
谢谢。

2
如果这是生产软件,请不要在单个表上使用M2M,虽然它可以工作,但维护起来非常麻烦。我现在正在工作,但如果你到我回家之前还没有得到答案,我会向你展示一个适当的解决方案。 - Chazt3n
你是在问如何使用Entity Framework向product_related表中添加数据吗? - Andrew Walters
@Chazt3n 有什么替代方案吗? - Max
3个回答

91
为了使用Database-First approach创建多对多关系,你需要设置符合以下规则的数据库架构:
  • 创建一个带有ProductID列作为主键的Products
  • 创建一个带有ProductID列和RelatedID列,并将两个列都标记为主键(复合主键)的ProductRelations
  • 不要在ProductRelations表中添加任何其他列。这两个关键列必须是表中唯一的列,以便让EF将此表识别为用于多对多关系的链接表
  • 在两个表之间创建两个外键关系:
    • 第一个关系将Products表作为主键表,使用ProductID作为主键,将ProductRelations表作为外键表,仅使用ProductID作为外键
    • 第二个关系也将Products表作为主键表,使用ProductID作为主键,将ProductRelations表作为外键表,仅使用RelatedID作为外键
  • 为前两个关系启用级联删除。(你不能同时将两个关系都启用,因为SQL Server不允许这样做,否则会导致多个级联删除路径。)

如果现在从这两个表生成实体数据模型,你将只得到一个实体,即一个Product实体(如果禁用单数化,则可能是Products)。 链接表ProductRelations将不会公开为实体。

Product实体将具有两个导航属性

public EntityCollection<Product> Products { get { ... } set { ... } }
public EntityCollection<Product> Products1 { get { ... } set { ... } }
这些导航集合是同一多对多关系的两个端点。(如果您想要通过多对多关系连接两个不同的表,例如表 AB,则一个导航集合(Bs)将在实体A中,另一个(As)将在实体 B中。但因为您的关系是"自引用",所以两个导航属性都在实体Product中。)
这两个属性的含义是: Products是与给定产品相关的产品,Products1是指向给定产品的产品。例如: 如果关系意味着一个产品需要其他产品作为零件进行制造,并且您拥有产品“笔记本电脑”、“处理器”、“硅片”,那么“处理器”由“硅片”制成(“硅片”是产品实体 ProcessorProducts 集合中的一个元素),并被“笔记本电脑”使用(“笔记本电脑”是产品实体ProcessorProducts1 集合中的一个元素)。取名为 MadeOfUsedBy会更加合适。
如果您只对关系的一侧感兴趣,可以安全地从生成的模型中删除其中一个集合。例如,在模型设计器表面上删除Products1即可。您还可以重命名属性,关系仍将是多对多。
编辑
如评论所要求,使用Code-First方法实现的模型和映射如下:
模型:
public class Product
{
    public int ProductID { get; set; }

    public ICollection<Product> RelatedProducts { get; set; }
}

映射:

public class MyContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>()
            .HasMany(p => RelatedProducts)
            .WithMany()
            .Map(m =>
            {
                m.MapLeftKey("ProductID");
                m.MapRightKey("RelatedID");
                m.ToTable("product_related");
            });
    }
}

3
使用Code-First时,如何使用Fluent API指定关系? - drzaus
4
请看我的编辑。实际上,在注意到被问及 DB-First 之后,我曾将其作为答案删除了。现在从已删除的答案中找回了代码 :) - Slauma
3
如果你想要实现延迟加载效果,请不要忘记将 RelatedProducts 属性标记为 virtual。否则,除非你在需要它们时明确地包含/加载它们,否则该属性似乎总是为空。 - Funka
2
这不应该是:.HasMany(p => p.RelatedProducts)吗? - Richard Pawson
2
嘿,你知道为什么我一直收到关于 CollectionNavigationBuilder<Person, Person> 不包含 WithMany() 定义的错误吗?我是不是缺少某个库的引用或其他东西? - pjrki
显示剩余4条评论

0

让我们以您的示例为例:

相关表格

  Related_id      PK
  Related_name
  Date

产品表格

  Product_id      PK
  Related_id      FK
  Product_Name
  Date

如何在EF中表示它
相关的模型类命名为RelatedModel
  [Key]
  public int Related_id { get; set; }

  public string Related_name {get;set}

  public Datetime Date{get;set;}

产品模型类被命名为ProductModel

   [Key]
  public int Product_id { get; set; }

  public string Product_name {get;set}

  public string Related_id {get;set}

  public Datetime Date{get;set;}

  [ForeignKey("Related_id ")]      //We  can also specify here Foreign key
  public virtual RelatedModel Related { get; set; } 

通过这种方式,我们可以在两个表之间创建关系

现在,在多对多关系的情况下,我想在这里举一个例子

假设我有一个模型类 Enrollment.cs

public class Enrollment
{
    public int EnrollmentID { get; set; }
    public int CourseID { get; set; }
    public int StudentID { get; set; }
    public decimal? Grade { get; set; }
    public virtual Course Course { get; set; }
    public virtual Student Student { get; set; }
}

这里CourseID和StudentId是两个外键。

现在我有另一个类Course.cs,我们将在其中创建多对多关系。

public class Course
{
    public int CourseID { get; set; }
    public string Title { get; set; }
    public int Credits { get; set; }
    public virtual ICollection<Enrollment> Enrollments { get; set; }
}

希望这能有所帮助!!!


2
你的回答离问题实在太远了。1)你的第一个例子是一对多,2)第二个例子也是一对多(尽管你说它应该是多对多)。3)你的关系不是在同一张表之间,4)他没有像你的例子那样使用 Code-First。 - Slauma

0
现在在2023年,我们有Entity Framework Core,而且这个版本中解决这个问题的方法与之前版本中解决同样问题的方法不同!
微软将这种依赖称为对称自引用多对多。
public class Product
{
    public int ProductID { get; set; }
    public List<Product> RelatedProducts { get; set; } = new();
}

无论你喜不喜欢,Entity Framework Core在这种情况下无法正确构建一个多对多的关系。所以你必须帮助它。 最好的办法是将其映射为单向的多对多关系。例如:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .HasMany(p => p.RelatedProducts)
        .WithMany();
}

如果由于某种原因,产品A必须在其相关产品中拥有产品B,而产品B必须在其相关产品中拥有产品A。 请按照以下步骤操作:
ApplePhone15.RelatedProducts.Add(AppleWatch);
AppleWatch.RelatedProducts.Add(ApplePhone15);

阅读有关多对多关系的更多详细信息,请访问官方页面 - EF Core多对多关系。我发现研究这个页面很有帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接