我想动态创建一个选择语句,通过数组初始值设定项创建对象数组。这些初始值设定项取自提供的属性表达式列表。
在此示例中,我们只想列出名为“topic”的实体的“Component”属性。
这就是选择语句的样子:
Query.Select(topic => new object[] { topic.Component });
这是我动态创建该表达式的方法:
// an example expression to be used. We only need its body: topic.Component
Expression<Func<Topic, object>> providedExpression = topic => topic.Component;
// a list of the initializers: new object[] { expression 1, expression 2, ..}. We only use the example expression here
List<Expression> initializers = new List<Expression>() { providedExpression.Body };
// the expression: new object[] {...}
NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(object), initializers);
// the expression topic =>
var topicParam = Expression.Parameter(typeof(Topic), "topic");
// the full expression topic => new object[] { ... };
Expression<Func<Topic, object[]>> lambda = Expression.Lambda<Func<Topic, object[]>>(newArrayExpression, topicParam);
// pass the expression
Query.Select(lambda);
现在,创建的表达式看起来与上面的示例完全相同,但 EF Core 抛出了旧的错误
无法翻译 LINQ 表达式“主题”。要么以可翻译的形式重写查询,要么显式切换到客户端评估...
但即使从调试器(参见图片)来看,(工作)示例表达式和生成的表达式也是相同。我不明白的魔法在哪里发生? 有什么建议吗?
最佳答案
生成的表达式和示例表达式在调试器中可能看起来相同,但实际上并非如此。问题在于您的 lambda
表达式引用了两个 ParameterExpression 对象,它们都名为 topic
:
- 第一个是由 C# 编译器在将
topic => topic.Component
转换为表达式时隐式创建的。 - 第二个
topicParam
是显式创建的。
即使两个 ParameterExpression 对象具有相同的名称,它们也会被视为不同的参数。要修复代码,您必须确保 lambda
的参数列表和主体中使用相同 ParameterExpression 对象:
var topicParam = providedExpression.Parameters[0]; // instead of Expression.Parameter
但是,如果您提供了多个表达式,则 C# 编译器将生成多个 topic
ParameterExpression 对象,因此这个简单的修复将不起作用。相反,您需要将每个 providedExpression
中自动生成的 topic
参数替换为您显式创建的 ParameterExpression:
public class ParameterSubstituter : ExpressionVisitor
{
private readonly ParameterExpression _substituteExpression;
public ParameterSubstituter(ParameterExpression substituteExpression)
{
_substituteExpression = substituteExpression;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _substituteExpression;
}
}
在你的方法中:
var topicParam = Expression.Parameter(typeof(Topic), "topic");
List<Expression> initializers =
new List<Expression>
{
new ParameterSubstituter(topicParam).Visit(providedExpression.Body)
};
NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(object), initializers);
Expression<Func<Topic, object[]>> lambda = Expression.Lambda<Func<Topic, object[]>>(newArrayExpression, topicParam);
关于C# EF Core 使用表达式树构建动态选择语句,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71960472/