То, что вы хотите здесь, называется полиморфными ассоциациями : несколько сущностей, имеющих дочерние сущности одного типа.Они обычно используются для комментариев, замечаний, файлов и т. Д. И обычно применяются к ассоциациям 1: n.В вашем случае возникают полиморфные 1: 1 ассоциации.В основном эти ассоциации выглядят так (используя несколько более общие имена):

Как их реализовать?
Entity Framework 6
В EF6 это проблема.EF6 реализует связи 1: 1 как общие первичные ключи: первичный ключ дочернего элемента также является внешним ключом первичного ключа его родителя.Это означало бы, что на Image.ID
должно быть два ФК, один указывает на Person.ID
, а другой - на Product.ID
.Технически это не проблема, семантически это так.Теперь двум родительским объектам принадлежит одно и то же изображение или 1020 *, или, по-разному, изображение всегда должно принадлежать двум разным родителям.В реальной жизни это чепуха.
Решением может быть обратная ссылка:

Но теперь есть другая проблема.Сущность, которая относится к , называется принципалом , другая сущность зависит .На второй диаграмме Image
является принципалом, поэтому для создания Person
сначала необходимо вставить его изображение, а затем человек скопирует его первичный ключ.Это нелогично и, скорее всего, нецелесообразно. невозможно , если изображения являются необязательными.
Тем не менее, поскольку в вашем случае вы хотите, чтобы изображения требовались, позвольте мне показать, как эта связь отображается в EF6.
Давайте рассмотримэта простая модель:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Image Image { get; set; }
}
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Image Image { get; set; }
}
public class Image
{
public int ImgID { get; set; } // Named for distinction
public string Url { get; set; }
}
Требуемое отображение:
modelBuilder.Entity<Image>().HasKey(pd => pd.ImgID);
modelBuilder.Entity<Person>().HasRequired(p => p.Image).WithRequiredDependent();
modelBuilder.Entity<Product>().HasRequired(p => p.Image).WithRequiredDependent();
Как видите, Image
имеет двух обязательных зависимых.Возможно, это лучше, чем двое обязательных родителей, но все же странно.К счастью, в действительности это не проблема, потому что EF не проверяет эти ассоциации.Вы даже можете вставить изображение без «обязательной» зависимости.Я не знаю, почему EF не подтверждает это, но здесь это пригодится.Часть WithRequiredDependent
могла бы также быть WithOptional
, она не имеет значения для сгенерированной модели данных, но по крайней мере это отображение передает ваши намерения.
Альтернативный подход может быть наследование .Если Person
и Product
наследуются от одного базового класса, этот базовый класс может быть основным в ассоциации 1: 1 с Image
.Тем не менее, я думаю, что это злоупотребление шаблоном дизайна.Люди и продукты не имеют ничего общего.С точки зрения дизайна, нет причин для того, чтобы они были частью одного дерева наследования.
Поэтому в EF6 я думаю, что наиболее выполнимым решением является использование третьей альтернативы: отдельных таблиц изображений для каждой сущности .
Entity Framework Core
В EF-core 1: 1 ассоциации могут быть реализованы способом EF6, но также возможно использовать отдельное поле внешнего ключа в зависимом объекте.При этом полиморфный случай выглядит следующим образом:

Класс Image
отличается:
public class Image
{
public Image()
{ }
public int ImgID { get; set; }
public int? PersonID { get; set; }
public int? ProductID { get; set; }
public string Url { get; set; }
}
ИСопоставление:
modelBuilder.Entity<Person>().Property(p => p.ID).UseSqlServerIdentityColumn();
modelBuilder.Entity<Person>()
.HasOne(p => p.Image)
.WithOne()
.HasForeignKey<Image>(p => p.PersonID);
modelBuilder.Entity<Product>().Property(p => p.ID).UseSqlServerIdentityColumn();
modelBuilder.Entity<Product>()
.HasOne(p => p.Image)
.WithOne()
.HasForeignKey<Image>(p => p.ProductID);
modelBuilder.Entity<Image>().HasKey(p => p.ImgID);
Смотрите обнуляемые внешние ключи.Они необходимы, потому что изображение принадлежит либо Person
, либо Product
.Это один недостаток этого дизайна.Другое - то, что вам нужно новое поле внешнего ключа для каждого нового объекта, которому вы хотите владеть изображениями.Обычно вы хотите избежать таких редких столбцов.Есть также преимущество по сравнению с реализацией EF6: эта модель допускает двунаправленную навигацию.Image
может быть расширен с помощью Person
и Product
навигационных свойств.
EF довольно неплохо работает, переводя это в дизайн базы данных.Каждый внешний ключ имеет отфильтрованный уникальный индекс , например, для Person
:
CREATE UNIQUE NONCLUSTERED INDEX [IX_Image_PersonID] ON [dbo].[Image]
(
[PersonID] ASC
)
WHERE ([PersonID] IS NOT NULL)
Это превращает ассоциацию в подлинную ассоциацию 1: 1 на стороне базы данных.Без уникального индекса это была бы связь 1: n с точки зрения базы данных.