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

标签 c# entity-framework-core expression-trees

我想动态创建一个选择语句,通过数组初始值设定项创建对象数组。这些初始值设定项取自提供的属性表达式列表。

在此示例中,我们只想列出名为“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 表达式“主题”。要么以可翻译的形式重写查询,要么显式切换到客户端评估...

但即使从调试器(参见图片)来看,(工作)示例表达式和生成的表达式也是相同。我不明白的魔法在哪里发生? 有什么建议吗?

Generated and example expression in debugger

最佳答案

生成的表达式和示例表达式在调试器中可能看起来相同,但实际上并非如此。问题在于您的 lambda 表达式引用了两个 ParameterExpression 对象,它们都名为 topic:

  1. 第一个是由 C# 编译器在将 topic => topic.Component 转换为表达式时隐式创建的。
  2. 第二个 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/

相关文章:

c# - 从 Asp.net ListBox 中删除选定的项目

c# - 如何在 Internet Explorer 中将 URL 添加到受信任的区域?

c# - 暴力破解性能: Java vs C#

c# - 将字节数组转换为位图图像

c# - 表达式树是 C# 的核心语言功能吗?

c# - "dotnet has stopped working"添加数据库迁移时出现StackOverflowException

vb.net - EF Core、VB.NET 中的 LINQ 运算符 '='

c# - Include() 上的 EF Core HasQueryFilter

C++ 二进制表达式树 : How do I print an infix expression with appropriate parentheses?

c# - 构建、迭代和调用不同类型的表达式列表