实体框架代码优先 - 来自同一个表的两个外键

Jar*_*rek 252 c# orm entity-framework code-first entity-framework-4.1

我刚开始使用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; }
}
Run Code Online (Sandbox Code Playgroud)

我得到一个例外:

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

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

Lad*_*nka 286

试试这个:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @NickW:这是你必须在你的应用程序中处理的东西,而不是映射.从映射的角度来看,允许对玩两次(每次是宾客和家庭一次). (4认同)
  • 如果允许两支球队只参加一次比赛怎么办? (3认同)
  • 我有一个类似的模型.如果团队被移除,处理级联删除的正确方法是什么?我研究了创建一个INSTEAD OF DELETE触发器,但不确定是否有更好的解决方案?我更愿意在数据库中处理这个问题,而不是应用程序. (2认同)

小智 51

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

[ForeignKey("HomeTeamID")]
public virtual Team HomeTeam { get; set; }
[ForeignKey("GuestTeamID")]
public virtual Team GuestTeam { get; set; }
Run Code Online (Sandbox Code Playgroud)

这样,你不需要任何代码添加到OnModelCreate方法

  • 这是我指定适用于所有情况的外键的标准方法EXCEPT当实体包含多个相同类型的nav属性时(类似于HomeTeam和GuestTeam场景),在这种情况下,EF在生成SQL时感到困惑.解决方案是根据接受的答案向"OnModelCreate"添加代码以及关系两侧的两个集合. (9认同)
  • 无论哪种方式我都得到同样的例外. (4认同)

kho*_*_89 42

我知道这是一个几年的帖子,你可以解决上述解决方案的问题.但是,我只想建议对仍然需要的人使用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; }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这对我来说非常有效。顺便提一句。如果您不希望列可以为空,您可以使用 [ForeignKey] 属性指定外键。如果该键不可为空,那么一切都已准备就绪。 (2认同)

Mai*_*ico 13

你也可以试试这个:

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; }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 谨慎读者.虽然这可能会解决模式定义问题,但它会改变语义.如果没有两支球队,可能不会有比赛. (3认同)

小智 13

这是因为默认情况下启用Cascade Deletes.问题是,当您在实体上调用删除时,它也会删除每个f键引用的实体.您不应该使"必需"值可以为空来解决此问题.更好的选择是删除EF Code First的Cascade删除约定:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 
Run Code Online (Sandbox Code Playgroud)

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


小智 11

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

反性质

因此,所需的解决方案是:

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; }
}
Run Code Online (Sandbox Code Playgroud)