如果我理解正确,您想在另一个表达式树中重复使用某一部分,并仍然允许编译器为您构建表达式树的所有魔力。
实际上,这是可行的,我在许多场合都做过这样的事情。
诀窍是将可重用部分包装在方法调用中,然后在应用查询之前取消包装。
首先,我会更改获取可重用部分的方法,使其成为返回您的表达式的静态方法(如mr100所建议的):
public static Expression<Func<Quote,QuoteProductImage, bool>> FilterQuoteProductImagesByQuote()
{
return (q,qpi) => q.User.Id == qpi.ItemOrder;
}
Wrapping 会使用以下方式进行:
public static TFunc AsQuote<TFunc>(this Expression<TFunc> exp)
{
throw new InvalidOperationException("This method is not intended to be invoked, just as a marker in Expression trees!");
}
然后展开将在以下位置发生:
public static Expression<TFunc> ResolveQuotes<TFunc>(this Expression<TFunc> exp)
{
var visitor = new ResolveQuoteVisitor();
return (Expression<TFunc>)visitor.Visit(exp);
}
显然,最有趣的部分发生在访问者中。
您需要做的是找到节点,这些节点是调用您的 AsQuote 方法的方法调用,并使用 lambda 表达式主体替换整个节点。该 lambda 将是该方法的第一个参数。
您的 resolveQuote 访问者将如下所示:
private class ResolveQuoteVisitor : ExpressionVisitor
{
public ResolveQuoteVisitor()
{
m_asQuoteMethod = typeof(Extensions).GetMethod("AsQuote").GetGenericMethodDefinition();
}
MethodInfo m_asQuoteMethod;
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (IsAsquoteMethodCall(node))
{
return Visit(ExtractQuotedExpression(node).Body);
}
return base.VisitMethodCall(node);
}
private bool IsAsquoteMethodCall(MethodCallExpression node)
{
return node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == m_asQuoteMethod;
}
private LambdaExpression ExtractQuotedExpression(MethodCallExpression node)
{
var quoteExpr = node.Arguments[0];
return (LambdaExpression)Expression.Lambda(quoteExpr).Compile().DynamicInvoke();
}
}
现在我们已经完成了一半。如果您的lambda函数没有任何参数,则以上内容已足够。在您的情况下,您需要替换lambda函数的参数为原始表达式中的参数。为此,我使用调用表达式,在其中获取我想要在lambda中具有的参数。
首先让我们创建一个visitor,它将用您指定的表达式替换所有参数。
private class MultiParamReplaceVisitor : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, Expression> m_replacements;
private readonly LambdaExpression m_expressionToVisit;
public MultiParamReplaceVisitor(Expression[] parameterValues, LambdaExpression expressionToVisit)
{
if (parameterValues.Length != expressionToVisit.Parameters.Count)
throw new ArgumentException(string.Format("The paraneter values count ({0}) does not match the expression parameter count ({1})", parameterValues.Length, expressionToVisit.Parameters.Count));
m_replacements = expressionToVisit.Parameters
.Select((p, idx) => new { Idx = idx, Parameter = p })
.ToDictionary(x => x.Parameter, x => parameterValues[x.Idx]);
m_expressionToVisit = expressionToVisit;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Expression replacement;
if (m_replacements.TryGetValue(node, out replacement))
return Visit(replacement);
return base.VisitParameter(node);
}
public Expression Replace()
{
return Visit(m_expressionToVisit.Body);
}
}
现在我们可以回到ResolveQuoteVisitor,并正确处理调用:
protected override Expression VisitInvocation(InvocationExpression node)
{
if (node.Expression.NodeType == ExpressionType.Call && IsAsquoteMethodCall((MethodCallExpression)node.Expression))
{
var targetLambda = ExtractQuotedExpression((MethodCallExpression)node.Expression);
var replaceParamsVisitor = new MultiParamReplaceVisitor(node.Arguments.ToArray(), targetLambda);
return Visit(replaceParamsVisitor.Replace());
}
return base.VisitInvocation(node);
}
这应该就能达到所有的目的了。你可以像这样使用它:
public IEnumerable<FilteredViewModel> GetFilteredQuotes()
{
Expression<Func<Quote, FilteredViewModel>> selector = q => new FilteredViewModel
{
Quote = q,
QuoteProductImages = q.QuoteProducts.SelectMany(qp => qp.QuoteProductImages.Where(qpi => ExpressionHelper.FilterQuoteProductImagesByQuote().AsQuote()(q, qpi)))
};
selector = selector.ResolveQuotes();
return _context.Context.Quotes.Select(selector);
}
当然,我认为你可以更多地提高代码的可重用性,甚至在更高的层级上定义表达式。
你可以迈出一步,定义一个 ResolveQuotes 在 IQueryable 上,并仅访问 IQueryable.Expression 并使用原始提供程序和结果表达式创建新的 IQueryable,例如:
public static IQueryable<T> ResolveQuotes<T>(this IQueryable<T> query)
{
var visitor = new ResolveQuoteVisitor();
return query.Provider.CreateQuery<T>(visitor.Visit(query.Expression));
}
通过这种方式,您可以内联表达式树的创建。甚至可以覆盖ef的默认查询提供程序,并为每个执行的查询解析引号,但可能太过分了:P
您还可以看到如何将其转换为实际上任何类似的可重用表达式树。
希望这有所帮助:)
免责声明:请记住,不要在不理解代码的情况下从任何地方复制粘贴代码到生产环境中。我没有包含太多错误处理内容,以使代码最小化。如果使用您的类的部分无法编译,我也没有检查。我对此代码的正确性不承担任何责任,但我认为说明足以理解发生了什么,并在出现任何问题时进行修复。还要记住,这仅适用于产生表达式的方法调用。我很快会撰写一篇基于这个答案的博客文章,让您在这方面使用更多的灵活性:P