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


260

我刚开始使用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]

如何使用两个指向同一个表的外键创建这样的模型?

Answers:


296

试试这个:

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引用的单个集合。匹配映射没有级联删除,因为它在这些自引用多对多中不起作用。


3
如果两个团队只允许玩一次该怎么办?
ca9163d9 2013年

4
@NickW:那是您必须在应用程序中而不是在映射中处理的事情。从映射的角度来看,允许双人对玩两次(每个双人都是宾客和家庭一次)。
Ladislav Mrnka

2
我有一个类似的模型。如果删除团队,处理层叠删除的正确方法是什么?我研究过创建INSTEAD OF DELETE触发器,但不确定是否有更好的解决方案?我希望在数据库中而不是在应用程序中处理。
Woodchipper 2014年

1
@mrshickadance:一样。一种方法使用流畅的API,另一种使用数据注释。
Ladislav Mrnka 2014年

1
如果我使用WillCascadeOnDelete为false,那么如果我想删除Team,则抛出错误。“ Team_HomeMatches” AssociationSet中的关系处于“已删除”状态。给定多重性约束,相应的“ Team_HomeMatches_Target”也必须处于“已删除”状态。
Rupesh Kumar Tiwari

55

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

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

这样,您无需向该OnModelCreate方法添加任何代码


4
无论哪种方式,我都会遇到相同的异常。
Jo Smo

11
这是我指定外键的标准方法,该方法适用于所有情况,但当一个实体包含多个相同类型的nav属性(类似于HomeTeam和GuestTeam场景)时,在这种情况下,EF在生成SQL时会感到困惑。解决方案是OnModelCreate根据已接受的答案以及关系双方的两个集合来添加代码。
史蒂文·曼努埃尔

除了提到的情况,我在所有情况下都使用onmodelcreating,我使用数据注释外键,我也不知道为什么不接受它!
hosam hemaily

48

我知道这是一个已有多年历史的职位,您可以使用上述解决方案解决您的问题。但是,我只想建议仍然需要使用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/zh-cn/data/jj591583?f = 255 & MSPPError = -2147217396#Relationships


1
感谢您提供此答案,但是这会使外键列在Match表中为空。
RobHurd

在需要可为null的集合的EF 6中,这对我来说非常有用。
Pynt

如果您想避免使用流利的api(无论出于何种原因#differentdiscussion),它都非常有效。在我的情况下,我需要在“匹配”实体上包括一个额外的foriegnKey注释,因为我的字段/表具有用于PK的字符串。
弟子

1
这对我来说非常有用。顺便说一句。如果您不希望列为空,则可以使用[ForeignKey]属性指定外键。如果密钥不能为空,那么您已经全部准备就绪。
雅各布·霍洛夫斯基

16

您也可以尝试以下方法:

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列允许NULL时,您就在打破循环。或者我们只是在欺骗EF模式生成器。

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


3
注意读者。尽管这可能会解决架构定义问题,但会更改语义。没有两支球队就不可能有一场比赛。
N8allan

14

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

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 

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


那么执行完之后又是什么呢?Restrict代替Cascade
Jo Smo

4

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; }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.