Один к одному и Один ко многим для одного и того же типа объекта

Я пытаюсь разработать следующие отношения сущностей

  • Программа
  • сборка

Программа может иметь несколько сборок. Одна из сборок будет основной сборкой. Каждая сборка принадлежит только одной программе.

Классы моделируются следующим образом:

public class Program
{
    public int Id {get;set;}
    public virtual ProgramAssembly MainAssembly {get;set;}
    public virtual ICollection<ProgramAssembly> Assemblies {get;set;}
}

public class ProgramAssembly
{
   public int Id {get;set;}
   public virtual Program Program {get;set;}
   public int ProgramId {get;set;}
}

Я ничего не указываю с помощью FluentApi (однако я ничего против этого не имею).
В приведенном выше коде я получаю эту ошибку:

Произошла ошибка при сохранении сущностей, которые не предоставляют свойства внешнего ключа для своих связей. Свойство EntityEntries вернет значение null, поскольку один объект не может быть идентифицирован как источник исключения. Обработку исключений при сохранении можно упростить, предоставив свойства внешнего ключа в ваших типах сущностей. Подробнее см. InnerException.

InnerException: невозможно определить допустимый порядок зависимых операций. Зависимости могут существовать из-за ограничений внешнего ключа, требований модели или значений, сгенерированных хранилищем.

Я попытался изменить ProgramAssembly на следующее

public class ProgramAssembly
{
    [ForeignKkey("Program")]
    public int Id {get;set;}
    public virtual Program Program {get;set;}
}

Однако затем я получаю эту ошибку:

ProgramAssembly_Program_Source: : Множественность недопустима в роли «ProgramAssembly_Program_Source» в отношении «ProgramAssembly_Program». Поскольку зависимая роль относится к ключевым свойствам, верхняя граница кратности зависимой роли должна быть равна «1».

Я также попробовал следующий свободный подход API

modelBuilder.Entity<ProgramAssembly>()
   .HasRequired(a => a.Program)
   .WithMany(p => p.Assemblies);

public class ProgramAssembly
    {        
        public int Id { get; set; }
        public virtual Program Program { get; set; }
        [ForeignKey("Program")] //same error with or without this attribute
        public int ProgramId { get; set; }
   }

Тогда ошибка:

Произошла ошибка при сохранении сущностей, которые не предоставляют свойства внешнего ключа для своих связей. Свойство EntityEntries вернет значение null, поскольку один объект не может быть идентифицирован как источник исключения. Обработку исключений при сохранении можно упростить, предоставив свойства внешнего ключа в ваших типах сущностей. Подробности смотрите в InnerException». UpdateException: невозможно определить допустимый порядок зависимых операций. Зависимости могут существовать из-за ограничений внешнего ключа, требований модели или значений, сгенерированных хранилищем.

Как разрешить эту связь без изменения подхода к добавлению атрибута IsPrimary в сборку?

Я видел несколько похожих вопросов на SO, но они были либо о ядре EF, например этот или предложенное существенное изменение логики, например этот или даже этот, который даже не компилируется.


person Bartosz    schedule 29.05.2018    source источник
comment
Насколько сильно вы можете изменить свою модель? Я думаю, что добавление свойства IsPrimary к Assembly будет выглядеть лучше, чем наличие двух отношений   -  person Camilo Terevinto    schedule 30.05.2018
comment
@CamiloTerevinto - спасибо. Я могу делать с моделью все, что захочу, однако я хочу избежать такого подхода. Причина в том, что я могу в конечном итоге установить любое количество сборок в качестве основных. Кроме того, как бы вы увидели доступ к первичной сборке программы?   -  person Bartosz    schedule 30.05.2018
comment
Вы не сможете делать то, что хотите, указав отношения таким образом. Чтобы получить что-то подобное, вам нужно, как сказал Камило, добавить свойство IsMain к сущности ProgramAssembly, и если вы хотите получить его внутри самой сущности Program, вам нужно будет изменить свойство MainAssembly на public ProgramAssembly MainAssembly { get { return Assemblies.where(asm => asm.IsMain).FirstOrDefault(); }}   -  person iNovelletto    schedule 30.05.2018
comment
Это будет просто program.Assemblies.Single(x => x.IsMain) (или вы даже можете использовать его как навигационное свойство), но вы правы, что вам нужно будет проверить это при сохранении. Ответ для EF Core должен работать почти так же с использованием Fluent API. Я почти уверен, что вы не можете сделать это через DataAnottations, хотя   -  person Camilo Terevinto    schedule 30.05.2018
comment
@CamiloTerevinto - спасибо. Я попробовал подход EF Core, только изменив «HasOne» на «HasRequired», но это не сработало.   -  person Bartosz    schedule 30.05.2018
comment
Можете ли вы опубликовать то, что вы пробовали (и ошибку, если она изменилась)? Ответ должен быть близок к этому   -  person Camilo Terevinto    schedule 30.05.2018
comment
@CamiloTerevinto - да, обновил вопрос   -  person Bartosz    schedule 30.05.2018
comment
Дело в том, что вам нужно будет сказать, что внешний ключ ProgramAssembly.Id для основной сборки и ProgramAssembly.ProgramId для всех остальных сборок. Ты согласен с этим? (Конечно, это звучит ужасно для меня)   -  person Camilo Terevinto    schedule 30.05.2018
comment
@CamiloTerevinto - не уверен, что вы имеете в виду, но я согласен, что это звучит странно.   -  person Bartosz    schedule 30.05.2018
comment
Я имел в виду, как значения хранятся в базе данных. Вы не можете использовать один столбец одновременно для один к одному и один ко многим. Вот почему я предложил иметь только один внешний ключ и столбец IsMain.   -  person Camilo Terevinto    schedule 30.05.2018
comment
Любое решение исходного вопроса?   -  person Bjorn    schedule 23.09.2020


Ответы (2)


Вам необходимо сообщить EF, как работают ваши первичные и внешние ключи. Вам нужно добавить атрибуты для определения ваших первичных ключей [Key] и определить внешние ключи и добавить атрибуты к вашим свойствам навигации, чтобы определить поле, которое является внешним ключом.

public class Program
{
    [Key]
    public int Id {get;set;}
    public int MainAssemblyId {get;set;}
    [ForeignKey("MainAssemblyId ")]
    public virtual ProgramAssembly MainAssembly {get;set;}
    public virtual ICollection<ProgramAssembly> Assemblies {get;set;}
}

public class ProgramAssembly
{
   [Key]
   public int Id {get;set;}
   [ForeignKey("ProgramId")]
   public virtual Program Program {get;set;}
   public int ProgramId {get;set;}
}
person Philip Smith    schedule 29.05.2018
comment
Хм, пока этот код дает мне это исключение: Introducing FOREIGN KEY constraint 'FK_dbo.ProgramAssemblies_dbo.Programs_ProgramId' on table 'ProgramAssemblies' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. - person Bartosz; 30.05.2018

Код ниже удовлетворит ваше требование.

public class Program
{
    [Key]
    public int Id {get;set;}
    public virtual ICollection<ProgramAssembly> Assemblies {get;set;}
}

public class ProgramAssembly
{
   public int Id {get;set;}

   [ForeignKey("ProgramId")]
   public virtual Program Program {get;set;}
   public int ProgramId {get;set;}
   public bool IsMainProgramAssembly
}

Вы можете использовать [Required] для ProgramId, если вам это нужно, или вы также можете сделать ProgramId обнуляемым.

person vivek nuna    schedule 30.05.2018