多对多自引用关系

26
我对EF还不熟悉。我遇到了一个创建自引用多对多关系的问题。 我尝试使用以下解决方案:Entity Framework Core: many-to-many relationship with same entity 我的实体:
public class WordEntity
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Json { get; set; }

    public virtual List<WordSinonymEntity> Sinonyms { get; set; }
}


public class WordSinonymEntity
{
    public long WordId { get; set; }
    public virtual WordEntity Word { get; set; }

    public long SinonymId { get; set; }
    public virtual WordEntity Sinonym { get; set; }
}

接下来是配置:

 modelBuilder.Entity<WordSinonymEntity>()
     .HasOne(pt => pt.Sinonym)
     .WithMany(p => p.Sinonyms)
     .HasForeignKey(pt => pt.SinonymId);

modelBuilder.Entity<WordSinonymEntity>()
    .HasOne(pt => pt.Word)
    .WithMany(t => t.Sinonyms)
    .HasForeignKey(pt => pt.WordId);`

但它会导致下一个异常。

System.InvalidOperationException:“无法在'WordEntity.Sinonyms'和'WordSinonymEntity.Word'之间创建关系,因为'WordEntity.Sinonyms'和'WordSinonymEntity.Sinonym'之间已经存在关系。导航属性只能参与一个关系。”

有人可以帮助我或者提供一些学习示例吗? 谢谢。

1个回答

45
你正在关注的帖子是错误的。
每个集合或引用导航属性只能成为单一关系的一部分。虽然具有显式连接实体的多对多关系是通过两个一对多关系实现的,但连接实体包含两个引用导航属性,而主实体仅有单个集合导航属性,必须将其与其中一个相关联,而不是两者都关联。
解决此问题的一种方法是添加第二个集合导航属性:
public class WordEntity
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Json { get; set; }

    public virtual List<WordSinonymEntity> Sinonyms { get; set; }
    public virtual List<WordSinonymEntity> SinonymOf { get; set; } // <--
}

并通过流畅的API指定关联:

modelBuilder.Entity<WordSinonymEntity>()
     .HasOne(pt => pt.Sinonym)
     .WithMany(p => p.SinonymOf) // <--
     .HasForeignKey(pt => pt.SinonymId)
     .OnDelete(DeleteBehavior.Restrict); // see the note at the end

modelBuilder.Entity<WordSinonymEntity>()
    .HasOne(pt => pt.Word)
    .WithMany(t => t.Sinonyms)
    .HasForeignKey(pt => pt.WordId); 

另一种方法是保留模型不变,但将WordSinonymEntity.Sinonym映射到单向关联(具有引用导航属性和无相应集合导航属性):
modelBuilder.Entity<WordSinonymEntity>()
     .HasOne(pt => pt.Sinonym)
     .WithMany() // <--
     .HasForeignKey(pt => pt.SinonymId)
     .OnDelete(DeleteBehavior.Restrict); // see the note at the end

modelBuilder.Entity<WordSinonymEntity>()
    .HasOne(pt => pt.Word)
    .WithMany(t => t.Sinonyms)
    .HasForeignKey(pt => pt.WordId); 

请确保WithMany与相应的导航属性的存在/不存在完全匹配。

请注意,在这两种情况下,您都必须关闭至少一个关系的删除级联,并在删除主实体之前手动删除相关的连接实体,因为自引用关系总是引入可能的循环或多个级联路径问题,从而防止使用级联删除。


1
非常感谢。我尝试了两种解决方案的变体,两种变体都完美地运作。我浪费了很多时间寻找答案。我不得不早些时候在stackoverflow上提出这个问题。 - butek
1
@Burak 嘿嘿,当然。但是...不幸的是,EF Core不支持更新(修改)键,因此没有流畅的配置。您必须手动修改生成的迁移,然后通过原始SQL命令执行更新,因为正如在开头提到的那样,EF Core不会让您通过上下文API更改实体PK。 - Ivan Stoev
有一个额外的问题可能有点晚了,但我正在解决类似的问题。在这个例子中,如何从WordEntity返回它所属的所有同义词列表?即Synonyms和SynonymsOf的连接列表。我的第一个想法是添加另一个属性,并使用getter返回完全相同的内容(Synonyms.Concat(SynonymsOf)),但是否有EF Core方法呢? - Sad-EyedLadyoftheLowlands
1
@Sad-EyedLadyoftheLowlands 目前没有 EF Core 解决方案。您需要在任何需要的地方手动使用 Concat。如果添加此类属性,请确保它不被视为单独的关系。还请注意,它不能在 LINQ to Entities 查询中使用。 - Ivan Stoev
1
@DavidY 树形层次结构通常使用单个一对多自引用关系(同一张表)进行建模,因此需要一个可选的(可为 null)引用导航属性和一个集合导航属性,并将它们与Has / With 流畅API配对(并关闭级联删除,因为存在可能循环或多个级联路径的SqlServer问题)。这是你所要求的吗?如果不是,请考虑发布自己的问题,说明你的具体要求,因为答案涉及特定的OP案例,即多对多。 - Ivan Stoev
显示剩余3条评论

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