【问题标题】:How to move parts of a LINQ query to an reusable LINQ expression tree如何将部分 LINQ 查询移动到可重用的 LINQ 表达式树
【发布时间】:2015-03-30 14:25:52
【问题描述】:

我想通过使用表达式树使部分 LINQ 查询可重用(我认为这就是它的调用方式)。

这是我的查询的简化版本:

var lQuery = 
  from m in ...
  join a in ... into ta
  from ra in ta.DefaultIfEmpty()
  select 
    new {
      ...
      Status = ra != null ? ... : ..., /* <- i want this to be reusable */
      ...
    };

如您所见,Status 的值是通过? : 语法确定的,该语法由 LINQ 自动翻译(我想是一个表达式树,SQL 日志最后显示了一个 CASE WHEN 查询)。

如何将该代码移动到单独的函数中,以使我的应用程序在运行时不会引发not supported 异常?

我尝试添加一个函数

public static System.Linq.Expressions.Expression<System.Func<%type of ra%, %status type%>>
  QueryStatusExpression()
{
  return ra => ra != null ? ... : ...;
}

然后像使用它一样使用它

Status = QueryStatusExpression().Invoke(ra)

但它会抛出 not supported 异常。

我现在没有想法。任何帮助表示赞赏。

【问题讨论】:

标签: c# linq expression-trees


【解决方案1】:

这个问题可以通过LINQKit项目的combined expressions来解决。

这是怎么做的:

  1. LinqKit 引用添加到您的项目(例如,通过 NuGet 包管理器)。

  2. 在您的 .cs 文件顶部添加以下行以使 LINQKit 的扩展方法可用

    using LinqKit; // for System.Linq.Expressions.Expression<>.Invoke()
    
  3. 将表达式定义为静态字段

    public static System.Linq.Expressions.Expression<
      System.Func<%type of ra% ra, %status type%>>
        GetStatusExpression = ra != null ? ... : ...;
    
  4. 在查询中调用表达式(确保将 .AsExpandable() 添加到第一个表中,如 LINQKit 文档中所述)

    var lQuery = 
      from m in %first table%.AsExpandable() ...
      join a in ... into ta
      from ra in ta.DefaultIfEmpty()
      select 
        new {
          ...
          Status = GetStatusExpression.Invoke(ra),
          ...
        };
    

如果你想在“普通”代码中使用表达式,那么你需要先编译它

 public static System.Func<%type of ra% ra, %status type%>
    GetStatusExpressionCompiled = GetStatusExpression.Compile();

 ...

 if (GetStatusExpressionCompiled(ra) == ...)
 {
   ...

非常感谢 svick 的评论为我指明了正确的方向。

【讨论】:

  • 如果我们预先定义了赋值语句的整个右侧,这可以工作,但是一旦我尝试像 Status = lStatusExpression.Invoke(ra) + "some words" 之类的东西(假设 Status 是一个字符串),我就会得到与翻译其余部分相关的错误的线。想法?
  • @DCShannon 这很可能是因为 Invoke() 编译表达式,因此您不能在之后使用 + 扩展表达式。尝试将 + 表达式添加到 lStatusExpression。
  • 我编辑了我的答案,因为在表达式中使用方法不起作用。
【解决方案2】:

编辑我的答案,因为我没有发现 EF 翻译问题。

使用 vanilla 代码,当您尝试对其进行抽象时,您将始终需要处理一个函数委托(因为您的表达式将变为 Expression&lt;Func&lt;T&gt;&gt;),这意味着查询提供程序将窒息当它发现自己无法解释函数委托时。为了解决这个问题,您将不得不依靠像 LINQKit 这样的库来允许您将表达式合并在一起,或者基本上自己编写相同的内容。 (见this discussion)。

看来,这段代码的动机本质上是执行左连接,并用默认值而不是 null 代替没有匹配的行。我认为在这种情况下,有必要了解您要完成的工作。很有可能您最好将逻辑向上或向下移动一层。您可以将默认值逻辑移动到数据库中的视图或存储过程中以对其进行整合。您还可以使用您要使用的方法,首先拉取结果集,然后对内存中的对象进行查询,这样您就不必担心将其转换为 SQL 查询。

【讨论】:

  • 查询提供者根本无法翻译这个,正如 OP 在他的问题中所说的那样,因为您正在创建一个委托,而不是 Expression
  • @Servy 啊,是的。始终是 EF 约束。
  • @moarboilerplate 是的,Servy 是对的,这不起作用,因为它会抛出 not supported 异常。
  • @moarboilerplate 感谢您的更新。正如您所提到的,动机不是替代品。 Status 是基于多个变量的复杂计算,而不仅仅是ra。为了更容易理解,我对示例进行了精简。如果我按照您指向其他讨论的链接,我会看到@fero 的相关帖子,但这似乎真的太复杂了。如果我想走那条路,我必须重新处理我已经相当大的整个查询。我很惊讶没有办法编写编译到适当的表达式树的东西......
  • 是的...有时最好先获取数据然后在内存中进行计算,或者将计算卸载到存储过程、视图、函数等中的数据库中...
猜你喜欢
  • 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
相关资源
最近更新 更多