【问题标题】:Using Expression<Func<T, TU>> within another Expression<Func<T, TU>>在另一个 Expression<Func<T, TU>> 中使用 Expression<Func<T, TU>>
【发布时间】:2019-01-11 14:22:12
【问题描述】:

我正在努力解决组合表达式的问题,环顾四周,应该可以做我想做的事情,但我很挣扎。

我有一个Expression&lt;Func&lt;class, dynamic&gt;&gt;,它定义了我最终针对我的 EF 上下文运行的选择,其中class 是我从中选择数据的表。例如:

Expression<Func<foo, dynamic>> expression = foo => new
{
   RecordExists = foo.bar.Exists,
   RecordHasPaid = foo.bar.Amount != null && foo.bar.Amount > 0
}

// run the expression against the database and get the results...
var results = context.foo.Select(expression);

我想要做的是将表达式中的通用逻辑提取到静态“帮助”样式表达式中,例如,我可以重复使用,而不是在我的第一个表达式中包含这一行

RecordHasPaid = foo.bar.Amount != null &amp;&amp; foo.bar.Amount &gt; 0

我可以有一个可重用的表达式,存储在一个可重用的类中,比如ExpressionHelper,然后我可以将其插入到我的第一个表达式中:

public static Expression<Func<bar, bool>> BarHasPaid(bar foobar)
{
   return b => b.Amount != null && b.Amount > 0;
}

变成:

Expression<Func<foo, dynamic>> expression = foo => new
{
   RecordExists = foo.bar.Exists,
   RecordHasPaid = ExpressionHelper.BarHasPaid(foo.bar)
}

// run the expression against the database and get the results...
var results = context.foo.Select(expression);

请注意,我向这个表达式传递了一个与外部表达式不同的参数(希望我可以是通用的)。

听起来不错,编译得很好,但是 - 当我开始运行 .Select 时,我得到了可怕的错误:

LINQ to Entities 无法识别该方法 'System.Linq.Expressions.Expression1[System.Func2[bar,System.Boolean]] BarHasPaid(bar)' 方法,并且该方法不能翻译成 存储表达式。

我理解发生这种情况的原因(至少我认为是这样),因为我的方法未知,因此无法翻译成 SQL,此外,我的表达式无法转换为对 EF 有意义的表达式树,因为我我本质上是在使用两个不同的表达式,它不知道如何将它们变成有意义的东西。

我尝试了各种方法,将 here 等表达式组合在一起,不幸的是,当您有两个表达式而不是一个表达式inside另一个表达式时,这似乎可以工作。

我还尝试使用 here 中提到的自定义 ExpressionVisitor 实现包装/解包(我最终确实可以正常编译,但我得到了同样的错误)

我还尝试了许多其他类似的答案,并尝试使用 LinqKit 来 Expand 我的表达式,并将我的上下文设置为 AsExpandable 无济于事。到目前为止,我的所有尝试都导致了上述错误,或者在尝试使用 LambdaExpression.Lambda 组合来编译我的组合表达式时出现这个错误:

变量 'foo' 的类型从范围 '' 引用但未定义

我认为这是被抛出的,因为在我的外部表达式 Expression&lt;Func&lt;foo, dynamic&gt;&gt; 中使用的变量 foo 在我的表达式 Expression&lt;Func&lt;bar, bool&gt;&gt; 中不存在,因为当它们组合时生成的表达式没有意义。

不幸的是,我认为我对我正在尝试做的事情的理解不够深入,所以我在下一步转向哪里时遇到了一些死胡同。任何关于这是否可能和/或如何实现的建议将不胜感激。

编辑:我使用 Rextester 创建了两个示例项目,this is the working examplethis is what causes the error - 使用这个在线工具,您实际上可以看到应该运行什么 EF,但是因为没有 EF 可以针对错误运行它并没有实际发生在该测试期间。

【问题讨论】:

  • @DannyLager,使用表达式 vistiors,并替换内部 lambda 中的参数。例如,当您编写 x =&gt; x.something 并将其与 x =&gt; x.somethingElse 组合时,仅仅因为参数命名相同并不会使它们相同,您需要重写表达式以使用相同的参数
  • 您对BarHasPaid 的定义似乎有问题 - 它有一个从未使用过的参数foobar
  • 在尝试实现这个时,我遇到了一个更大的问题——当编译器编译Expression时,它使用BarHasPaid的类型作为RecordHasPaid匿名成员的类型,即Expression&lt;&gt; 不是 boolean。在这一点上,你开始重写这么多Expression,你正在构建一个编译器。
  • @NetMage 表达式中的 func 不能包含与传入的参数相同的名称,知道 foobar 是 func 的参数并且你可以给它另一个别名就足够聪明了,这很烦人......关于 BarHasPaid 的类型作为 RecordHasPaid 的类型,我会对此进行一些挖掘

标签: c# lambda expression func linqkit


【解决方案1】:

你的代码

Expression<Func<foo, dynamic>> expression = foo => new
{ RecordExists = foo.bar.Exists,
  RecordHasPaid = ExpressionHelper.BarHasPaid(foo.bar)
}

不返回所需的结果。 RecordHasPaid 的类型是 Expression&lt;Func&lt;bar, bool&gt;&gt; 而不仅仅是 bool。显然EF对此感到困惑。

基本上,您希望在表达式主体中调用另一个表达式,而不是返回 lambda 表达式。 不幸的是,AFAIK 使用 LINQ 语法是不可能的。

但是如果你处理表达式树,你就可以完成这项工作。 看看这里: Combining two lambda expressions in c#

处理表达式树缺乏美观和类型安全性,但它是一把强大的剑。可惜编译器拒绝支持表达式树的全套特性。缺少 Lambda 调用以及循环。

【讨论】:

  • 没有(文字)lambda 调用语法,所以我不确定是否缺少。循环不是表达式,但更糟糕的是,表达式树也不支持所有现代 C# 表达式构造! (例如空条件访问)。
  • @NetMage 我很确定表达式树支持空合并运算符 。只是因为它只是语法糖,也可以用条件运算符来表达。
  • 是的,但不是 null-conditional operators 在 C# 6.0 中添加的没有 Expression 树支持。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多