我想动态创建一个select语句,该语句通过数组初始化器创建一个对象数组。这些初始化式取自所提供的属性表达式列表。
在这个例子中,我们只想列出一个名为'topic'的实体的'Component'属性。
select语句应该是这样的:
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抛出了老的
The LINQ expression 'topic' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly...
但即使从调试器中(见图),(工作)示例表达式和生成的表达式也是相同的. 我不明白的魔法发生在哪里?任何建议吗?
在调试器中生成和示例表达式
生成的表达式和示例表达式在调试器中可能看起来相同,但实际上并非如此。问题是您的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);