gpt4 book ai didi

c# - 表达式访问者仅为某些 lambda 表达式调用 VisitParameter

转载 作者:行者123 更新时间:2023-12-04 03:18:02 26 4
gpt4 key购买 nike

我希望能够使用嵌套扩展方法将 EF 中的实体投影到相应的 View 模型。 (有关我在做什么的更多详细信息,请参阅我之前的问题 Projection of single entities in EF with extension methods)。

根据这个问题,我构建了一个属性来用 lambda 替换表达式树中的扩展方法,以便能够执行此操作。它从扩展方法中获取方法参数,并在调用 VisitParameter 时替换它们(我不知道是否有办法替换 LambdaExpression 中的内联参数)。

这适用于这样的事情:

entity => new ProfileModel
{
Name = entity.Name
}

而且我可以看到表达式访问者将 LambdaExpression 上的实体参数替换为扩展方法 args 中的正确参数。

但是,当我将其更改为更嵌套的内容时,

entity => new ProfileModel
{
SomethingElses = entity.SomethingElses.AsQueryable().ToViewModels()
}

然后我得到:

The parameter 'entity' was not bound in the specified LINQ to Entities query expression.

此外,我的表达式访问者中的 VisitParameter 似乎根本没有被参数“entity”调用。

第二个 Lambda 好像根本不使用我的访问者,但我不知道为什么它会用一个而不是另一个?

如何在两种类型的 lambda 表达式的情况下正确替换参数?

下面是我的访客:

    protected override Expression VisitMethodCall(MethodCallExpression node)
{
bool expandNode = node.Method.GetCustomAttributes(typeof(ExpandableMethodAttribute), false).Any();
if (expandNode && node.Method.IsStatic)
{
object[] args = new object[node.Arguments.Count];
args[0] = _provider.CreateQuery(node.Arguments[0]);

for (int i = 1; i < node.Arguments.Count; i++)
{
Expression arg = node.Arguments[i];
args[i] = (arg.NodeType == ExpressionType.Constant) ? ((ConstantExpression)arg).Value : arg;
}
return ((IQueryable)node.Method.Invoke(null, args)).Expression;
}
var replaceNodeAttributes = node.Method.GetCustomAttributes(typeof(ReplaceInExpressionTree), false).Cast<ReplaceInExpressionTree>();
if (replaceNodeAttributes.Any() && node.Method.IsStatic)
{
var replaceWith = node.Method.DeclaringType.GetMethod(replaceNodeAttributes.First().MethodName).Invoke(null, null);
if (replaceWith is LambdaExpression)
{
RegisterReplacementParameters(node.Arguments.ToArray(), replaceWith as LambdaExpression);
return Visit((replaceWith as LambdaExpression).Body);
}
}
return base.VisitMethodCall(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
Expression replacement;
if (_replacements.TryGetValue(node, out replacement))
return Visit(replacement);
return base.VisitParameter(node);
}
private void RegisterReplacementParameters(Expression[] parameterValues, LambdaExpression expressionToVisit)
{
if (parameterValues.Length != expressionToVisit.Parameters.Count)
throw new ArgumentException(string.Format("The parameter values count ({0}) does not match the expression parameter count ({1})", parameterValues.Length, expressionToVisit.Parameters.Count));
foreach (var x in expressionToVisit.Parameters.Select((p, idx) => new { Index = idx, Parameter = p }))
{
if (_replacements.ContainsKey(x.Parameter))
{
throw new Exception("Parameter already registered, this shouldn't happen.");
}
_replacements.Add(x.Parameter, parameterValues[x.Index]);
}
}

完整的重现代码示例在这里:https://github.com/lukemcgregor/ExtensionMethodProjection

编辑:

我现在有一篇博文 ( Composable Repositories - Nesting Extensions ) 和 nuget package帮助在 linq 中嵌套扩展方法

最佳答案

首先要记住的是,在解析节点时,我们实际上是向后运行:

entity => new ProfileModel
{
SomethingElses = entity.SomethingElses.AsQueryable().ToViewModels()
}

在这里,我们处理 ToViewModels(),然后是 AsQueryable(),然后是 SomethingElses,最后是 entity .由于我们发现 entity 从未被解析(VisitParameter),这意味着我们链中的某些东西停止了树的遍历。

这里有两个罪魁祸首:

VisitMethodCall()(AsQueryable 和 ToViewModels)和 VisitMemberAccess()(SomethingElses)

我们没有覆盖 VisitMemberAccess,所以问题一定出在 VisitMethodCall

我们有该方法的三个导出点:

return ((IQueryable)node.Method.Invoke(null, args)).Expression;

return Visit((replaceWith as LambdaExpression).Body);

return base.VisitMethodCall(node);

第一行逐字返回一个表达式,并停止对树的进一步遍历。这意味着永远不会访问后代节点——正如我们所说的,工作基本上已经完成。这是否是正确的行为实际上取决于您希望与访问者达成的目标。

将代码更改为

return Visit(((IQueryable)node.Method.Invoke(null, args)).Expression);

意味着我们遍历这个(可能是新的!)表达式。这并不能保证我们会访问正确的节点(例如,这个表达式可能完全独立于原来的)——但它确实意味着如果这个新表达式包含一个参数表达式,那么将正确访问参数表达式。

关于c# - 表达式访问者仅为某些 lambda 表达式调用 VisitParameter,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39864270/

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