gpt4 book ai didi

c# - 将表达式参数作为参数传递给另一个表达式

转载 作者:太空狗 更新时间:2023-10-29 18:24:44 24 4
gpt4 key购买 nike

我有一个过滤结果的查询:

public IEnumerable<FilteredViewModel> GetFilteredQuotes()
{
return _context.Context.Quotes.Select(q => new FilteredViewModel
{
Quote = q,
QuoteProductImages = q.QuoteProducts.SelectMany(qp => qp.QuoteProductImages.Where(qpi => q.User.Id == qpi.ItemOrder))
});
}

在 where 子句中,我使用参数 q 将属性与参数 qpi 中的属性进行匹配。因为过滤器将在多个地方使用,所以我试图将 where 子句重写为一个表达式树,它看起来像这样:

public IEnumerable<FilteredViewModel> GetFilteredQuotes()
{
return _context.Context.Quotes.Select(q => new FilteredViewModel
{
Quote = q,
QuoteProductImages = q.QuoteProducts.SelectMany(qp => qp.QuoteProductImages.AsQueryable().Where(ExpressionHelper.FilterQuoteProductImagesByQuote(q)))
});
}

在此查询中,参数 q 用作函数的参数:

public static Expression<Func<QuoteProductImage, bool>> FilterQuoteProductImagesByQuote(Quote quote)
{
// Match the QuoteProductImage's ItemOrder to the Quote's Id
}

我将如何实现这个功能?或者我应该使用不同的方法吗?

最佳答案

如果我理解正确的话,你想在另一个表达式树中重用一个表达式树,并且仍然允许编译器为你完成构建表达式树的所有魔法。

这其实是可以的,我也做过很多次。

诀窍是将可重用部分包装在方法调用中,然后在应用查询之前将其解包。

首先,我会将获取可重用部分的方法更改为返回表达式的静态方法(如 mr100 所建议的那样):

 public static Expression<Func<Quote,QuoteProductImage, bool>> FilterQuoteProductImagesByQuote()
{
return (q,qpi) => q.User.Id == qpi.ItemOrder;
}

包装将完成:

  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))
{
// 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,并正确处理调用:

        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

您还可以看到这将如何转化为任何类似的可重用表达式树。

希望对您有所帮助:)

免责声明:切记,切勿在不了解其作用的情况下将粘贴代码从任何地方复制到生产环境中。我没有在此处包含太多错误处理,以将代码保持在最低限度。我也没有检查使用你的类的部分是否可以编译。我也不对这段代码的正确性承担任何责任,但我认为解释应该足够了,以了解正在发生的事情,并在有任何问题时修复它。还要记住,这仅适用于当您有一个生成表达式的方法调用时的情况。我很快会根据这个答案写一篇博文,让您也可以在那里使用更多的灵 active :P

关于c# - 将表达式参数作为参数传递给另一个表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29448432/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com