Entity Framework Code First - два внешних ключа из одной таблицы

Я только начал использовать код 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]

Как создать такую модель с двумя внешними ключами для одной таблицы?

Ответ 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. Матч отображается без каскадного удаления, потому что он не работает в этих ссылках со многими из многих.

Ответ 2

Также можно указать атрибут ForeignKey() для свойства навигации:

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

Таким образом, вам не нужно добавлять код в метод OnModelCreate

Ответ 3

Я знаю это несколько лет назад, и вы можете решить свою проблему с помощью вышеупомянутого решения. Тем не менее, я просто хочу предложить использовать 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; }
}

Вы можете узнать больше о InverseProperty в MSDN: https://msdn.microsoft.com/en-us/data/jj591583?f=255&MSPPError=-2147217396#Relationships

Ответ 4

Вы также можете попробовать:

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.

В моем случае эта простая модификация решает проблему.

Ответ 5

Это связано с тем, что Cascade Deletes включены по умолчанию. Проблема заключается в том, что при вызове delete в сущности он также удаляет все связанные с f-key объекты. Вы не должны указывать обязательные значения, чтобы устранить эту проблему. Лучшим вариантом было бы удалить правило EF Code First Cascade delete:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 

Вероятно, безопаснее явно указывать, когда делать каскадное удаление для каждого из детей при сопоставлении /config. объект.

Ответ 6

InverseProperty в EF Core делает решение простым и чистым.

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; }
}