Преобразование SQL-фильтра EF Core

Я использую EF Core 5.0.1 с веб-API ASP NET 5.0.1 и хочу построить запрос с помощью PredicateBuilder, используя LinqKit.Microsoft.EntityFrameworkCore 5.0.2.1

Для целей вопроса я упростил свою модель до:

public class User
{
    public long IdUser { get; set; }
    public string Name { get; set; }
    public virtual ICollection<UserDepartment> UserDepartments { get; set; }
}

public class Department
{
    public long IdDepartament { get; set; }
    public string Name { get; set; }
    public virtual ICollection<UserDepartment> UsersDepartment { get; set; }
}

public UserDepartment
{
    public long IdUser { get; set; }
    public long IdDepartment { get; set; }
    public virtual Department { get; set; }
    public virtual User { get; set; }
}

У одного User может быть много Departaments, а у одного Departament может быть много Users

Три модели имеют соответствующую таблицу в SQL Server и класс IEntityTypeConfiguration с установленными соответствующими отношениями.

Все, чего я хочу добиться, это найти любой User, принадлежащий любому Departament, который Department.Name находится в List<String>.

List<String> содержит список ключевых слов, а не точное название отдела.

Department таблица имеет такие строки:

Идентификационный отдел Имя 1 Администрация 2 HHRR 3 Продажи 4 Маркетинг

А List<String> может быть любым ключевым словом, например «Админ», «Сал», «Отметить» и так далее.

Первая попытка

... должен был построить такой предикат:

List<string> kwDepartments = new List<String> {"mark","admin"};
var predicate = PredicateBuilder.New<User>(true);

predicate = predicate.And(x => x.UserDepartments.Where(y => kwDepartments.Any(c => y.Department.Name.Equals(c))).Any());

Это создает SQL с оператором IN, например:

...[t].[Name] IN (N'mark', N'admin'))

Очевидно, это не то, что я хочу, но если я использую .Contains вместо .Equals, возникает исключение

Выражение LINQ не может быть переведено

Я думаю, это потому, что я пытаюсь оценить непримитивное значение.

Вторая попытка

... должен был перебрать kwDepartments и добавить .Or для каждой строки, например:

foreach (string dep in kwDepartments )
{
    predicateDep = predicateDep.Or(x => x.UserDepartments.Where(y=> y.Department.Name.Contains(dep)).Any());
}

predicate = predicate.And(predicateDep);

Это возвращает совпадение, которое я ожидаю, но также две проблемы.

  1. Перевод SQL имеет низкую производительность, так как EF Core обрабатывает INNER JOIN для каждого ключевого слова. Неважно, состоит ли kwDepartment из двух или трех элементов, но со 100 или 1000 элементами будет недопустимо.

  2. У каждого INNER JOIN на Departments таблице есть SELECT со всеми полями таблицы, и мне не нужно ни одно из них. Я пытался поместить оператор .Select(x=> x.Name) в предикат, чтобы он принимал только два поля, но это не дало никакого эффекта.

Третья попытка

... использовал полнотекстовый поиск с помощью EF.Functions.FreeText, но, похоже, это не имеет значения.

Моя цель - построить предикат, который переводит что-то похожее на:

SELECT [c.IdUser]. [c.Name]
FROM [User] AS [c]
WHERE EXISTS (
    SELECT 1
    FROM [UserDepartment] AS [u]
    INNER JOIN (
        SELECT [c0].[IdDepartment], [c0].[Name] <--ONLY NEED TWO FIELDS INSTEAD OF ALL FIELDS
        FROM [Department] AS [c0]
               ) AS [t] ON [u].[IdDepartment] = [t].[IdDepartment]
    WHERE ([c].[IdUser] = [u].[IdUser]) AND ([t].[Name] like (N'admin%') or [t].[Name] like  (N'mark%'))) 

Использование оператора LIKE не обязательно, но я добавил его для лучшего понимания.

Еще раз спасибо!

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
536
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы на правильном пути с предикатом Or, но вместо нескольких предикатов Or для user.UserDepatments.Any(single_match) вы должны создать один предикат на основе Or, который будет использоваться внутри одного user.UserDepatments.Any(multi_or_match).

Что-то вроде этого:

var departtmentPredicate = kwDepartments
    .Select(kw => Linq.Expr((Department d) => EF.Functions.Like(d.Name, "%" + kw + "%")))
    .Aggregate(PredicateBuilder.Or);

а потом

predicate = predicate.And(u => u.UserDepartments
    .Select(ud => ud.Department) // navigate to department
    .AsQueryable() // to be able to use departtmentPredicate expression directly
    .Any(departtmentPredicate));

С этим кодом и списком примеров DbSet<User>().Where(predicate) переводится примерно так:

DECLARE @__p_1 nvarchar(4000) = N'%mark%';
DECLARE @__p_2 nvarchar(4000) = N'%admin%';

SELECT [u].[IdUser], [u].[Name]
FROM [User] AS [u]
WHERE EXISTS (
    SELECT 1
    FROM [UserDepartment] AS [u0]
    INNER JOIN [Department] AS [d] ON [u0].[IdDepartment] = [d].[IdDepartament]
    WHERE ([u].[IdUser] = [u0].[IdUser]) AND (([d].[Name] LIKE @__p_1) OR ([d].[Name] LIKE @__p_2)))

Большое спасибо Иван. Ваше решение работает как шарм! :) Остается только одно, чтобы все было идеально. SQL, который создает ваше решение, включает SELECT со всеми полями (моя модель UserDepartment имеет 15 полей) во ВНУТРЕННЕМ СОЕДИНЕНИИ с UserDepartment, который мне не нужен, нужны только IdDepartment и Name. Есть ли способ взять только эти два поля из SQL?

Pablo7747 19.12.2020 15:14

На самом деле вы можете видеть, что он объединяет две таблицы без специального выбора каких-либо полей, обратите внимание select 1 from ...

Ivan Stoev 19.12.2020 15:19

Моя ошибка, дополнительные поля извлекаются, потому что у меня есть дополнительный предикат запроса в классе IEntittypeConfiguration. Ваше решение было идеальным, как есть Еще раз спасибо, Иван!

Pablo7747 19.12.2020 15:22

Другие вопросы по теме