【问题标题】:Entity Framework Code First - two Foreign Keys from same table实体框架代码优先 - 来自同一个表的两个外键
【发布时间】:2011-07-30 08:45:27
【问题描述】:

我刚开始使用 EF 代码,所以我完全是这个主题的初学者。

我想在团队和比赛之间建立关系:

1 场比赛 = 2 支球队(主队、客队)和结果。

我认为创建这样的模型很容易,所以我开始编码:

public class Team
{
    [Key]
    public int TeamId { get; set;} 
    public string Name { get; set; }

    public virtual ICollection<Match> Matches { get; set; }
}


public class Match
{
    [Key]
    public int MatchId { get; set; }

    [ForeignKey("HomeTeam"), Column(Order = 0)]
    public int HomeTeamId { get; set; }
    [ForeignKey("GuestTeam"), Column(Order = 1)]
    public int GuestTeamId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public virtual Team HomeTeam { get; set; }
    public virtual Team GuestTeam { get; set; }
}

我得到一个例外:

引用关系将导致不允许的循环引用。 [约束名称 = Match_GuestTeam]

如何创建这样一个模型,同一个表有 2 个外键?

【问题讨论】:

    标签: c# entity-framework orm code-first entity-framework-4.1


    【解决方案1】:

    试试这个:

    public class Team
    {
        public int TeamId { get; set;} 
        public string Name { get; set; }
    
        public virtual ICollection<Match> HomeMatches { get; set; }
        public virtual ICollection<Match> AwayMatches { get; set; }
    }
    
    public class Match
    {
        public int MatchId { get; set; }
    
        public int HomeTeamId { get; set; }
        public int GuestTeamId { get; set; }
    
        public float HomePoints { get; set; }
        public float GuestPoints { get; set; }
        public DateTime Date { get; set; }
    
        public virtual Team HomeTeam { get; set; }
        public virtual Team GuestTeam { get; set; }
    }
    
    
    public class Context : DbContext
    {
        ...
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Match>()
                        .HasRequired(m => m.HomeTeam)
                        .WithMany(t => t.HomeMatches)
                        .HasForeignKey(m => m.HomeTeamId)
                        .WillCascadeOnDelete(false);
    
            modelBuilder.Entity<Match>()
                        .HasRequired(m => m.GuestTeam)
                        .WithMany(t => t.AwayMatches)
                        .HasForeignKey(m => m.GuestTeamId)
                        .WillCascadeOnDelete(false);
        }
    }
    

    默认约定映射主键。球队必须有两个比赛集合。您不能有两个 FK 引用的单个集合。匹配在没有级联删除的情况下映射,因为它在这些自引用多对多中不起作用。

    【讨论】:

    • 如果两支球队只能打一场怎么办?
    • @NickW:这是您必须在应用程序中而不是在映射中处理的事情。从映射的角度来看,允许双人玩两次(每人一次是客人和一次家)。
    • 我有一个类似的模型。如果团队被删除,处理级联删除的正确方法是什么?我考虑创建一个 INSTEAD OF DELETE 触发器,但不确定是否有更好的解决方案?我更愿意在数据库中处理这个,而不是应用程序。
    • @mrshickadance:是一样的。一种方法使用流式 API 和另一种数据注释。
    • 如果我使用 WillCascadeOnDelete false 那么如果我想删除团队 那么它会抛出错误。来自“Team_HomeMatches”AssociationSet 的关系处于“已删除”状态。给定多重约束,相应的“Team_HomeMatches_Target”也必须处于“已删除”状态。
    【解决方案2】:

    你也可以试试这个:

    public class Match
    {
        [Key]
        public int MatchId { get; set; }
    
        [ForeignKey("HomeTeam"), Column(Order = 0)]
        public int? HomeTeamId { get; set; }
        [ForeignKey("GuestTeam"), Column(Order = 1)]
        public int? GuestTeamId { get; set; }
    
        public float HomePoints { get; set; }
        public float GuestPoints { get; set; }
        public DateTime Date { get; set; }
    
        public virtual Team HomeTeam { get; set; }
        public virtual Team GuestTeam { get; set; }
    }
    

    当您使 FK 列允许 NULLS 时,您正在打破循环。或者我们只是在欺骗 EF 模式生成器。

    就我而言,这个简单的修改解决了问题。

    【讨论】:

    • 读者谨慎。尽管这可能会解决模式定义问题,但它会改变语义。没有两支球队的比赛可能不是这样的。
    【解决方案3】:

    还可以在导航属性上指定ForeignKey() 属性:

    [ForeignKey("HomeTeamID")]
    public virtual Team HomeTeam { get; set; }
    [ForeignKey("GuestTeamID")]
    public virtual Team GuestTeam { get; set; }
    

    这样您就不需要在OnModelCreate 方法中添加任何代码

    【讨论】:

    • 我得到了同样的异常。
    • 这是我指定外键的标准方法,适用于所有情况,除非实体包含多个相同类型的导航属性(类似于 HomeTeam 和 GuestTeam 场景),在这种情况下 EF在生成 SQL 时感到困惑。解决方案是根据接受的答案以及关系双方的两个集合将代码添加到OnModelCreate
    • 我在除上述情况外的所有情况下都使用onmodelcreating,我使用数据注释外键,我也不知道为什么它不被接受!!
    【解决方案4】:

    这是因为级联删除默认启用。问题是当您在实体上调用 delete 时,它​​也会删除每个 f-key 引用的实体。您不应将“必需”值设为可空来解决此问题。更好的选择是删除 EF Code First 的级联删除约定:

    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 
    

    在映射/配置时明确指示何时对每个子级执行级联删除可能更安全。实体。

    【讨论】:

    • 那么这个执行完之后是什么呢? Restrict 而不是 Cascade?
    【解决方案5】:

    我知道这是一个几年前的帖子,您可以通过上述解决方案解决您的问题。但是,我只是想建议仍然需要的人使用 InverseProperty。至少您不需要更改 OnModelCreating 中的任何内容。

    以下代码未经测试。

    public class Team
    {
        [Key]
        public int TeamId { get; set;} 
        public string Name { get; set; }
    
        [InverseProperty("HomeTeam")]
        public virtual ICollection<Match> HomeMatches { get; set; }
    
        [InverseProperty("GuestTeam")]
        public virtual ICollection<Match> GuestMatches { get; set; }
    }
    
    
    public class Match
    {
        [Key]
        public int MatchId { get; set; }
    
        public float HomePoints { get; set; }
        public float GuestPoints { get; set; }
        public DateTime Date { get; set; }
    
        public virtual Team HomeTeam { get; set; }
        public virtual Team GuestTeam { get; set; }
    }
    

    您可以在 MSDN 上阅读有关 InverseProperty 的更多信息:https://msdn.microsoft.com/en-us/data/jj591583?f=255&MSPPError=-2147217396#Relationships

    【讨论】:

    • 感谢您的回答,但它使匹配表中的外键列可以为空。
    • 这在需要可空集合的 EF 6 中非常适合我。
    • 如果你想避免使用流利的 api(无论出于何种原因#differentdiscussion),这非常有效。在我的情况下,我需要在“匹配”实体上包含一个额外的 foriegnKey 注释,因为我的字段/表有 PK 的字符串。
    • 这对我很有用。顺便提一句。如果您不希望列可为空,您可以使用 [ForeignKey] 属性指定外键。如果键不可为空,则一切就绪。
    【解决方案6】:

    EF Core 中的InverseProperty 使解决方案变得简单而干净。

    InverseProperty

    所以理想的解决方案是:

    public class Team
    {
        [Key]
        public int TeamId { get; set;} 
        public string Name { get; set; }
    
        [InverseProperty(nameof(Match.HomeTeam))]
        public ICollection<Match> HomeMatches{ get; set; }
    
        [InverseProperty(nameof(Match.GuestTeam))]
        public ICollection<Match> AwayMatches{ get; set; }
    }
    
    
    public class Match
    {
        [Key]
        public int MatchId { get; set; }
    
        [ForeignKey(nameof(HomeTeam)), Column(Order = 0)]
        public int HomeTeamId { get; set; }
        [ForeignKey(nameof(GuestTeam)), Column(Order = 1)]
        public int GuestTeamId { get; set; }
    
        public float HomePoints { get; set; }
        public float GuestPoints { get; set; }
        public DateTime Date { get; set; }
    
        public Team HomeTeam { get; set; }
        public Team GuestTeam { get; set; }
    }
    

    【讨论】:

    • 为什么我们需要'Column(Order = 0)'?顺便解决了我的问题
    • 用于对列进行排序。从原始问题复制。它与逆属性无关。
    【解决方案7】:

    我知道这是一个很老的问题,但 2021 年来到这里,下面的 EF Core > 3 解决方案对我有用。

    1. 确保外键可以为空

    2. 指定删除的默认行为

      public class Match 
      { 
         public int? HomeTeamId { get; set; }
         public int? GuestTeamId { get; set; }
      
         public float HomePoints { get; set; }
         public float GuestPoints { get; set; }
         public DateTime Date { get; set; }
      
         public Team HomeTeam { get; set; }
         public Team GuestTeam { get; set; }
      }
      
      protected override void OnModelCreating(ModelBuilder modelBuilder)
      {
          modelBuilder.Entity<Match>()
                      .HasRequired(m => m.HomeTeam)
                      .WithMany(t => t.HomeMatches)
                      .HasForeignKey(m => m.HomeTeamId)
                      .OnDelete(DeleteBehavior.ClientSetNull);
      
          modelBuilder.Entity<Match>()
                      .HasRequired(m => m.GuestTeam)
                      .WithMany(t => t.AwayMatches)
                      .HasForeignKey(m => m.GuestTeamId)
                      .OnDelete(DeleteBehavior.ClientSetNull);
      }
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-11-20
      • 1970-01-01
      • 1970-01-01
      • 2011-08-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多