Используйте 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);
или используйте compile напрямую
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
1 ответов
глядя на код, можно предположить, что типа castCondition
переменная Expression<Func<CastInfo, bool>>
(как это было в более ранних версиях PredicateBuilder
).
но если это так, то n.CastInfo.Any(castCondition)
не должен даже компилироваться (предполагая CastInfo
является свойством навигации коллекции, поэтому компилятор нажмет Enumerable.Any
что ожидает Func<CastInfo, bool>
, а не Expression<Func<CastInfo, bool>>
). Так что здесь происходит?
на мой взгляд, это хороший пример c# неявное обращение. The PredicateBuilder.New<T>
способ фактически возвращает класс с именем ExpressionStarter<T>
, который имеет много методов эмуляции Expression
, но, что более важно, имеет подразумевается преобразование Expression<Func<T, bool>>
и Func<CastInfo, bool>
. Более поздний позволяет использовать этот класс для верхнего уровня Enumerable
/ Queryable
методы как замена соответствующего лямбда-функции / выражения. Однако он также предотвращает ошибку времени компиляции при использовании внутри дерева выражений, как в вашем случае-complier выдает что-то вроде n.CastInfo.Any((Func<CastInfo, bool>)castCondition)
какой из конечно, вызывает исключение во время выполнения.
вся идея LinqKit AsExpandable
метод должен позволять" вызывать " выражения через custom 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)));