.NET Разработчик
6.54K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2371. #ЧтоНовенького #EF10
Именованные Фильтры Запросов в EF 10

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

Однако в предыдущих версиях EF Core было одно существенное ограничение: для каждого типа сущности можно было определить только один фильтр. Если нужно было объединить несколько условий, приходилось либо писать явные выражения &&, либо вручную отключать и повторно применять фильтры в конкретных запросах. Если HasQueryFilter вызывался дважды для одной и той же сущности, второй вызов перезаписывал первый.
modelBuilder.Entity<Order>()
.HasQueryFilter(o => !o.IsDeleted && o.TenantId == id);

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

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

Чтобы прикрепить несколько фильтров к сущности, вызовите HasQueryFilter, указав имя для каждого фильтра:
modelBuilder.Entity<Order>()
.HasQueryFilter("SoftDeletionFilter",
o => !o.IsDeleted)
.HasQueryFilter("TenantFilter",
o => o.TenantId == tenantId);


Под капотом EF создаёт отдельные фильтры, идентифицируемые по указанным вами именам. Теперь вы можете отключить только фильтр мягкого удаления, сохранив фильтр клиента:
var allOrders = await context
.Orders
.IgnoreQueryFilters(["SoftDeletionFilter"])
.ToListAsync();

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

Совет: Используйте константы для имён фильтров
Именованные фильтры используют строковые ключи. Использование имён в виде строк в коде приводит к опечаткам и трудно уловимым ошибкам. Чтобы избежать этого, определите константы или перечисления для имён фильтров и используйте их повторно при необходимости:
public static class OrderFilters
{
public const string SoftDelete = nameof(SoftDelete);
public const string Tenant = nameof(Tenant);
}

modelBuilder.Entity<Order>()
.HasQueryFilter(OrderFilters.SoftDelete,
o => !o.IsDeleted)
.HasQueryFilter(OrderFilters.Tenant,
o => o.TenantId == tenantId);

// В запросе
var allOrders = await context
.Orders
.IgnoreQueryFilters([OrderFilters.SoftDelete])
.ToListAsync();


Определение имён фильтров в одном месте уменьшает дублирование и повышает удобство поддержки. Другой рекомендуемый подход — обернуть вызов в метод расширения:
public static IQueryable<Order> 
IncludeSoftDeleted(this IQueryable<Order> query)
=> query.IgnoreQueryFilters([OrderFilters.SoftDelete]);

Это делает ваши намерения явными и централизует логику фильтрации в одном месте.

Итого
Добавление именованных фильтров запросов в EF 10 устраняет одно из давних ограничений функции глобальных фильтров запросов EF. Теперь вы можете:
- Прикреплять несколько фильтров к одной сущности и управлять ими по отдельности;
- Выборочно отключать определённые фильтры в запросе LINQ с помощью IgnoreQueryFilters(["FilterName"]);
- Упрощать распространённые шаблоны, такие как мягкое удаление и мультитенантность, без использования сложной условной логики.
Именованные фильтры запросов могут стать мощным инструментом для поддержания чистоты запросов и инкапсуляции логики предметной области.

Источник: https://www.milanjovanovic.tech/blog/named-query-filters-in-ef-10-multiple-query-filters-per-entity
👍16