Выражение с несколькими параметрами и типом возвращаемого значения int в предложении EF Core Where

Я использую .NET Core 3.1, Microsoft.EntityFrameworkCore 3.1.9 и Npgsql 4.1.9. Я хотел бы повторно использовать функцию CompareBuilds для сравнения 2 объектов Build в операторах Where(...). Я хотел бы, чтобы функция была переведена в SQL.

Это функция, которая у меня есть в настоящее время:

public List<Ticket> GetTicketsForBuild(Build build)
{
    Expression<Func<Build, Build, int>> CompareBuilds = (first, second) =>
        // complex logic, simplified for the sake of example
        first.Version == second.Version ? 0 : (first.Version < second.Version ? -1 : 1);
    
    return _myDbContext.Ticket
        .Where(x => CompareBuilds(x.BuildIntroducedIn, build) <= 0)
        .Where(x => x.IsPublic ?
            CompareBuilds(x.BuildResolvedIn, build) < 0 :
            CompareBuilds(x.BuildResolvedIn, build) > 0)
        .ToList();
}

Однако компилятор выдает мне следующую ошибку в первом предложении Where:

CS0149: Method name expected

Как я могу повторно использовать одну и ту же функцию для сравнения двух Build объектов внутри Where предложений? Более того, эта функция должна возвращать int, как показано в фрагменте кода.

Вы получаете эту ошибку, потому что Выражения не могут быть вызваны таким образом - если бы вы объявили CompareBuilds просто как Func, то вы могли бы назвать это как CompareBuilds(x.BuildIntroducedIn, build) в своей лямбде, но сначала Выражение должно быть .Compile()'d, и это, конечно, сделало бы сравнение разрешенным на стороне клиента.

Orion 03.02.2023 13:05

А если объявить его просто Func, то он будет разрешаться и на стороне клиента.

Orion 03.02.2023 13:15

Хорошо, я понимаю, что компиляция Expression препятствует его переводу в SQL. Однако я хотел бы, чтобы запрос выполнялся на уровне базы данных, а не на клиенте. Есть ли способ сделать то, что я хочу, без компиляции выражения, может быть, каким-то образом обернув его, используя скомпилированные запросы, ...?

Mark 03.02.2023 13:15

Я делаю это в своей кодовой базе следующим образом dotnetfiddle.net/JIPXcw, но он не позволяет вам дополнительно изменять условие Where, как вы делаете с тернарным if или < 0 или > 0.

Orion 03.02.2023 13:28

Возможно, то, что вы хотите сделать, достижимо с помощью создания собственных деревьев выражений (learn.microsoft.com/en-us/dotnet/csharp/… ), но я не могу с этим помочь.

Orion 03.02.2023 13:30

Точно, я видел много таких примеров. Я использую этот подход в своей кодовой базе, а также для предикатных предложений. Однако я еще не нашел пример того, как использовать результат Expression<Func<...>> в предложении Where (например, < 0). Я посмотрю на создание собственного дерева выражений.

Mark 03.02.2023 13:32

Вам нужно LINQKit здесь. Если интересно могу показать образец.

Svyatoslav Danyliv 03.02.2023 14:31

@SvyatoslavDanyliv Да, пожалуйста, я был бы очень признателен за пример.

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

Ответы 1

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

Сначала активируйте LINQKit. Его нужно просто настроить DbContextOptions:

builder
    .UseSqlServer(connectionString) // or any other provider
    .WithExpressionExpanding();     // enabling LINQKit extension

Затем вы можете повторно использовать это выражение:

public List<Ticket> GetTicketsForBuild(Build build)
{
    Expression<Func<Build, Build, int>> CompareBuilds = (first, second) =>
        // complex logic, simplified for the sake of example
        first.Version == second.Version ? 0 : (first.Version < second.Version ? -1 : 1);
    
    return _myDbContext.Ticket
        .Where(x => CompareBuilds.Invoke(x.BuildIntroducedIn, build) <= 0)
        .Where(x => x.IsPublic ?
            CompareBuilds.Invoke(x.BuildResolvedIn, build) < 0 :
            CompareBuilds.Invoke(x.BuildResolvedIn, build) > 0)
        .ToList();
}

Но обычно такие функции удобны в других запросах, и мы можем сделать это более общим способом:

public static class QueryHelper
{
    [Expandable(nameof(CompareBuildsImpl))]
    public static int CompareBuilds(Build first, Build second)
    {
        throw new InvalidOperationexception();
    }

    private static Expression<Func<Build, Build, int>> CompareBuildsimpl()
    {
        return (first, second) =>
            // complex logic, simplified for the sake of example
            first.Version == second.Version ? 0 : (first.Version < second.Version ? -1 : 1);
    }
}

И повторно использовать в запросах:

public List<Ticket> GetTicketsForBuild(Build build)
{    
    return _myDbContext.Ticket
        .Where(x => QueryHelper.CompareBuilds(x.BuildIntroducedIn, build) <= 0)
        .Where(x => x.IsPublic ?
            QueryHelper.CompareBuilds(x.BuildResolvedIn, build) < 0 :
            QueryHelper.CompareBuilds(x.BuildResolvedIn, build) > 0)
        .ToList();
}

Есть и другие библиотеки, которые делают то же самое, и я собрал некоторые из них здесь

Спасибо. Это именно то, что мне нужно. Всего одно небольшое замечание для всех будущих читателей. Если вы не хотите включать LinqKit глобально, вы можете включить его для каждого запроса с помощью _myDbContext.Ticket.AsExpandable().

Mark 06.02.2023 10:32

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