Перший Рамковий кодекс сутності - два іноземних ключі з однієї таблиці


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]

Як я можу створити таку модель з двома сторонніми ключами до тієї ж таблиці?

Відповіді:


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

4
@NickW: Це те, з чим ви маєте звертатися у своїй програмі, а не у картографуванні. З точки зору карти, парам дозволено грати двічі (кожна - гостьова та додому - один раз).
Ladislav Mrnka

2
У мене схожа модель. Що є правильним способом обробки каскадного видалення, якщо команда видалена? Я розглядав питання про створення тригера INSTEAD OF DELETE, але не впевнений, чи є краще рішення? Я вважаю за краще це обробляти в БД, а не в додатку.
Вудчіппер

1
@mrshickadance: Це те саме. Один підхід використовує вільні API та інший анотації даних.
Ладислав Мрнка

1
Якщо я використовую WillCascadeOnDelete false, то якщо я хочу видалити команду, то це помилка кидання. Відносини з "Team_HomeMatches" AssociationSet перебувають у стані "Видалено". Враховуючи обмеження кратності, відповідна команда "Team_HomeMatches_Target" також повинна знаходитись у стані "Видалено".
Рупеш Кумар Тіварі

55

Також можна вказати ForeignKey()атрибут властивості навігації:

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

Таким чином, вам не потрібно додавати код до OnModelCreateметоду


4
У будь-якому випадку я отримую той самий виняток.
Джо Смо

11
Це мій стандартний спосіб визначення зовнішніх ключів, який працює у всіх випадках, ВКЛЮЧЕНО, коли суб'єкт господарювання містить більше одного властивості навігації одного типу (подібний до сценарію HomeTeam та GuestTeam), і в цьому випадку EF плутається в створенні SQL. Рішенням є додавання коду OnModelCreateвідповідно до прийнятої відповіді, а також двох колекцій для обох сторін відносин.
Стівен Мануель

Я використовую onmodelcreating у всіх випадках, крім згаданого випадку, я використовую зовнішній ключ анотації даних, а також я не знаю, чому це не прийнято !!
хосам 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


1
Дякуємо за цю відповідь, однак це робить стовпці зовнішнього ключа нульовими в таблиці відповідності.
RobHurd

Для мене це чудово спрацювало в EF 6, де потрібні мінливі колекції.
Пінт

Якщо ви хочете уникнути вільних api (з будь-якої причини #differentdiscus), це працює фантастично. У моєму випадку мені потрібно було включити додаткову анотацію foriegnKey до об'єкта "Match", оскільки в моїх полях / таблицях є рядки для ПК.
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.

У моєму випадку ця проста модифікація вирішує проблему.


3
Обережність читачів. Хоча це може вирішити проблему визначення схеми, вона змінює семантику. Напевно, не так, що матч може бути без двох команд.
N8allan

14

Це пов’язано з тим, що видалення Cascade увімкнено за умовчанням. Проблема полягає в тому, що, коли ви викликаєте видалення на об'єкті, воно також видалить кожне з об'єктів, на які посилається f-ключ. Ви не повинні робити «необхідні» значення зведеними для усунення цієї проблеми. Кращим варіантом було б видалити конвенцію про видалення каскаду EF Code First:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 

Напевно, безпечніше чітко вказати, коли робити каскадне видалення для кожного з дітей під час відображення / конфігурування. суб'єкт господарювання.


Отже, що це після виконання цього? Restrictзамість Cascade?
Джо Смо

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; }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.