自我引用多对多关系

时间:2016-09-27 15:00:13

标签: asp.net-core entity-framework-core

我有一个Ticket实体:

    public class Ticket
    { 
        public int Id { get; set; }
        public string Title { get; set; }

        public virtual ICollection<Relation> RelatedTickets { get; set; }
    }

我想在Entity Framework Core中设置多对多的自我关系,所以我做了两个一对多的关系:

public class Relation
{
    [Required, ForeignKey("TicketFrom")]
    public int FromId { get; set; }

    [Required, ForeignKey("TicketTo")]
    public int ToId { get; set; }

    public virtual Ticket TicketFrom { get; set; }
    public virtual Ticket TicketTo { get; set; }
}

我尝试使用流畅的API创建关系:

        builder.Entity<Relation>()
               .HasKey(uc => new { uc.FromId, uc.ToId });
        builder.Entity<Relation>()
           .HasOne(c => c.TicketFrom)
           .WithMany(p => p.RelatedTickets)
           .HasForeignKey(pc => pc.FromId);
        builder.Entity<Relation>()
           .HasOne(c => c.TicketTo)
           .WithMany(p => p.RelatedTickets)
           .HasForeignKey(pc => pc.ToId);

但结果我有一个错误:

  

无法在'Ticket.RelatedTickets'和。之间建立关系   'Relation.TicketTo',因为之间已经存在关系   'Ticket.RelatedTickets'和'Relation.TicketForm'。导航   属性只能参与一个关系。

可能的解决方案是直接将父关系添加到TicketEntity

public class Ticket
{ 
    public int Id { get; set; }

    [Required, ForeignKey("ParentRelation")]
    public Nullable<int> ParentRelationId { get; set; }

    public virtual Ticket ParentRelation {get;set;}

    public virtual ICollection<Ticket> RelatedTickets { get; set; }
    ...
}

像这样流利的api:

modelBuilder.Entity<Ticket> =>
{
    entity
        .HasMany(e => e.RelatedTickets)
        .WithOne(e => e.ParentRelation) 
        .HasForeignKey(e => e.ParentRelationId );
});

但是像这样存储父母关系看起来很“脏” 什么是正确的方法?

3 个答案:

答案 0 :(得分:5)

不可能只有一个有关系的集合。您需要两个 - 一个关系,票证等于TicketFrom,第二个关系票证等于TicketTo

这样的事情:

型号:

public class Ticket
{ 
    public int Id { get; set; }
    public string Title { get; set; }

    public virtual ICollection<Relation> RelatedTo { get; set; }
    public virtual ICollection<Relation> RelatedFrom { get; set; }
}

public class Relation
{
    public int FromId { get; set; }
    public int ToId { get; set; }

    public virtual Ticket TicketFrom { get; set; }
    public virtual Ticket TicketTo { get; set; }
}

配置:

modelBuilder.Entity<Relation>()
    .HasKey(e => new { e.FromId, e.ToId });

modelBuilder.Entity<Relation>()
    .HasOne(e => e.TicketFrom)
    .WithMany(e => e.RelatedTo)
    .HasForeignKey(e => e.FromId);

modelBuilder.Entity<Relation>()
    .HasOne(e => e.TicketTo)
    .WithMany(e => e.RelatedFrom)
    .HasForeignKey(e => e.ToId);

请注意,使用Parent的解决方案并不等效,因为它会创建one-to-many关联,而如果我理解正确您正在寻找many-to-many

答案 1 :(得分:1)

这里很好地解释了如何在EF Core中建立多对多关系 Many-to-many self referencing relationship

  

每个集合或参考导航属性只能是单个关系的一部分。与显式联接实体的多对多关系是通过两个一对多关系实现的。连接实体包含两个参考导航属性,但主要实体只有一个集合导航属性,该属性必须与其中一个相关联,而不能与两者相关联。

   builder.Entity<Relation>()
           .HasKey(uc => new { uc.FromId, uc.ToId });

    builder.Entity<Relation>()
       .HasOne(c => c.TicketFrom)
       .WithMany() // <-- one of this must be empty
       .HasForeignKey(pc => pc.FromId)
       .OnDelete(DeleteBehavior.Restrict);

    builder.Entity<Relation>()
       .HasOne(c => c.TicketTo)
       .WithMany(p => p.RelatedTickets)
       .HasForeignKey(pc => pc.ToId);
  

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

     

请注意,您必须关闭删除级联。

答案 2 :(得分:0)

@IvanStoev 是正确的。这是一个更一般的自我引用与许多父母和许多孩子的多对多关系的例子。

public class Ticket
{
    [Key]
    public int Id { get; set; }
    public string Title { get; set; }
 
    public List<TicketTicket> TicketChildren { get; set; }
    public List<TicketTicket> TicketParents { get; set; }
}

public class TicketTicket
{
    public int TicketChildId { get; set; }

    public Ticket TicketChild { get; set; }
    
    public int TicketParentId { get; set; }
        
    public Ticket TicketParent { get; set; }
}

modelBuilder.Entity<TicketTicket>()
    .HasKey(tt => new {tt.TicketChildId, tt.TicketParentId});

modelBuilder.Entity<Ticket>()
    .HasMany(t => t.TicketChildren)
    .WithOne(tt => tt.ProductParent)
    .HasForeignKey(f => tt.ProductParentId);

modelBuilder.Entity<Ticket>()
    .HasMany(t => t.TicketParents)
    .WithOne(tt => tt.TicketChild)
    .HasForeignKey(tt => tt.TicketChildId);