Use LinqKit PredicateBuilder for related model (EF Core)

孤街浪徒 提交于 2019-12-04 05:10:05
Ivan Stoev

Looking at the code, one will assume that the type of castCondition variable is Expression<Func<CastInfo, bool>> (as it was in earlier versions of PredicateBuilder).

But if that was the case, then n.CastInfo.Any(castCondition) should not even compile (assuming CastInfo is a collection navigation property, so the compiler will hit Enumerable.Any which expects Func<CastInfo, bool>, not Expression<Func<CastInfo, bool>>). So what's going on here?

In my opinion, this is a good example of C# implicit operator abuse. The PredicateBuilder.New<T> method actually returns a class called ExpressionStarter<T>, which has many methods emulating Expression, but more importantly, has implicit conversion to Expression<Func<T, bool>> and Func<CastInfo, bool>. The later allows that class to be used for top level Enumerable / Queryable methods as replacement of the respective lambda func/expression. However, it also prevents the compile time error when used inside the expression tree as in your case - the complier emits something like n.CastInfo.Any((Func<CastInfo, bool>)castCondition) which of course causes exception at runtime.

The whole idea of LinqKit AsExpandable method is to allow "invoking" expressions via custom Invoke extension method, which then is "expanded" in the expression tree. So back at the beginning, if the variable type was Expression<Func<CastInfo, bool>>, the intended usage is:

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));

But now this doesn't compile because of the reason explained earlier. So you have to convert it first to Expression<Func<T, bool> outside of the query:

Expression<Func<CastInfo, bool>> castPredicate = castCondition;

and then use

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castPredicate.Invoke(c)));

or

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile()));

To let compiler infer the expression type, I would create a custom extension method like this:

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;
    }
}

and then simply use

var castPredicate = castCondition.ToExpression();

It still has to be done outside of the query, i.e. the following does not work:

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c)));
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!