有没有一种方法可以将外部函数内联到EF-Linq查询中

本文关键字:函数 外部 查询 EF-Linq 一种 方法 有没有 | 更新日期: 2023-09-27 18:29:01

假设我有一个这样的函数:

var filterValue = GetCurrentFilter(state);

然后是EF查询:

var result = context.EntitySet.Where(x=> x.column > filterValue);

这是有效的,但只要我尝试内联:

var result = context.EntitySet.Where(x=> x.column > GetCurrentFilter(state));

这并不是因为EF Linq试图将GetCurrentFilter解析到表达式树中,但无法做到这一点。这一切都是可以理解的。

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

类似的东西

var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)));

由于GetCurrentFilter没有作为查询一部分的参数,如果EF-Linq能够支持它,那么从技术上讲,这应该是可能的。我怀疑我只是缺少正确的语法。

有没有一种方法可以将外部函数内联到EF-Linq查询中

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

这将允许你写:

var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)))
    .EvaluateResults();

然后,它将在客户端评估GetCurrentFilter(state),并将结果作为常量内联到查询中。

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

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

它会打印出来:

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

这正是我们想要的。

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