如何将 LINQ 查询的一部分移动到可重用的 LINQ 表达式树
How to move parts of a LINQ query to an reusable LINQ expression tree
我想通过使用表达式树(我想这就是它的名字)使 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
异常。
我现在没主意了。感谢任何帮助。
编辑我的答案,因为我没有发现 EF 翻译问题。
使用 vanilla 代码,当你尝试抽象它时,你将始终有一个函数委托来处理(由于你的表达式将变成 Expression<Func<T>>
),这意味着查询提供者当它发现自己无法解释函数委托时会窒息。为了解决这个问题,您将不得不依赖像 LINQKit 这样的库来允许您将表达式合并在一起,或者基本上自己编写相同的内容。 (参见 this discussion)。
看来此代码的动机是实质上执行左联接并为没有匹配的行替换默认值而不是空值。我认为在那种情况下,有必要了解您要完成的任务。您很有可能最好将逻辑向上或向下移动一层。您可以将默认值逻辑移动到数据库中的视图或存储过程中以对其进行整合。您还可以通过先拉出结果集然后对内存中的对象进行查询来使用您要使用的方法,这样您就不必担心将其转换为 SQL 查询。
这个问题可以通过 LINQKit 项目的 combined expressions
解决。
操作方法如下:
将 LinqKit
引用添加到您的项目(例如通过 NuGet 包管理器)。
在 .cs 文件顶部添加以下行以使 LINQKit 的扩展方法可用
using LinqKit; // for System.Linq.Expressions.Expression<>.Invoke()
将表达式定义为静态字段
public static System.Linq.Expressions.Expression<
System.Func<%type of ra% ra, %status type%>>
GetStatusExpression = ra != null ? ... : ...;
调用查询中的表达式(确保将 .AsExpandable() 添加到第一个 table,如 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),
...
};
如果你想在"normal"代码中使用表达式,那么你需要像
一样先编译它
public static System.Func<%type of ra% ra, %status type%>
GetStatusExpressionCompiled = GetStatusExpression.Compile();
...
if (GetStatusExpressionCompiled(ra) == ...)
{
...
非常感谢 svick 用它的评论为我指明了正确的方向。
我想通过使用表达式树(我想这就是它的名字)使 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
异常。
我现在没主意了。感谢任何帮助。
编辑我的答案,因为我没有发现 EF 翻译问题。
使用 vanilla 代码,当你尝试抽象它时,你将始终有一个函数委托来处理(由于你的表达式将变成 Expression<Func<T>>
),这意味着查询提供者当它发现自己无法解释函数委托时会窒息。为了解决这个问题,您将不得不依赖像 LINQKit 这样的库来允许您将表达式合并在一起,或者基本上自己编写相同的内容。 (参见 this discussion)。
看来此代码的动机是实质上执行左联接并为没有匹配的行替换默认值而不是空值。我认为在那种情况下,有必要了解您要完成的任务。您很有可能最好将逻辑向上或向下移动一层。您可以将默认值逻辑移动到数据库中的视图或存储过程中以对其进行整合。您还可以通过先拉出结果集然后对内存中的对象进行查询来使用您要使用的方法,这样您就不必担心将其转换为 SQL 查询。
这个问题可以通过 LINQKit 项目的 combined expressions
解决。
操作方法如下:
将
LinqKit
引用添加到您的项目(例如通过 NuGet 包管理器)。在 .cs 文件顶部添加以下行以使 LINQKit 的扩展方法可用
using LinqKit; // for System.Linq.Expressions.Expression<>.Invoke()
将表达式定义为静态字段
public static System.Linq.Expressions.Expression< System.Func<%type of ra% ra, %status type%>> GetStatusExpression = ra != null ? ... : ...;
调用查询中的表达式(确保将 .AsExpandable() 添加到第一个 table,如 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), ... };
如果你想在"normal"代码中使用表达式,那么你需要像
一样先编译它 public static System.Func<%type of ra% ra, %status type%>
GetStatusExpressionCompiled = GetStatusExpression.Compile();
...
if (GetStatusExpressionCompiled(ra) == ...)
{
...
非常感谢 svick 用它的评论为我指明了正确的方向。