Сложные отношения между сущностями с Entity Framework - PullRequest
3 голосов
/ 26 июня 2019

Возможно, это дубликат, но я не смог найти ни одной темы, подобной этой.

Я использую Entity Framework и в моей базе данных есть две таблицы:

public class A
{
    public virtual B B1 { get; set; }
    public virtual B B2 { get; set; }
}

public class B
{
    public virtual A A1 { get; set; }
}

И нет никакой связи между B1 и A1 или B2 и A1. Это как 3 односторонних отношения. Как вы можете сделать это в Entity Framework?

Я получаю эту ошибку:

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

Кто-нибудь знает, как с этим справиться?

Заранее спасибо

Ответы [ 2 ]

1 голос
/ 29 июня 2019

Поскольку вы не указываете, в какой версии EF вы используете, давайте посмотрим на две текущие версии, EF 6.2.0 и EF-core 2.2.4.

С EF6 это легко.Отображение ...

modelBuilder.Entity<A>().HasRequired(a => a.B1).WithMany();
modelBuilder.Entity<A>().HasRequired(a => a.B2).WithMany().WillCascadeOnDelete(false);
modelBuilder.Entity<B>().HasRequired(b => b.A1).WithMany().WillCascadeOnDelete(false);

... создает следующую модель базы данных (без учета индексов):

CREATE TABLE [dbo].[A] (
    [ID] [int] NOT NULL IDENTITY,
    [B1_ID] [int] NOT NULL,
    [B2_ID] [int] NOT NULL,
    CONSTRAINT [PK_dbo.A] PRIMARY KEY ([ID])
)
CREATE TABLE [dbo].[B] (
    [ID] [int] NOT NULL IDENTITY,
    [A1_ID] [int] NOT NULL,
    CONSTRAINT [PK_dbo.B] PRIMARY KEY ([ID])
)

...., в которой поля с _ являются чужимиключи, один из которых может каскадно удалять.

С ef-core это не так просто, даже глючно, на первый взгляд.Первый импульс - это эквивалент EF6:

modelBuilder.Entity<A>().HasOne(a => a.B1).WithMany();
modelBuilder.Entity<A>().HasOne(a => a.B2).WithMany();
modelBuilder.Entity<B>().HasOne(b => b.A1).WithMany();

Но сгенерированная модель не соответствует ожиданиям:

  CREATE TABLE [B] (
      [ID] int NOT NULL IDENTITY,
      [A1ID] int NULL,
      CONSTRAINT [PK_B] PRIMARY KEY ([ID])
  );
  CREATE TABLE [A] (
      [ID] int NOT NULL,
      [B1ID] int NULL,
      CONSTRAINT [PK_A] PRIMARY KEY ([ID]),
      CONSTRAINT [FK_A_B_B1ID] FOREIGN KEY ([B1ID]) REFERENCES [B] ([ID]) ON DELETE NO ACTION,
      CONSTRAINT [FK_A_B_ID] FOREIGN KEY ([ID]) REFERENCES [B] ([ID]) ON DELETE CASCADE
  );
  ALTER TABLE [B] ADD CONSTRAINT [FK_B_A_A1ID] FOREIGN KEY ([A1ID]) REFERENCES [A] ([ID]) ON DELETE NO ACTION;

Одна из ассоциаций A-B интерпретируется как 1:1.На мой взгляд, это ошибка.Инструкция WithMany не должна оставлять места для интерпретации.Два по-видимому идентичных отображения производят совершенно разные отношения базы данныхЭто не может быть правдой.

Тем не менее, легко (но не обязательно), чтобы получить EF на правильном пути, назвав столбцы FK:

modelBuilder.Entity<A>().HasOne(a => a.B1).WithMany().HasForeignKey("B1_ID")
    .IsRequired();
modelBuilder.Entity<A>().HasOne(a => a.B2).WithMany().HasForeignKey("B2_ID")
    .IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<B>().HasOne(b => b.A1).WithMany().HasForeignKey("A1_ID")
    .IsRequired().OnDelete(DeleteBehavior.Restrict);

Производство (игнорирование индексов):

  CREATE TABLE [B] (
      [ID] int NOT NULL IDENTITY,
      [A1_ID] int NOT NULL,
      CONSTRAINT [PK_B] PRIMARY KEY ([ID])
  );
  CREATE TABLE [A] (
      [ID] int NOT NULL IDENTITY,
      [B1_ID] int NOT NULL,
      [B2_ID] int NOT NULL,
      CONSTRAINT [PK_A] PRIMARY KEY ([ID]),
      CONSTRAINT [FK_A_B_B1_ID] FOREIGN KEY ([B1_ID]) REFERENCES [B] ([ID]) ON DELETE CASCADE,
      CONSTRAINT [FK_A_B_B2_ID] FOREIGN KEY ([B2_ID]) REFERENCES [B] ([ID]) ON DELETE NO ACTION
  );
  ALTER TABLE [B] ADD CONSTRAINT [FK_B_A_A1_ID] FOREIGN KEY ([A1_ID]) REFERENCES [A] ([ID]) ON DELETE NO ACTION;

Обратите внимание, что поля внешнего ключа должны быть установлены обязательными явным образом (если они есть).Ну, это просто детали реализации.

1 голос
/ 27 июня 2019

Если таблица A1 содержит B1_Id и B2_Id, указывающие на одну и ту же таблицу B, но вы ожидаете, что запись B будет когда-либо ассоциирована только с A один раз, то, насколько я знаю о том, как происходит отображение, это не возможный. Ничто не помешает вам связать одну и ту же запись B с B1 или B2 на разных записях A, так как же ссылка B на A когда-либо будет разрешена юридически? Объекты отражают состояние данных, поэтому, если он является законным / недопустимым в схеме данных, то же самое относится и к объекту. Наличие идентификаторов B на A формирует множество к 1 или может составлять 1 к одному, но для совместного использования 2x FK для B на A вам понадобится DB для поддержки альтернативных FK для B, чего нет.

У вас может быть A, содержащая идентификаторы для 2 записей в B, но B не может сопоставить одну ссылку обратно с A, она должна подделать ее.

public class A
{
   public int AId { get; set; }

   public virtual B B1 { get; set; }
   public virtual B B2 { get; set; }
}

public class B
{
   public int BId { get; set; }

   public virtual ICollection<A> RawA1 { get; private set; } = new List<A>();
   public virtual ICollection<A> RawA2 { get; private set; } = new List<A>();

   [NotMapped]
   public A A
   {
      get { return RawA1.SingleOrDefault() ?? RawA2.SingleOrDefault(); }
   }
}

Предостережение: нельзя использовать B.A в любом выражении Linq, идущем к EF, потому что в отношении EF оно не знает об этом свойстве.

Альтернативой является использование 1-ко-многим, когда AId живет на B, а затем работа с ограничением допустимых операций в отношении сбора на стороне A. Проблема будет заключаться в том, что порядок B1 и B2 не будет надежным, если он не будет явно определен свойствами в записи B. A может предоставлять несопоставленные свойства B1 и B2 из коллекции, но для этого нужно будет определить, какой из двух элементов следует рассмотреть для каждого, или просто открыть коллекцию. В конечном счете, именно ваша бизнес-логика должна контролировать тот факт, что A всегда должен иметь только 2 ссылки B, поскольку база данных не может это обеспечить, равно как и отношение 2 к 1, когда B может вернуться к A.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...