Принадлежащие типы сущностей

EF Core позволяет моделировать типы сущностей, которые могут отображаться только в свойствах навигации других типов сущностей. Они называются собственными типами сущностей. Сущность, содержащая собственный тип сущности, является его владельцем.

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

Настройка типов как принадлежащих

В большинстве поставщиков типы сущностей никогда не настраиваются как принадлежащие соглашению. Этот метод необходимо явно использовать OwnsOne или OnModelCreating замечать тип, OwnedAttribute чтобы настроить тип как принадлежащий. Поставщик Azure Cosmos DB является исключением из этого. Так как Azure Cosmos DB — это база данных документов, поставщик настраивает все связанные типы сущностей как принадлежащие по умолчанию.

В этом примере StreetAddress тип без свойства удостоверения. Он используется как свойство сущности Order для указания адреса доставки конкретного заказа.

Мы можем использовать OwnedAttribute его для обработки как принадлежащей сущности при ссылке из другого типа сущности:

[Owned]
public class StreetAddress
{
    public string Street { get; set; }
    public string City { get; set; }
}
public class Order
{
    public int Id { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Также можно использовать OwnsOne метод, OnModelCreating чтобы указать, что ShippingAddress свойство является владельцем сущности Order типа сущности и настроить дополнительные аспекты при необходимости.

modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);

ShippingAddress Если свойство закрыто в типеOrder, можно использовать строковую OwnsOne версию метода:

modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");

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

Screenshot of the database model for entity containing owned reference

Дополнительные сведения см. в полном примере проекта .

Совет

Тип собственной сущности можно пометить как обязательный, дополнительные сведения см. в разделе "Обязательные зависимые от одного к одному".

Неявные ключи

Собственные типы, настроенные с помощью навигации по ссылке, всегда имеют отношение "один к одному" с OwnsOne владельцем, поэтому им не нужны собственные значения ключей, так как значения внешнего ключа уникальны. В предыдущем примере StreetAddress тип не должен определять свойство ключа.

Чтобы понять, как EF Core отслеживает эти объекты, полезно знать, что первичный ключ создается как теневое свойство для собственного типа. Значение ключа экземпляра собственного типа будет совпадать со значением ключа экземпляра владельца.

Коллекции собственных типов

Настройка коллекции собственных типов OwnsMany в OnModelCreating.

Для собственных типов требуется первичный ключ. Если в типе .NET отсутствуют хорошие свойства кандидатов, EF Core может попытаться создать его. Однако при определении собственных типов с помощью коллекции недостаточно просто создать теневое свойство, чтобы действовать как внешний ключ в владельце, так и первичный ключ собственного экземпляра, так как мы делаем для OwnsOne: для каждого владельца может быть несколько экземпляров типа, поэтому ключ владельца не достаточно, чтобы предоставить уникальное удостоверение для каждого собственного экземпляра.

Ниже приведены два самых простых решения.

  • Определение суррогатного первичного ключа для нового свойства независимо от внешнего ключа, указывающего на владельца. Содержащиеся значения должны быть уникальными для всех владельцев (например, если родительский {1} имеет дочерний, то родительский {2} не может иметь дочерний{1}{1}), поэтому значение не имеет никакого неотъемлемого значения. Так как внешний ключ не является частью первичного ключа, его значения можно изменить, поэтому можно переместить дочерний элемент из одного родителя в другой, однако обычно это относится к агрегатной семантике.
  • Использование внешнего ключа и дополнительного свойства в качестве составного ключа. Дополнительное значение свойства теперь должно быть уникальным только для заданного родительского элемента (поэтому, если родительский {1} имеет дочерний, то родительский {2} объект по-прежнему может иметь дочерний {1,1}{2,1}). Благодаря тому, что внешний ключ является частью первичного ключа связь между владельцем и владельцем сущности становится неизменяемой и отражает статистическую семантику. Это то, что EF Core делает по умолчанию.

В этом примере мы будем использовать Distributor класс.

public class Distributor
{
    public int Id { get; set; }
    public ICollection<StreetAddress> ShippingCenters { get; set; }
}

По умолчанию первичный ключ, используемый для собственного типа, на который ShippingCenters ссылается свойство навигации, будет ("DistributorId", "Id") где "DistributorId" находится FK и "Id" является уникальным int значением.

Настройка другого вызова HasKeyпервичного ключа.

modelBuilder.Entity<Distributor>().OwnsMany(
    p => p.ShippingCenters, a =>
    {
        a.WithOwner().HasForeignKey("OwnerId");
        a.Property<int>("Id");
        a.HasKey("Id");
    });

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

Sceenshot of the database model for entity containing owned collection

Сопоставление принадлежащих типов с разделением таблицы

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

По умолчанию EF Core будет называть столбцы базы данных для свойств типа принадлежащих сущностей после шаблона Navigation_OwnedEntityProperty. StreetAddress Поэтому свойства будут отображаться в таблице "Заказы" с именами "ShippingAddress_Street" и "ShippingAddress_City".

Этот метод можно использовать HasColumnName для переименования этих столбцов.

modelBuilder.Entity<Order>().OwnsOne(
    o => o.ShippingAddress,
    sa =>
    {
        sa.Property(p => p.Street).HasColumnName("ShipsToStreet");
        sa.Property(p => p.City).HasColumnName("ShipsToCity");
    });

Примечание.

Большинство методов конфигурации обычного типа сущности, таких как Ignore , можно вызывать таким же образом.

Совместное использование одного типа .NET между несколькими собственными типами

Тип собственной сущности может иметь тот же тип .NET, что и другой тип сущности, поэтому тип .NET может быть недостаточно для идентификации собственного типа.

В таких случаях свойство, указывающее владельца на собственную сущность, становится определяющей навигацией собственного типа сущности. С точки зрения EF Core определяющая навигация является частью удостоверения типа вместе с типом .NET.

Например, в следующем классе ShippingAddress и BillingAddress оба типа StreetAddress.NET.

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Чтобы понять, как EF Core будет различать отслеживаемые экземпляры этих объектов, может быть полезно подумать, что определяющая навигация стала частью ключа экземпляра вместе со значением ключа владельца и типа .NET собственного типа.

Вложенные типы

В этом примере OrderDetails принадлежит BillingAddress и ShippingAddressимеет оба StreetAddress типа. А OrderDetails, в свою очередь, принадлежит типу DetailedOrder.

public class DetailedOrder
{
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }
    public OrderStatus Status { get; set; }
}
public enum OrderStatus
{
    Pending,
    Shipped
}

Каждая навигация по собственному типу определяет отдельный тип сущности с полностью независимой конфигурацией.

В дополнение к вложенным типам, принадлежащий тип может ссылаться на обычную сущность, которая может быть владельцем или другой сущностью, если принадлежащая сущность находится на зависимой стороне. Эта возможность задает типы сущностей, принадлежащие отдельно от сложных типов в EF6.

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Настройка собственных типов

Чтобы настроить эту модель, можно создать OwnsOne цепочку метода в простом вызове:

modelBuilder.Entity<DetailedOrder>().OwnsOne(
    p => p.OrderDetails, od =>
    {
        od.WithOwner(d => d.Order);
        od.Navigation(d => d.Order).UsePropertyAccessMode(PropertyAccessMode.Property);
        od.OwnsOne(c => c.BillingAddress);
        od.OwnsOne(c => c.ShippingAddress);
    });

Обратите внимание, что WithOwner вызов, используемый для определения свойства навигации, указывающего на владельца. Чтобы определить навигацию по типу сущности владельца, который не является частью отношения WithOwner() владения, следует вызывать без каких-либо аргументов.

Этот результат также можно достичь с помощью OwnedAttribute обоих OrderDetails и StreetAddress.

Кроме того, обратите внимание Navigation на звонок. Свойства навигации в собственные типы можно дополнительно настроить как для свойств навигации, отличных от собственных.

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

Screenshot of the database model for entity containing nested owned references

Хранение принадлежащих типов в отдельных таблицах

Кроме того, в отличие от сложных типов EF6, типы, принадлежащие типы, могут храниться в отдельной таблице от владельца. Чтобы переопределить соглашение, которое сопоставляет принадлежащий тип с той же таблицей, что и владелец, можно просто вызвать ToTable и указать другое имя таблицы. Следующий пример сопоставляется OrderDetails с двумя адресами отдельной таблицы:DetailedOrder

modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od => { od.ToTable("OrderDetails"); });

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

Запросы принадлежащих типов

При запросе владельца принадлежащие типы включаются по умолчанию. Использовать метод не обязательно Include , даже если принадлежащие типы хранятся в отдельной таблице. На основе модели, описанной ранее, следующий запрос будет получен OrderOrderDetails и два принадлежащих StreetAddresses из базы данных:

var order = context.DetailedOrders.First(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.OrderDetails.ShippingAddress.City}");

Ограничения

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

Ограничения путем разработки

  • Невозможно создать DbSet<T> для собственного типа.
  • Вы не можете вызывать Entity<T>() собственный тип в ModelBuilder.
  • Экземпляры собственных типов сущностей не могут совместно использоваться несколькими владельцами (это хорошо известный сценарий для объектов значений, которые не могут быть реализованы с помощью собственных типов сущностей).

Текущие недостатки

  • Типы принадлежащих сущностей не могут иметь иерархии наследования

Недостатки в предыдущих версиях

  • В EF Core 2.x ссылочные навигации к типам принадлежащих сущностей не могут иметь значение NULL, если они явно не сопоставлены с отдельной таблицей от владельца.
  • В EF Core 3.x столбцы для типов принадлежащих сущностей, сопоставленных с той же таблицей, что и владелец, всегда помечены как null.