Книги онлайн и без регистрации » Разная литература » Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 275 276 277 278 279 280 281 282 283 ... 407
Перейти на страницу:
она не рассматривается. За дополнительными сведениями о ленивой загрузке и ее применением с EF Core обращайтесь в документацию по ссылке https://docs.microsoft.com/ru-ru/ef/core/querying/related-data/lazy.

Глобальные фильтры запросов

Глобальные фильтры запросов позволяют добавлять конструкцию where во все запросы LINQ для определенной сущности. Например, распространенное проектное решение для баз данных предусматривает использование "мягкого" удаления вместо "жесткого". В таблицу добавляется поле, указывающее состояние удаления записи. Если запись "удалена", то значение поля устанавливается в true (или 1), но запись из базы данных не убирается. Прием называется "мягким" удалением. Чтобы отфильтровать записи, повергнувшиеся "мягкому" удалению, от тех, которые обрабатывались нормальными операциями, каждая конструкция where обязана проверять значение поля с состоянием удаления записи. Включение такого фильтра в каждый запрос может занять много времени, да и не забыть о нем довольно проблематично.

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

Придерживаясь в книге темы автомобилей, предположим, что все записи Car, которые не являются управляемыми, должны отфильтровываться из нормальных запросов. Вот как можно добавить глобальный фильтр запросов с использованием Fluent API:

modelBuilder.Entity<Car>(entity =>

{

  entity.HasQueryFilter(c => c.IsDrivable == true);

  entity.Property(p => p.IsDrivable).HasField("_isDrivable").

    HasDefaultValue(true);

});

Благодаря такому глобальному фильтру запросов запросы, вовлекающие сущность Car, будут автоматически отфильтровывать неуправляемые автомобили. Скажем, запуск следующего запроса LINQ:

var cars = Context.Cars.ToList();

приводит к выполнению показанного ниже оператора SQL:

SELECT [i].[Id], [i].[Color], [i].[IsDrivable], [i].[MakeId],

       [i].[PetName], [i].[TimeStamp]

FROM [Dbo].[Inventory] AS [i]

WHERE [i].[IsDrivable] = CAST(1 AS bit)

Если вам нужно просмотреть отфильтрованные записи, тогда добавьте в запрос LINQ вызов IgnoreQueryFilters(), который отключает глобальные фильтры запросов для каждой сущности в запросе LINQ. Запуск представленного далее запроса LINQ:

var cars = Context.Cars.IgnoreQueryFilters().ToList();

инициирует выполнение следующего оператора SQL:

SELECT [i].[Id], [i].[Color], [i].[IsDrivable], [i].[MakeId],

       [i].[PetName], [i].[TimeStamp]

FROM [Dbo].[Inventory] AS [i]

Важно отметить, что вызов IgnoreQueryFilters() удаляет фильтр запросов для всех сущностей в запросе LINQ, в том числе и тех, которые задействованы в вызовах Include() или Thenlnclude().

Глобальные фильтры запросов на навигационных свойствах

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

modelBuilder.Entity<Order>().HasQueryFilter(e => e.CarNavigation.IsDrivable);

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

// Код C#

var orders = Context.Orders.ToList();

/* Сгенерированный запрос SQL */

SELECT [o].[Id], [o].[CarId], [o].[CustomerId], [o].[TimeStamp]

FROM [Dbo].[Orders] AS [o]

INNER JOIN (SELECT [i].[Id], [i].[IsDrivable]

                       FROM [Dbo].[Inventory] AS [i]

                       WHERE [i].[IsDrivable] = CAST(1 AS bit)) AS [t]

         ON [o].[CarId] = [t].[Id]

WHERE [t].[IsDrivable] = CAST(1 AS bit)

Для удаления фильтра запросов используйте вызов IgnoreQueryFilters(). Вот как выглядит модифицированный оператор LINQ и сгенерированный запрос SQL:

// Код C#

var orders = Context.Orders.IgnoreQueryFilters().ToList();

/* Сгенерированный запрос SQL */

SELECT [o].[Id], [o].[CarId], [o].[CustomerId], [o].[TimeStamp]

FROM [Dbo].[Orders] AS [o]

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

Явная загрузка с глобальными фильтрами запросов

Глобальные фильтры запросов действуют и при явной загрузке связанных данных. Например, если вы хотите загрузить записи Car для Make, то фильтр IsDrivable предотвратит загрузку в память записей, представляющих неуправляемые автомобили. В качестве примера взгляните на следующий фрагмент кода:

var make = Context.Makes.First(x => x.Id == makeId);

Context.Entry(make).Collection(c=>c.Cars).Load();

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

SELECT [i].[Id], [i].[Color], [i].[IsDrivable],

              [i].[MakeId], [i].[PetName], [i].[TimeStamp]

FROM [Dbo].[Inventory] AS [i]

WHERE ([i].[IsDrivable] = CAST(1 AS bit)) AND ([i].[MakeId] = 1

С игнорированием фильтров запросов при явной загрузке данных связана небольшая загвоздка. Возвращаемым типом метода Collection() является CollectionEntry<Make,Car>, который явно не реализует интерфейс IQueryable<T>. Чтобы вызвать IgnoreQueryFilters(), сначала потребуется вызвать метод Query(), который возвращает экземпляр реализации IQueryable<Car>:

var make = Context.Makes.First(x => x.Id == makeId);

Context.Entry(make).Collection(c=>c.Cars).Query().IgnoreQueryFilters().Load();

Тот же процесс применяется в случае использования метода Reference() для извлечения данных из навигационного свойства типа коллекции.

Выполнение низкоуровневых запросов SQL с помощью LINQ

Иногда получить корректный оператор LINQ для компилируемого запроса сложнее, чем просто написать код SQL напрямую. К счастью, инфраструктура EF Core располагает механизмом, позволяющим выполнять низкоуровневые операторы SQL в DbSet<T>. Методы FromSqlRaw() и FromSqlRawInterpolated() принимают строку, которая становится основой запроса LINQ. Такой запрос выполняется на стороне сервера.

Если низкоуровневый оператор SQL не является завершающим (скажем, хранимой процедурой, пользовательской функцией или оператором, который использует общее табличное выражение или заканчивается точкой с запятой), тогда в запрос можно добавить дополнительные операторы LINQ. Дополнительные операторы LINQ наподобие конструкций Include(), OrderBy() или Where() будут объединены с первоначальным низкоуровневым обращением SQL и любыми глобальными фильтрами запросов, после чего весь запрос выполнится на стороне сервера.

При использовании одного из вариантов FromSql*() запрос должен формироваться с использованием схемы базы данных и имени таблицы, а не имен сущностей. Метод FromSqlRaw() отправит строку в том виде, в каком она записана. Метод FromSqlInterpolated() применяет интерполяцию строк C# и каждая интерполированная строка транслируется в параметр SQL. В случае использования переменных вы должны использовать версию с интерполяцией для обеспечения дополнительной защиты, присущей параметризованным запросам.

Предположим, что для сущности Car установлен глобальный фильтр запросов. Тогда показанный ниже оператор LINQ получит первую запись Inventory

1 ... 275 276 277 278 279 280 281 282 283 ... 407
Перейти на страницу:

Комментарии
Минимальная длина комментария - 20 знаков. В коментария нецензурная лексика и оскорбления ЗАПРЕЩЕНЫ! Уважайте себя и других!
Комментариев еще нет. Хотите быть первым?