Как я могу реализовать двунаправленную связь 1..n в Entity Framework Code First

Я абсолютно озадачен, пытаясь понять, как реализовать двунаправленную связь 1..n в Entity Framework с использованием Code First. Например, у команды (представленной сущностью Team) есть тренер и менеджер (оба представлены сущностью Person). Итак, моя модель команды может быть следующей:

public class Team
{
    public Team()
    {
        Manager = new Person();
        Coach = new Person();
    }

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int TeamID { get; set; }
    public string Name { get; set; }

    [ForeignKey("Manager")]
    public int ManagerID { get; set; }
    public virtual Person Manager { get; set; }

    [ForeignKey("Coach")]
    public int CoachID { get; set; }
    public virtual Person Coach { get; set; }
}

Я могу реализовать одностороннюю навигацию, реализовав объект Person следующим образом:

public class Person
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int PersonID { get; set; }
    public string Name { get; set; }
}

и свободный API:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Team>()
        .HasRequired(t => t.Manager);

    modelBuilder.Entity<Team>()
        .HasRequired(t => t.Coach);

    base.OnModelCreating(modelBuilder);
}

Однако, хотя это позволяет мне переходить от объекта «Команда» к соответствующему Coach и Manager (оба экземпляра Person), это не позволяет мне напрямую переходить от Coach или Manager к соответствующей команде. Итак, чтобы реализовать двустороннюю навигацию, я изменил сущность Person следующим образом:

public class Person
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int PersonID { get; set; }
    public string Name { get; set; }

    [ForeignKey("Team")]
    public int TeamID { get; set; }
    public virtual Team Team { get; set; }
}

Пока все идет нормально, я получаю следующую ошибку времени выполнения, когда пытаюсь сохранить в базе данных:

System.Data.Entity.Core.UpdateException
InnerException: Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.

Итак, чтобы указать порядок между объектами, я попытался изменить свободный API, добавив «WithRequiredPricipal» следующим образом:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Team>()
        .HasRequired(t => t.Manager)
        .WithRequiredPrincipal(t => t.Team);

    modelBuilder.Entity<Team>()
        .HasRequired(t => t.Coach)
        .WithRequiredPrincipal(t => t.Team);

    base.OnModelCreating(modelBuilder);
}

Однако, когда я пытаюсь выполнить «надстройку-миграцию» в консоли диспетчера пакетов, я получаю следующую ошибку:

System.InvalidOperationException: The navigation property 'Team' declared on type 'RelatedEntities.Models.Person' has been configured with conflicting foreign keys.

То, что я пытаюсь достичь, кажется простым требованием, но я много раз искал решение, но пока не нашел ответа. Мне что-то не хватает в текучем API или аннотациях?

(Я не хочу использовать обходной путь, например, реализацию отдельных моделей для тренера и менеджера, потому что у команды может быть множество других ролей (например, помощник тренера, менеджер по связям с общественностью и т. Д.). Я бы хотел, чтобы каждая была просто экземпляром сущности Person.)


person Harry Smith    schedule 03.09.2014    source источник
comment
1 к 1 или от 1 ко многим? кажется, что вы хотите иметь два свойства коллекции Team на Person   -  person Yuliam Chandra    schedule 03.09.2014


Ответы (2)


Вы не определили ограничения внешнего ключа.

.HasRequired(t => t.Manager)
.WithMany()
.HasForeignKey(t => t.ManagerId)

В качестве примечания. Если ваш менеджер управлял несколькими командами, WithMany будет .WithMany(m => m.Teams), а вашей модели менеджера потребуется что-то вроде этого:

private ICollection<Team> _teams
public ICollection<Team> Teams 
{
    get { return _teams ?? (teams = new List<Team>()); }
    protected set { _teams = value; }
}

Извините за форматирование. На моем телефоне. Удачи.

person trevorc    schedule 03.09.2014
comment
Спасибо за предложение, @nameEqualsPNamePrube, но проблема не в этом (см. Мой следующий комментарий ниже). Предлагаемый вами свободный API - это просто альтернативный способ указать то, что я уже указал, используя аннотации ForeignKey в моем примере кода (см. Выше); свободный API и аннотации создают идентичный метод Up () класса, производного от DbMigration, который создается в ответ на команду add-migration в консоли диспетчера пакетов. - person Harry Smith; 04.09.2014
comment
Проблема, которую я пытаюсь решить, заключается в том, что у Team и Person внешние ключи указывают друг на друга, и Entity Framework не может определить, какая из Team и Person является основной, а какая зависимой, т.е. Person и Person содержат команду. Вот где я думал, что WithRequiredPrincipal вступает в игру, но, как упоминалось в моем исходном вопросе, это приводит к тому, что Entity Framework жалуется на конфликтующие внешние ключи. Без указания отношения принципал / зависимый я получаю ошибки времени выполнения (как описано выше), когда пытаюсь сохранить объекты. - person Harry Smith; 04.09.2014
comment
Я почти уверен, что это невозможно. Кроме того, команда содержит «людей» и «человек», «принадлежащий» команде. Я думаю, что сначала я бы посмотрел на обновление вашей модели данных. EF не может сказать, кто является главным и зависимым, потому что ваша модель по своей сути не говорит об этом. - person trevorc; 04.09.2014
comment
Нет, то, что я пытаюсь сделать, действительно. Если вам не нравится гипотетический пример команды, который я использовал для демонстрации проблемы, изобразите Брак. Брак - это не просто собрание людей. Брак имеет особые сущности Муж и Жена, оба типа Лицо. Должна быть возможность перемещаться в обоих направлениях, как в Брак. Муж и Муж. Брак. Оказывается, у EF есть признанный недостаток. См. entityframework.codeplex.com/workitem/142. Команда EF Triage отметила, что эта проблема будет решена в следующем выпуске. Тем временем мне нужно найти обходной путь - person Harry Smith; 05.09.2014

Хорошо, теперь я могу ответить на свой вопрос, если кто-то еще столкнется с той же проблемой.

Во-первых, как я упоминал в своем комментарии выше, проблема, которую я описал в своем вопросе, оказывается признанным недостатком Entity Framework, который команда EF Triage отметила для устранения в будущей версии EF.

Между тем, обходной путь, который был предложен рядом участников в ответ на вопросы о том, как реализовать циклические зависимости в EF (что и пытается сделать мой пример выше), состоит в том, чтобы делать это поэтапно следующим образом:

  1. Создайте основную сущность
  2. Вызов SaveChanges () в DbContext
  3. Создайте зависимую сущность и установите внешний ключ перед повторным вызовом SaveChanges () (в некоторой последующей точке)

Итак, используя пример команды в моем исходном вопросе выше, вместо создания зависимых сущностей Manager и Coach в конструкторе Team первым изменением было сделать внешние ключи Team для Coach и Manager необязательными (вместо обязательных), чтобы можно создать экземпляр команды без тренера и менеджера, а затем вызвать SaveChanges ():

Team team = Teams.Add(new Team());
SaveChanges();

После этого я создаю зависимые сущности Manager и Coach и устанавливаю для их внешних ключей идентификатор экземпляра Team:

team.Manager = new Person();
team.Manager.TeamID = team.TeamID;

team.Coach = new Person();
team.Coach.TeamID = team.TeamID;

В любое время после этого можно вызвать SaveChanges (), не вызывая ошибки времени выполнения, как раньше.

Когда эта проблема будет решена в будущем выпуске EF, должна появиться возможность создавать основные и зависимые сущности без необходимости вызывать SaveChanges () между ними.

person Harry Smith    schedule 05.09.2014