Предложение EFcore where с пользовательской функцией для определения типа сравнения

Что я пытаюсь сделать

У меня есть функция репозитория, которую я хочу поддерживать поиск по строке Equals, Contains, StartsWith, EndsWith. Я создал простой метод расширения, который охватывает эти строковые функции, но EFCore, похоже, не может его перевести.

Существуют ли какие-либо альтернативные, многоразовые подходы, подобные этому?

Как я пытаюсь это сделать
public enum StringComparisonType
{
    Equals,
    Contains,
    BeginsWith,
    EndsWith
}
public static bool CompareTo(this string inputText, string comparisonText, StringComparisonType comparisonType) => comparisonType switch
{
    StringComparisonType.Equals => inputText.Equals(comparisonText),
    StringComparisonType.BeginsWith => inputText.StartsWith(comparisonText),
    StringComparisonType.Contains => inputText.Contains(comparisonText),
    StringComparisonType.EndsWith => inputText.EndsWith(comparisonText),
    _ => throw new NotImplementedException($"{nameof(StringComparisonType)} {comparisonType} not currently supported.")
};
var searchText = "hello";
var comparison = StringComparisonType.BeginsWith;
_context.Records.Where(r => r.Text.CompareTo(searchText, comparison))
Проблема с подходом

Это вызывает ошибку в строках:

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

Альтернативный подход

Единственная альтернатива, которую я нашел, это просто встраивание логики для определения типа сравнения, но это ужасно для чтения, ужасно для записи и не может использоваться повторно, например.

_context.Records
    .Where(r => comparison == StringComparisonType.Equals 
        ? r.Text.Equals(searchText) 
        : comparison == StringComparisonType.BeginsWith 
            ? r.Text.StartsWith(searchText) 
            : comparison == StringComparisonType.EndsWith 
                ? r.Text.EndsWith(searchText) 
                : r.Text.Contains(searchText))

В настоящее время я использую EFCore 7.

Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Если вы используете его для предопределенного типа (например, Record), попробуйте что-то вроде этого:

public static IQueryable<Record> WhereCompare(this IQueryable<Record> query, string comparisonText, StringComparisonType comparisonType) => comparisonType switch
{
    StringComparisonType.Equals => query.Where(r => r.Text.Equals(comparisonText)),
    StringComparisonType.BeginsWith => query.Where(r => r.Text.StartsWith(comparisonText)),
    StringComparisonType.Contains => query.Where(r => r.Text.Contains(comparisonText)),
    StringComparisonType.EndsWith => query.Where(r => r.Text.EndsWith(comparisonText)),
    _ => throw new NotImplementedException($"{nameof(StringComparisonType)} 
             {comparisonType} not currently supported.")
}

А затем используйте его следующим образом:

var result = _context.Records.WhereCompare(searchText, comparison).ToList();

Общий метод

Может быть, это можно сделать проще, но давайте сделаем это. Сначала реализуйте функцию Compose (объедините последовательность выражений в одно):

using System;
using System.Linq;
using System.Linq.Expressions;

...

private static Expression<Func<TSource, TResult>> Compose<TSource, TIntermediate, TResult>(
    this Expression<Func<TSource, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TSource));
    var intermediateValue = first.Body.ReplaceParameter(first.Parameters[0], param);
    var body = second.Body.ReplaceParameter(second.Parameters[0], intermediateValue);
    return Expression.Lambda<Func<TSource, TResult>>(body, param);
}

private static Expression ReplaceParameter(this Expression expression,
    ParameterExpression toReplace,
    Expression newExpression)
{
    return new ParameterReplaceVisitor(toReplace, newExpression)
        .Visit(expression);
}
private class ParameterReplaceVisitor : ExpressionVisitor
{
    private ParameterExpression from;
    private Expression to;
    public ParameterReplaceVisitor(ParameterExpression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node == from ? to : node;
    }
}

С помощью этих методов мы теперь можем создать это:

public static IQueryable<T> WhereCompare<T>(this IQueryable<T> query, Expression<Func<T, string>> selector, string comparisonText, StringComparisonType comparisonType)
{
    var filter = Compose<T, string, bool>(selector, WhereCompareSelector<T>(comparisonText, comparisonType));
    return query.Where(filter);
}

public static Expression<Func<string, bool>> WhereCompareSelector<T>(string comparisonText, StringComparisonType comparisonType) => comparisonType switch
{
    StringComparisonType.Equals => r => r.Equals(comparisonText),
    StringComparisonType.BeginsWith => r => r.StartsWith(comparisonText),
    StringComparisonType.Contains => r => r.Contains(comparisonText),
    StringComparisonType.EndsWith => r => r.EndsWith(comparisonText),
    _ => throw new NotImplementedException($"{nameof(StringComparisonType)}{comparisonType} not currently supported.")
};

Применение:

var result = _context.Records.WhereCompare(t => t.Text, searchText, comparison).ToList();

Хороший, работает удовольствие! Я не буду принимать ваш ответ, пока я поиграю, чтобы попытаться найти более многоразовый подход к работе. Это хорошо и может быть повторно использовано для всех объектов Record, но было бы здорово, если бы эту функцию можно было сделать еще более универсальной и использовать для любого объекта.

devklick 19.11.2022 10:38

Я добавил генетический способ сделать это. Но также оставил оригинальный метод для людей, не знакомых с дженериками. Это ответ T N, но с исправлением и небольшим объяснением.

Kazbek 19.11.2022 13:15

Ваш общий подход, похоже, не компилируется для меня. Имеет ошибку Method name expected, где вызывается selector. Это должно быть selector.Compile()(r) вместо selector(r)? (Я вообще не знаком с выражениями. Хотя selector.Compile()(r) успешно компилируется, возникает та же ошибка времени выполнения, The LINQ expression 'DbSet<Answer>() .Where(a => Invoke(__Compile_0, a).Contains(__comparisonText_1))' could not be translated

devklick 19.11.2022 14:17

Добавлен еще один сложный вариант. Проверил, что сейчас компилируется. Попробуйте это и дайте мне знать. Compose Метод не мой, найден недавно на stackoverflow и используется несколько лет.

Kazbek 19.11.2022 14:59

Очень хорошо. Работает как шарм. Теперь мне нужно будет потратить больше времени на изучение выражений, чтобы понять, как это работает: D Спасибо за это!

devklick 19.11.2022 15:24

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