Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

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

0
, а для булевских —
false
. Если вы установите значение числового свойства в
0
или булевского свойства в
false
и затем вставите такую сущность, тогда инфраструктура EF Core будет трактовать это свойство как не имеющее установленного значения. При отображении свойства на столбец со стандартным значением используется стандартное значение в определении столбца.

Например, добавьте в класс

Car
свойство типа
bool
по имени
IsDrivable
. Установите в
true
стандартное значение для отображения свойства на столбец:

// Car.cs

public class Car : BaseEntity

{

...

public bool IsDrivable { get; set; }

}

// ApplicationDbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

modelBuilder.Entity<Car>(entity =>

{

...

entity.Property(e => e.IsDrivable).HasDefaultValue(true);

});

В случае если вы сохраните новую запись с

IsDrivable = false
, то значение
false
игнорируется (т.к. оно является стандартным значением для булевского типа) и будет применяться стандартное значение. Таким образом, значение для
IsDrivable
всегда будет равно
true
! Одно из решений предусматривает превращение вашего открытого свойства (и, следовательно, столбца) в допускающее
null
, но это может не соответствовать бизнес-потребностям.

Другое решение предлагается инфраструктурой EF Core, в частности, ее работой с поддерживающими полями. Вспомните, что если поддерживающее поле существует (и идентифицируется как таковое для свойства через соглашение, аннотацию данных или Fluent API), тогда для действий по чтению и записи EF Core будет использовать поддерживающее поле, а не открытое свойство.

Если вы модифицируете

IsDrivable
с целью применения поддерживающего поля, допускающего
null
(но оставите свойство не допускающим
null
), то EF Core будет выполнять чтение и запись, используя поддерживающее поле, а не свойство. Стандартным значением для булевского типа, допускающего
null
, является
null
— не
false
. Описанное изменение обеспечит ожидаемое поведение свойства:

public class Car

{

...

private bool? _isDrivable;

public bool IsDrivable

{

get => _isDrivable ?? true;

set => _isDrivable = value;

}

Для информирования EF Core о поддерживающем поле используется Fluent API:

modelBuilder.Entity<Car>(entity =>

{

entity.Property(p => p.IsDrivable)

.HasField("_isDrivable")

.HasDefaultValue(true);

});

На

заметку!
В приведенном примере вызов метода
HasField
не обязателен, потому что имя поддерживающего поля следует соглашениям об именовании. Он включен в целях демонстрации применения Fluent API для указания поддерживающего поля.

Исполняющая среда EF Core транслирует поле в показанное ниже определение SQL:

CREATE TABLE [dbo].[Inventory](

...

[IsDrivable] [BIT] NOT NULL,

...

GO

ALTER TABLE [dbo].[Inventory] ADD DEFAULT (CONVERT([BIT],(1)))

FOR [IsDrivable]

GO

Вычисляемые столбцы

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

Inventory
вычисляемый столбец, который объединяет значения
PetName
и
Color
для создания
DisplayName
, применяйте функцию
HasComputedColumnSql
:

modelBuilder.Entity<Car>(entity =>

{

entity.Property(p => p.FullName)

.HasComputedColumnSql("[PetName] + ' (' + [Color] + ')'");

});

В версии EF Core 5 появилась возможность сохранения вычисляемых значений, так что значение вычисляется только при создании или обновлении строки. Хотя в SQL Server упомянутая возможность поддерживается, она присутствует не во всех хранилищах данных, поэтому проверяйте документацию по своему поставщику баз данных:

modelBuilder.Entity<Car>(entity =>

{

entity.Property(p => p.FullName)

.HasComputedColumnSql("[PetName] + ' (' + [Color] + ')'", stored:true);

});

Отношения "один ко многим"

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

modelBuilder.Entity<Car>(entity =>

{

...

entity.HasOne(d => d.MakeNavigation)

.WithMany(p => p.Cars)

.HasForeignKey(d => d.MakeId)

.OnDelete(DeleteBehavior.ClientSetNull)

.HasConstraintName("FK_Inventory_Makes_MakeId");

});

Если вы выберете в качестве основы для конфигурации навигационной сущности главную сущность, тогда код будет выглядеть примерно так:

Поделиться с друзьями: