C# EF Core 构建带有表达式树的动态 select 语句
C# EF Core build dynamic select statement with expression tree
我想动态创建一个 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...
但即使在调试器中(见图),(工作)示例表达式和生成的表达式也是 相同。我不明白的魔法在哪里发生?
有什么建议吗?
Generated and example expression in debugger
生成的表达式和示例表达式在调试器中可能看起来相同,但实际上并非如此。问题是您的 lambda
表达式引用了 两个 ParameterExpression 对象,它们都命名为 topic
:
- 第一个是由 C# 编译器在将
topic => topic.Component
转换为表达式时隐式创建的。
- 第二个
topicParam
是显式创建的。
即使两个 ParameterExpression 对象具有相同的名称,它们也被视为不同的参数。要修复代码,您必须确保 相同的 ParameterExpression 对象用于 lambda
:
的参数列表和正文
var topicParam = providedExpression.Parameters[0]; // instead of Expression.Parameter
但是,如果您有多个提供的表达式,那么 C# 编译器将生成多个 topic
ParameterExpression 对象,因此这个简单的修复将不起作用。相反,您需要将每个 providedExpression
中的 auto-generated 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);
我想动态创建一个 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...
但即使在调试器中(见图),(工作)示例表达式和生成的表达式也是 相同。我不明白的魔法在哪里发生? 有什么建议吗?
Generated and example expression in debugger
生成的表达式和示例表达式在调试器中可能看起来相同,但实际上并非如此。问题是您的 lambda
表达式引用了 两个 ParameterExpression 对象,它们都命名为 topic
:
- 第一个是由 C# 编译器在将
topic => topic.Component
转换为表达式时隐式创建的。 - 第二个
topicParam
是显式创建的。
即使两个 ParameterExpression 对象具有相同的名称,它们也被视为不同的参数。要修复代码,您必须确保 相同的 ParameterExpression 对象用于 lambda
:
var topicParam = providedExpression.Parameters[0]; // instead of Expression.Parameter
但是,如果您有多个提供的表达式,那么 C# 编译器将生成多个 topic
ParameterExpression 对象,因此这个简单的修复将不起作用。相反,您需要将每个 providedExpression
中的 auto-generated 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);