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

ЖАНРЫ

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

modelBuilder.Entity<Make>(entity =>

{

...

entity.HasMany(e=>e.Cars)

.WithOne(c=>c.MakeNavigation)

.HasForeignKey(c=>c.MakeId)

.OnDelete(DeleteBehavior.ClientSetNull)

.HasConstraintName("FK_Inventory_Makes_MakeId");

});

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

Отношения "один к одному" конфигурируются аналогично,

но только вместо метода
WithMany
интерфейса Fluent API используется метод
WithOne
. К зависимой сущности добавляется уникальный индекс. Вот код для отношения между сущностями
Car
и
Radio
, где применяется зависимая сущность (
Radio
):

modelBuilder.Entity<Radio>(entity =>

{

entity.HasIndex(e => e.CarId, "IX_Radios_CarId")

.IsUnique;

entity.HasOne(d => d.CarNavigation)

.WithOne(p => p.RadioNavigation)

.HasForeignKey<Radio>(d => d.CarId);

});

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

Car
и
Radio
, где для отношения используется главная сущность:

modelBuilder.Entity<Radio>(entity =>

{

entity.HasIndex(e => e.CarId, "IX_Radios_CarId")

.IsUnique;

});

modelBuilder.Entity<Car>(entity =>

{

entity.HasOne(d => d.RadioNavigation)

.WithOne(p => p.CarNavigation)

.HasForeignKey<Radio>(d => d.CarId);

});

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

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

modelBuilder.Entity<Car>

.HasMany(p => p.Drivers)

.WithMany(p => p.Cars)

.UsingEntity<Dictionary<string, object>>(

"CarDriver",

j => j

.HasOne<Driver>

.WithMany

.HasForeignKey("DriverId")

.HasConstraintName("FK_CarDriver_Drivers_DriverId")

.OnDelete(DeleteBehavior.Cascade),

j => j

.HasOne<Car>

.WithMany

.HasForeignKey("CarId")

.HasConstraintName("FK_CarDriver_Cars_CarId")

.OnDelete(DeleteBehavior.ClientCascade));

Соглашения,

аннотации данных и Fluent API — что выбрать?

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

DbContext
. Независимо от того, используете вы аннотации данных или Fluent API, имейте в виду, что аннотации данных переопределяют встроенные соглашения, а методы Fluent API переопределяют вообще все.

Выполнение запросов

Запросы на извлечение данных создаются посредством запросов LINQ в отношении свойств

DbSet<T>
. На стороне сервера механизм трансляции LINQ поставщика баз данных видоизменяет запрос LINQ с учетом специфичного для базы данных языка (скажем, Т-SQL). Запросы LINQ, охватывающие (или потенциально охватывающие) множество записей, не выполняются до тех пор, пока не начнется проход по результатам запросов (например, с применением
foreach
) или не произойдет привязка к элементу управления для их отображения (наподобие визуальной сетки данных). Такое отложенное выполнение позволяет строить запросы в коде, не испытывая проблем с производительностью из-за частого взаимодействия с базой данных.

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

var cars = Context.Cars.Where(x=>x.Color == "Yellow");

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

ToList
:

var cars = Context.Cars.Where(x=>x.Color == "Yellow").ToList;

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

var query = Context.Cars.AsQueryable;

query = query.Where(x=>x.Color == "Yellow");

var cars = query.ToList;

Запросы с одной записью (как в случае применения

First/FirstOrDefault
) выполняются немедленно при вызове действия (такого как
FirstOrDefault
), а операторы создания, обновления и удаления выполняются немедленно, когда запускается метод
DbContext.SaveChanges
.

Смешанное выполнение на клиентской и серверной сторонах

В предшествующих версиях EF Core была введена возможность смешивания выполнения на стороне сервера и на стороне клиента. Это означало, что где-то в середине оператора LINQ можно было бы вызвать функцию C# и по существу свести на нет все преимущества, описанные в предыдущем разделе. Часть до вызова функции C# выполнится на стороне сервера, но затем все результаты (в данной точке запроса) доставляются на сторону клиента и остаток запроса будет выполнен как LINQ to Objects. В итоге возможность смешанного выполнения привнесла больше проблем, нежели решила, и в выпуске EF Core 3.1 такая функциональность была изменена. Теперь выполнять на стороне клиента можно только последний узел оператора LINQ.

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