如果我理解正确,您希望在另一个表达树内重用表达式树,并且仍然允许编译器为您构建表达式树的所有魔法。
这实际上是可能的,我已经在很多场合做到了。
诀窍是将可重用部分包装在方法调用中,然后在应用查询之前将其解包。
首先,我会改变这种状况得到可重复使用的部分是一个静态方法返回你的表达方法(如MR100建议):
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<Func<Quote,QuoteProductImage, bool>> FilterQuoteProductImagesByQuote()
{
return (q,qpi) => q.User.Id == qpi.ItemOrder;
}
包装将与实现展开会发生在:
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))
{
// we cant handle here parameters, so just ignore them for now
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];
// you know this is a method call to a static method without parameters
// you can do the easiest: compile it, and then call:
// alternatively you could call the method with reflection
// or even cache the value to the method in a static dictionary, and take the expression from there (the fastest)
// the choice is up to you. as an example, i show you here the most generic solution (the first)
return (LambdaExpression)Expression.Lambda(quoteExpr).Compile().DynamicInvoke();
}
}
现在我们通过已经一半。如果你的lambda没有任何参数,以上就足够了。在你的情况下,你这样做,所以你想实际上将lambda的参数替换为原始表达式的参数。为此,我使用invoke表达式,在那里获取我想要的lambda参数。
首先让我们创建一个访问者,它将用您指定的表达式替换所有参数。
private class MultiParamReplaceVisitor : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, Expression> m_replacements;
private readonly LambdaExpression m_expressionToVisit;
public MultiParamReplaceVisitor(Expression[] parameterValues, LambdaExpression expressionToVisit)
{
// do null check
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,正确hanlde调用:
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);
}
当然我觉得你可以在这里做更多的可重用性,甚至在更高水平定义表达式。
你甚至可以更进一步,并在IQueryable的定义ResolveQuotes,只是参观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!
好吧,我印象深刻,它完美运作。这绝对是非常有用的,我会尽量让它更通用,以便在更多场合使用它。 – 2015-04-07 07:44:35