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

时间:2014-11-04 20:09:26

标签: c# .net linq entity-framework

假设我有这样的功能:

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知道在构建树时需要执行GetCurrentFilter函数并在树中使用它的结果?

这样的东西
var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)));

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

1 个答案:

答案 0 :(得分: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);
    }
}

这将允许你写:

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 =&gt;(x&gt; 2))

这正是我们想要的。

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