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

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]

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

Ярек
источник

Ответы:

297

Попробуй это:

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

Первичные ключи отображаются по умолчанию. Команда должна иметь две коллекции матчей. Вы не можете иметь одну коллекцию, на которую ссылаются два ФК. Сопоставление сопоставляется без каскадного удаления, потому что оно не работает во многих случаях.

Ладислав Мрнка
источник
3
Что если две команды могут играть только один раз?
ca9163d9
4
@NickW: Это то, что вы должны обрабатывать в своем приложении, а не в отображении. С точки зрения картирования, парам разрешено играть дважды (каждый - гость и дом один раз).
Ладислав Мрнка
2
У меня есть похожая модель. Как правильно обработать каскадное удаление, если команда удалена? Я смотрел на создание триггера INSTEAD OF DELETE, но не уверен, что есть лучшее решение? Я бы предпочел обрабатывать это в БД, а не в приложении.
Дровосек
1
@mrshickadance: это то же самое. Один подход использует свободный API и другой аннотации данных.
Ладислав Мрнка,
1
Если я использую WillCascadeOnDelete false, то если я хочу удалить команду, значит, выдается ошибка. Отношение из AssociationSet Team_HomeMatches находится в состоянии «Удалено». Учитывая ограничения множественности, соответствующий Team_HomeMatches_Target также должен находиться в состоянии «Удалено».
Рупеш Кумар Тивари
55

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

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

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

ShaneA
источник
4
Я получаю одно и то же исключение в любом случае.
Джо Смо
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; }
}

Подробнее об InverseProperty можно прочитать в MSDN: https://msdn.microsoft.com/en-us/data/jj591583?f=255&MSPPError=-2147217396#Relationships

khoa_chung_89
источник
1
Спасибо за этот ответ, однако он делает столбцы внешнего ключа обнуляемыми в таблице соответствия.
RobHurd
Это отлично сработало для меня в EF 6, где были нужны обнуляемые коллекции.
Пинт
Если вы хотите избежать свободного API (по любой причине #differentdiscussion), это работает фантастически. В моем случае мне нужно было добавить дополнительную аннотацию foriegnKey к сущности «Match», потому что в моих полях / таблицах есть строки для PK.
DiscipleMichael
1
Это сработало для меня. Btw. если вы не хотите, чтобы столбцы обнулялись, вы можете просто указать внешний ключ с атрибутом [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 разрешить NULLS, вы прерываете цикл. Или мы просто обманываем генератор схемы EF.

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

Maico
источник
3
Осторожно, читатели. Хотя это может обойти проблему определения схемы, она изменяет семантику. Вероятно, дело не в том, что Матч можно провести без двух команд.
N8allan
14

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

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 

Вероятно, безопаснее явно указать, когда выполнять каскадное удаление для каждого из дочерних элементов при отображении / конфигурации. организация.

Juls
источник
Так что же это после того, как это выполнено? Restrictвместо Cascade?
Джо Смо
4

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; }
}
прите агравал
источник