У меня есть функция репозитория, которую я хочу поддерживать поиск по строке 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.
Если вы используете его для предопределенного типа (например, 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();
Я добавил генетический способ сделать это. Но также оставил оригинальный метод для людей, не знакомых с дженериками. Это ответ T N, но с исправлением и небольшим объяснением.
Ваш общий подход, похоже, не компилируется для меня. Имеет ошибку 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
Добавлен еще один сложный вариант. Проверил, что сейчас компилируется. Попробуйте это и дайте мне знать. Compose
Метод не мой, найден недавно на stackoverflow и используется несколько лет.
Очень хорошо. Работает как шарм. Теперь мне нужно будет потратить больше времени на изучение выражений, чтобы понять, как это работает: D Спасибо за это!
Хороший, работает удовольствие! Я не буду принимать ваш ответ, пока я поиграю, чтобы попытаться найти более многоразовый подход к работе. Это хорошо и может быть повторно использовано для всех объектов
Record
, но было бы здорово, если бы эту функцию можно было сделать еще более универсальной и использовать для любого объекта.