Linq 表达式树 - 连接值数组

Linq Expression Tree - Concatenate Array of values

我目前正在使用 .net 核心 (vnext) 解决方案,它使用 linq 和 entity framework 核心。

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

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

然后 consume/execute 它:

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

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

这就是我们目前的结果:

.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%'

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

我已经开始通过截取字符串格式表达式并构建 list/array 成员和常量表达式来编写这一层,即 名, " ", 姓, “(” 等....

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

.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 类型的列表。

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 });
}

好的,我明白了!

@usr 的评论促使我更深入地研究语法树,看看代码是如何被编译下来的。我在 "View -> Other Windows -> Syntax Visualizer"

下使用了 VS2015 内置的语法可视化工具

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

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

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

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

The binary operator Add is not defined for the types 'System.String' and 'System.String'

这让我想到了这个 article/post:

"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?也许 "params object[]"?

无论如何,将我的 GenerateStringConcat 方法更改为以下内容已经成功:

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