有没有办法将外部函数内联到EF Linq查询中?

And*_*ykh 8 .net c# linq entity-framework

假设我有这样的函数:

var filterValue = GetCurrentFilter(state);
Run Code Online (Sandbox Code Playgroud)

然后是EF查询:

var result = context.EntitySet.Where(x=> x.column > filterValue);
Run Code Online (Sandbox Code Playgroud)

这有效,但一旦我试图内联:

var result = context.EntitySet.Where(x=> x.column > GetCurrentFilter(state));
Run Code Online (Sandbox Code Playgroud)

它不是因为EF Linq试图解析GetCurrentFilter到表达式树而无法做到这一点.这一切都是可以理解的.

我的问题是,有没有办法让EF Linq知道在GetCurrentFilter构建树时需要执行函数并在树中使用它的结果?

就像是

var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)));
Run Code Online (Sandbox Code Playgroud)

由于GetCurrentFilter没有参数作为查询的一部分,因此如果EF Linq可以支持它,那么技术上应该可以做到这一点.我怀疑我只是错过了正确的语法.

Ser*_*rvy 8

GetCurrentFilter一个(只读)属性,而不是方法.与方法不同,EF会将属性评估为其值,而不是尝试将它们转换为SQL.


您拥有的唯一其他方法是遍历整个表达式树,搜索ResultOf方法的用法,将其参数计算为值,然后内联该值为ResultOf调用值的值,重新围绕该值重新构建查询.

为了实现这一点,它意味着您不仅需要在调用中包装您想要内联的代码EfUtil.ResultOf,而且还意味着调用查询本身的方法以强制它返回并评估它:

public class EfUtil
{
    public static T ResultOf<T>(T value)
    {
        return value;
    }
}
//Note this could probably use a better name
public static IQueryable<T> EvaluateResults<T>(this IQueryable<T> query)
{
    return query.Provider.CreateQuery<T>(
        new ExpressionEvaluator().Visit(query.Expression));
}

internal class ExpressionEvaluator : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression m)
    {
        if (m.Method.Name == "ResultOf" && m.Method.DeclaringType == typeof(EfUtil))
        {
            Expression target = m.Arguments[0];

            object result = Expression.Lambda(target)
                .Compile()
                .DynamicInvoke();

            return Expression.Constant(result, target.Type);
        }
        else
            return base.VisitMethodCall(m);
    }
}
Run Code Online (Sandbox Code Playgroud)

这将允许你写:

var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)))
    .EvaluateResults();
Run Code Online (Sandbox Code Playgroud)

然后,它将GetCurrentFilter(state)在客户端进行评估,并将结果内联为查询中的常量.

作为一个稍微简单的测试,我们可以编写以下内容:

var query = new[] { 1, 2, 3 }
    .AsQueryable()
    .Where(x => x > EfUtil.ResultOf(Math.Max(1, 2)))
    .EvaluateResults();

Console.WriteLine(query.ToString());
Run Code Online (Sandbox Code Playgroud)

它会打印出来:

System.Int32 [].其中(x =>(x> 2))

这正是我们想要的.

请注意,lambda的参数(x在这些示例中)的使用不能在调用中的任何地方使用,EfUtil.ResultOf否则代码将无法工作,并且无法使其工作(尽管我们可以生成更好的错误消息,如果我们关心的话足够).