Используйте LinqKit PredicateBuilder для связанной модели (EF Core)
Я хочу использовать PredicateBuilder от LinqKit и передать предикат в .Any
метод для связанной модели.
Итак, я хочу построить предикат:
var castCondition = PredicateBuilder.New<CastInfo>(true);
if (movies != null && movies.Length > 0)
{
castCondition = castCondition.And(c => movies.Contains(c.MovieId));
}
if (roleType > 0)
{
castCondition = castCondition.And(c => c.RoleId == roleType);
}
А затем используйте его для фильтрации модели, которая имеет отношение к модели в предикате:
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();
Но это вызывает System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(Convert(__castCondition_0, Func``2))': The given arguments did not match the expected arguments: Object of type 'System.Linq.Expressions.UnaryExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
Я видел аналогичный вопрос и ответ там предлагает использовать .Compile
, Или еще один вопрос, который создает дополнительный предикат.
Поэтому я попытался использовать дополнительный предикат
var tp = PredicateBuilder.New<Name>(true);
tp = tp.And(n => n.CastInfo.Any(castCondition.Compile()));
IQueryable<Name> result = _context.Name.AsExpandable().Where(tp);
Или используйте компиляцию напрямую
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition.Compile()));
Но у меня есть ошибка по поводу компиляции: System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(__Compile_0)'
Так можно ли преобразовать результат из PredicateBuilder для передачи в Any
?
Примечание: я смог построить желаемое поведение, комбинируя выражения, но мне не нравится, что мне нужны дополнительные переменные.
System.Linq.Expressions.Expression<Func<CastInfo,bool>> castExpression = (c => true);
if (movies != null && movies.Length > 0)
{
castExpression = (c => movies.Contains(c.MovieId));
}
if (roleType > 0)
{
var existingExpression = castExpression;
castExpression = c => existingExpression.Invoke(c) && c.RoleId == roleType;
}
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castExpression.Compile()));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();
Так что я предполагаю, что просто что-то упустил из-за строителя
Обновление о версиях: я использую dotnet core 2.0 и LinqKit.Microsoft.EntityFrameworkCore 1.1.10
2 ответа
Глядя на код, можно предположить, что тип castCondition
переменная Expression<Func<CastInfo, bool>>
(как это было в более ранних версиях PredicateBuilder
).
Но если бы это было так, то n.CastInfo.Any(castCondition)
не должен даже компилироваться (при условии CastInfo
это свойство навигации по коллекции, поэтому компилятор Enumerable.Any
что ожидает Func<CastInfo, bool>
не Expression<Func<CastInfo, bool>>
). Так что здесь происходит?
На мой взгляд, это хороший пример неявного злоупотребления оператором в C#. PredicateBuilder.New<T>
Метод на самом деле возвращает класс с именем ExpressionStarter<T>
, который имеет много методов эмуляции Expression
, но что более важно, имеет неявное преобразование в Expression<Func<T, bool>>
а также Func<CastInfo, bool>
, Последнее позволяет использовать этот класс для верхнего уровня Enumerable
/ Queryable
методы как замена соответствующего лямбда-функции / выражения. Тем не менее, он также предотвращает ошибку времени компиляции при использовании внутри дерева выражений, как в вашем случае - компилятор выдает что-то вроде n.CastInfo.Any((Func<CastInfo, bool>)castCondition)
что, конечно, вызывает исключение во время выполнения.
Вся идея LinqKit AsExpandable
метод должен позволить "вызывать" выражения через пользовательский Invoke
метод расширения, который затем "раскрывается" в дереве выражений. Итак, вернемся к началу, если тип переменной был Expression<Func<CastInfo, bool>>
, предполагаемое использование:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));
Но теперь это не компилируется из-за причины, объясненной ранее. Таким образом, вы должны сначала преобразовать его в Expression<Func<T, bool>
вне запроса:
Expression<Func<CastInfo, bool>> castPredicate = castCondition;
а затем использовать
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castPredicate.Invoke(c)));
или же
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile()));
Чтобы позволить компилятору выводить тип выражения, я бы создал собственный метод расширения, например:
using System;
using System.Linq.Expressions;
namespace LinqKit
{
public static class Extensions
{
public static Expression<Func<T, bool>> ToExpression<T>(this ExpressionStarter<T> expr) => expr;
}
}
а потом просто использовать
var castPredicate = castCondition.ToExpression();
Это все еще должно быть сделано вне запроса, то есть следующее не работает:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c)));
Это может быть не совсем связано с исходным вопросом, но с учетом следующей модели:
public Class Music
{
public int Id { get; set; }
public List<Genre> Genres { get; set; }
}
public Class Genre
{
public int Id { get; set; }
public string Title { get; set; }
}
List<string> genresToFind = new() {"Pop", "Rap", "Classical"};
Если вы пытаетесь найти все
Musics
что их жанры существуют в
genresToFind
список, вот что вы можете сделать:
Создавать
PredicateBuilder
цепочка выражений на
Genre
модель :
var pre = PredicateBuilder.New<Genre>();
foreach (var genre in genresToFind)
{
pre = pre.Or(g => g.Title.Contains(genre));
}
Затем выполните свой запрос следующим образом:
var result = await _db.Musics.AsExpandable()
.Where(m => m.Genres
.Any(g => pre.ToExpression().Invoke(g)))
.ToListAsync();
ToExpression()
— это общий метод расширения, который мы создали для преобразования
ExpressionStarter<Genre>
введите в
Expression<Func<Genre, bool>>
:
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> ToExpression<T> (this
ExpressionStarter<T> exp) => exp;
}
Также вам понадобится
LinqKit.Microsoft.EntityFrameworkCore
пакет для efcore.