假设我有这样的功能:
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可以支持它,那么技术上应该可以做到这一点。我怀疑我只是错过了正确的语法。
答案 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
),否则代码将无效,并且无法使用工作(虽然如果我们足够关心,我们可以生成更好的错误消息)。