Язык программирования C#9 и платформа .NET5
@p9 nvarchar(50),@p10 int,@p11 nvarchar(50)',@p0=N'Yellow',@p1=1,@p2=N'Herbie',@p3=N'White',@p4=2,@p5=N'Mach 5',@p6=N'Pink',@p7=3,@p8=N'Avon',@p9=N'Blue',@p10=4,@p11=N'Blueberry'Принадлежащие сущностные типы
Возможность применения класса C# в качестве свойства сущности с целью определения коллекции свойств для другой сущности впервые появилась в версии EF Core 2.0 и в последующих версиях постоянно обновлялась. Когда типы, помеченные атрибутом
или сконфигурированные посредством Fluent API, добавлены в виде свойств сущности, инфраструктура EF Core добавит все свойства из сущностного класса[Owned]к владеющему классу. В итоге увеличивается вероятность многократного использования кода С#.[Owned]"За кулисами" EF Core считает результат отношением "один к одному". Принадлежащий класс является зависимой сущностью, а владеющий класс — главной сущностью. Хотя принадлежащий класс рассматривается как сущность, он не может существовать без владеющего класса. Имена столбцов из принадлежащего класса по умолчанию получают формат
(например,ИмяНавигационногоСвойства_ИмяСвойстваПринадлежащейСущности). Стандартные имена можно изменять с применением Fluent API.PersonalNavigation_FirstNameВзгляните на приведенный далее класс Person (обратите внимание на атрибут
):[Owned]<b>[Owned]</b>public class Person{[Required, StringLength(50)]public string FirstName { get; set; } = "New";[Required, StringLength(50)]public string LastName { get; set; } = "Customer";}Он используется классом
:Customer[Table("Customers", Schema = "Dbo")]public partial class Customer : BaseEntity{public Person PersonalInformation { get; set; } = new Person();[JsonIgnore][InverseProperty(nameof(CreditRisk.CustomerNavigation))]public IEnumerable<CreditRisk> CreditRisks { get; set; } =new List<CreditRisk>();[JsonIgnore][InverseProperty(nameof(Order.CustomerNavigation))]public IEnumerable<Order> Orders { get; set; } = new List<Order>();}По умолчанию два свойства
отображаются на столбцы с именамиPersonиPersonalInformation_FirstName. Чтобы изменить это, добавьте в методPersonalInformation_LastNameследующий код Fluent API:OnConfiguring()modelBuilder.Entity<Customer>(entity =>{entity.OwnsOne(o => o.PersonalInformation,pd =>{pd.Property<string>(nameof(Person.FirstName)).HasColumnName(nameof(Person.FirstName)).HasColumnType("nvarchar(50)");pd.Property<string>(nameof(Person.LastName)).HasColumnName(nameof(Person.LastName)).HasColumnType("nvarchar(50)");});});Вот как будет создаваться результирующая таблица (обратите внимание, что допустимость значений
в столбцахnullиFirstNameне соответствует аннотациям данных в принадлежащей сущностиLastName):PersonCREATE TABLE [dbo].[Customers]([Id] [int] IDENTITY(1,1) NOT NULL,[FirstName] [nvarchar](50) NULL,[LastName] [nvarchar](50) NULL,[TimeStamp] [timestamp] NULL,[FullName] AS (([LastName]+', ')+[FirstName]),CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED([Id] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS= ON, ALLOW_PAGE_LOCKS = ON,OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]) ON [PRIMARY]GOПроблема с принадлежащими сущностями, которая может быть не видна вам, но приводить к сложной ситуации, в версии EF Core 5 устранена. Легко заметить, что класс
содержит аннотации данных Required для обоих своих свойств, но оба столбца SQL Server определены как допускающиеPerson.Так происходит из-за проблемы с тем, каким образом система миграции транслирует принадлежащие сущности, когда они используются с необязательным отношением. Для исправления проблемы потребуется сделать отношение обязательным.NULLРешить задачу можно двумя способами. Первый — включить допустимость
на уровне проекта или в классах С#. Тогда навигационное свойствоnullстановится не допускающим значениеPersonalInformation, что исполняющая среда EF Core учитывает и соответствующим образом конфигурирует столбцы в принадлежащей сущности. Второй способ предусматривает добавление кода Fluent API для того, чтобы сделать навигационное свойство обязательным:null