c# EF Core用表达式树构建动态选择语句



我想动态创建一个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:

  1. 第一个是c#编译器在将topic => topic.Component转换为表达式时隐式创建的。
  2. 第二个topicParam是显式创建的。

尽管两个ParameterExpression对象具有相同的名称,但它们被视为不同的参数。要修复代码,必须确保在lambda的参数列表和主体中同时使用相同的ParameterExpression对象:

var topicParam = providedExpression.Parameters[0]; // instead of Expression.Parameter

然而,如果你有多个提供的表达式,那么c#编译器将生成多个topicParameterExpression对象,所以这个简单的修复将不起作用。相反,您需要将每个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);

最新更新