针对使用联接的查询的Linq2Entities CompiledQuery

本文关键字:查询 Linq2Entities CompiledQuery | 更新日期: 2023-09-27 18:29:11

我有一个查询执行得不太好,例如生成的SQL代码是次优的。

最初的声明看起来像这样(简化):

ctx.Table1.Where(t => ...)
          .OrderBy(t => ....)
          .Select(t => new {Table1 = t,
                            SomeProperty = t.Table2.SomeProperty,
                            SomeProperty2 = t.Table2.SomeProperty2,
                            AnotherProperty = t.Table3.AnotherProperty,
                            ...
                            }

我查看了SQL事件探查器,发现生成的SQL会多次加入同一个表,执行该语句大约需要1秒的时间。

然后,我将声明改写为以下内容:

from t in ctx.Table1
join t2 in ctx.Table2 on t.key equals t2.key into lt2
from t2 in lt2.DefaultIfEmpty()
join t3 in ctx.Table3 on t.key equals t3.key into lt3
from t3 in lt3.DefaultIfEmpty()
where t ...
orderby t...
select new {Table1 = t, .... }

这生成了一个更好的语句,当从SQL探查器中获取并在Management studio中执行时,它的速度是上一个示例中代码生成的语句的两倍。

然而,当运行第二个示例中的代码时,EF生成表达式所花费的时间远远超过了从查询优化中获得的时间。

那么,我该如何将第二条语句写成CompiledQuery呢。我基本上不知道如何从CompiledQuery返回匿名类型。

针对使用联接的查询的Linq2Entities CompiledQuery

我为使用CompiledQueries找到的一个变通方法是:

  1. 在使用LINQ to Entity的每个QueryX()方法之前添加一个私有InitQueryX(()方法
  2. 使用属性和反射从Init()方法调用所有InitQueryX()方法
  3. 在应用程序启动时调用Init()方法一次

这强制在一开始就编译查询,但允许以比CompiledQueries更灵活的方式编写查询。

InitQueryX()应该使用多个伪输入,以便覆盖QueryX(()方法中的所有路径(有点像单元测试代码覆盖率)。

如果可能的话,InitQueryX()的输入应该是在数据库中产生0行的mock,这样Init()方法运行所需的时间就会更少。

如果返回对象的属性不超过8个,则可以使用Tuple类。如果您有更多的属性,并且不想为这些属性声明类,则可以使用dynamic作为返回类型。