【问题标题】:Linq Expression Tree - Concatenate Array of valuesLinq 表达式树 - 连接值数组
【发布时间】:2016-06-26 00:46:37
【问题描述】:

我目前正在使用 .net 核心 (vnext) 解决方案,该解决方案使用 linq 和实体框架核心。

我们使用 Microsoft.Linq.Translations 将计算列定义存储为 lambda 表达式,以便在需要时将它们注入到我们的语句中。计算定义的示例如下:

private static readonly CompiledExpression<Model, string> fullNameExpression =
        aTranslation<Model>.Expression(e => e.FullName, e => $"{e.FirstName} {e.LastName} ({e.Code})");

并使用/执行它:

var list = context.Set<Model>().Where(e => e.FullName.Contains("Bob")).WithTranslations();

这一切“技术上”都可以正常工作......因为正确的结果被绑定回实体模型,但是工作是在内存中完成的,而不是在 SQL 中构建查询。这是一个问题,因为我们的数据模型最终可能包含数百万行数据,并且在内存中执行此操作会很麻烦。

这就是我们目前的结果:

.Lambda #Lambda1<System.Func`2[Model,System.Boolean]>(Model $e) {
.Call (.Call System.String.Format(
    "{0} {1} ({2})",
    $e.FirstName,
    $e.LastName,
    $e.Code)).Contains("Bob")

}

就像我说的那样,这可行,但会导致任务在内存中而不是 SQL 中执行。生成的查询类似于:

SELECT FirstName, LastName, Code FROM Model

所以,如果我将计算定义更改为以下内容(注意我只是手动构建一个字符串,而不是使用 string.format:

private static readonly CompiledExpression<Model, string> fullNameExpression =
        aTranslation<Model>.Expression(e => e.FullName, e => e.FirstName + " " + e.LastName + "(" + e.Code + ")");

这会产生一个如下所示的 lambda 表达式:

.Lambda #Lambda1<System.Func`2[Model,System.Boolean]>(Model $e) {
.Call ($e.FirstName + " " + $e.LastName + " (" + $e.Code + ")").Contains("Bob")}

这将正常工作并正确构建 SQL 查询。

SELECT * FROM Model WHERE (FirstName + ' ' + LastName + '(' + Code + ')') LIKE '%bob%'

因此,除了更改计算定义的写入/定义方式之外,我正在尝试编写一个层,该层采用使用字符串格式 linq 表达式编写的定义并将该表达式替换为第二种类型(如字符串连接)

我已经开始通过截取字符串格式表达式并构建成员和常量表达式的列表/数组来编写这一层,即 名, " ", 姓, " (" 等等……

我尝试通过遍历值并评估左右表达式来使用字符串连接方法,但这最终会导致其他不起作用的东西:

.Call System.String.Concat(
.Call System.String.Concat(
    .Call System.String.Concat(
        .Call System.String.Concat(
            $e.FirstName,
            " "),
        $e.LastName),
    " ("),
$e.Code)

现在,我不知道如何转换表达式列表以生成这种表达式:

 .Lambda #Lambda1<System.Func`2[Model,System.Boolean]>(Model $e) {
.Call ($e.FirstName + " " + $e.LastName + " (" + $e.Code + ")").Contains("Bob")}

任何帮助将不胜感激。

----------------- 编辑 -------------

到目前为止尝试过的代码。为简洁起见,我省略了“concatArgs”变量的构建。它只是一个包含 MemberExpression 和 ConstantExpression 类型的 List。

Expression expr = GenerateStringConcat(concatArgs[0], concatArgs[1]);
for(int i = 2; i < concatArgs.Count; i++)
    expr = GenerateStringConcat(expr, concatArgs[i]);

被调用的方法如下所示

private Expression GenerateStringConcat(Expression left, Expression right) {
        return Expression.Call(
             null,
             typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) }),
             new[] { left, right });
}

【问题讨论】:

  • 在有效的查询中手动编写表达式。然后,反编译程序集(或在线使用 Roslyn)查看 C# 编译器生成的表达式树。这是您可以使用的模板。
  • 发布您尝试过的代码,以便我们尝试调整它。

标签: entity-framework linq linq-to-sql lambda linq-to-entities


【解决方案1】:

好的,我想通了!

@usr 的评论促使我深入研究语法树,看看代码是如何被编译下来的。我在“View -> Other Windows -> Syntax Visualizer”下使用了VS2015内置的Syntax Visualizer

分解所需结果的标准 linq 表达式时,这表明在幕后将其编译为 BinaryOperator,这看起来很奇怪...

最初我尝试将我的 string.concat 调用替换为:

BinaryExpression.Add(concatArgs[0], concatArgs[1])

但是,这导致了以下错误:

二元运算符 Add 没有为类型“System.String”定义 和'System.String'

这导致我看到这篇文章/帖子:

"The binary operator Add is not defined for the types 'System.String' and 'System.String'." -- Really?

使用这个建议的重载:

https://msdn.microsoft.com/en-us/library/bb339231%28v=vs.110%29.aspx

基本上,在幕后,这一切最终都会转换为 string.concat 调用,但是使用 BinaryExpression.Add 方法似乎允许 linq 在运行时解决这个问题,并且只使用一次对 string.concat 的调用?也许是“参数对象[]”?

无论如何,将我的 GenerateStringConcat 方法更改为以下方法就成功了:

private Expression GenerateStringConcat(Expression left, Expression right)
{
    return BinaryExpression.Add(left, right, typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) }));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多