Linq表达式树 - 连接值数组

时间:2016-03-10 22:50:23

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

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

我们使用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表达式编写定义,并用第二个类型替换该表达式(如字符串连接)

我已经开始通过拦截字符串格式表达式并构建成员和常量表达式的列表/数组来开始编写该层 名字, “”, 姓, “(” 等....

我尝试使用字符串concat方法循环遍历值并评估左右表达式,但最终会出现其他不起作用的内容:

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

1 个答案:

答案 0 :(得分:0)

好的,所以我想出来了!

来自@usr的评论促使我再深入研究语法树,看看代码是如何被编译下来的。我在&#34; View - &gt;下使用了VS2015中内置的Syntax Visualizer。其他Windows - &gt;语法Visualizer&#34;

当拆分所需结果的标准linq表达式时,这表明在幕后这会编译成一个看似奇怪的BinaryOperator ......

最初我尝试用:

替换我的string.concat调用
BinaryExpression.Add(concatArgs[0], concatArgs[1])

然而,这导致以下错误:

  

没有为类型&#39; System.String&#39;定义二元运算符Add。   和&#39; System.String&#39;

这引导我发表这篇文章/帖子:

"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?也许&#34; params对象[]&#34;?

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

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