LINQ和实体框架 - 避免子查询

vet*_*uri 11 c# linq sql-server entity-framework

我很难Entity Framework在我的应用程序中调优我生成的一个查询.这是非常基本的查询,但由于某种原因EF使用了多个内部子查询,这些子查询似乎执行得非常糟糕,DB而不是使用连接.

这是我的LINQ代码:

Projects.Select(proj => new ProjectViewModel()
                {
                    Name = proj.Name,
                    Id = proj.Id,
                    Total = proj.Subvalue.Where(subv =>
                        subv.Created >= startDate
                        && subv.Created <= endDate
                        &&
                        (subv.StatusId == 1 ||
                         subv.StatusId == 2))
                        .Select(c => c.SubValueSum)
                        .DefaultIfEmpty()
                        .Sum()
                })
                .OrderByDescending(c => c.Total)
                .Take(10);
Run Code Online (Sandbox Code Playgroud)

EF生成具有多个子查询的非常复杂的查询,这些子查询具有如下可怕的查询性能:

SELECT TOP (10) 
[Project3].[Id] AS [Id], 
[Project3].[Name] AS [Name], 
[Project3].[C1] AS [C1]
FROM ( SELECT 
    [Project2].[Id] AS [Id], 
    [Project2].[Name] AS [Name], 
    [Project2].[C1] AS [C1]
    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Name] AS [Name], 
        (SELECT 
            SUM([Join1].[A1]) AS [A1]
            FROM ( SELECT 
                CASE WHEN ([Project1].[C1] IS NULL) THEN cast(0 as decimal(18)) ELSE [Project1].[SubValueSum] END AS [A1]
                FROM   ( SELECT 1 AS X ) AS [SingleRowTable1]
                LEFT OUTER JOIN  (SELECT 
                    [Extent2].[SubValueSum] AS [SubValueSum], 
                    cast(1 as tinyint) AS [C1]
                    FROM [dbo].[Subvalue] AS [Extent2]
                    WHERE ([Extent1].[Id] = [Extent2].[Id]) AND ([Extent2].[Created] >= '2015-08-01') AND ([Extent2].[Created] <= '2015-10-01') AND ([Extent2].[StatusId] IN (1,2)) ) AS [Project1] ON 1 = 1
            )  AS [Join1]) AS [C1]
        FROM [dbo].[Project] AS [Extent1]
        WHERE ([Extent1].[ProjectCountryId] = 77) AND ([Extent1].[Active] = 1)
    )  AS [Project2]
)  AS [Project3]
ORDER BY [Project3].[C1] DESC;
Run Code Online (Sandbox Code Playgroud)

EF生成的查询的执行时间是~10 seconds.但是当我手写这样的查询时:

select 
    TOP (10)
    Proj.Id,
    Proj.Name,
    SUM(Subv.SubValueSum) AS Total
from 
    SubValue as Subv
left join
    Project as Proj on Proj.Id = Subv.ProjectId
where
    Subv.Created > '2015-08-01' AND Subv.Created <= '2015-10-01' AND Subv.StatusId IN (1,2)
group by
    Proj.Id,
    Proj.Name
order by 
    Total DESC
Run Code Online (Sandbox Code Playgroud)

执行时间接近即时; 下面30ms.

问题显然在于我能够EF用LINQ 编写好的查询,但无论我尝试做什么(Linqpad用于测试)我都无法编写类似的高性能查询,LINQ\EF因为我可以手工编写.我已经查询了SubValue表和Project表,但最终结果大致相同:多个无效的嵌套子查询而不是单个连接执行工作.

如何编写一个模仿SQL上面显示的手写的查询?如何控制生成的实际查询EF?最重要的是:当我想要而不是嵌套的子查询时,我怎样才能获得Linq2SQLEntity Framework使用Joins.

Mar*_*age 5

EF从您提供的LINQ表达式生成SQL,您不能指望此转换完全解析您放入表达式的任何内容的结构以优化它.在您的情况下,您创建了一个表达式树,每个项目将使用导航属性来汇总与项目相关的一些子值.这会导致您发现的嵌套子查询.

要改进生成的SQL,您需要避免在对子值执行所有操作之前从项目导航到子值,并且可以通过创建连接(这也是您在手工制作的SQL中执行的操作)来执行此操作:

var query = from proj in context.Project
            join s in context.SubValue.Where(s => s.Created >= startDate && s.Created <= endDate && (s.StatusId == 1 || s.StatusId == 2)) on proj.Id equals s.ProjectId into s2
            from subv in s2.DefaultIfEmpty()
            select new { proj, subv } into x
            group x by new { x.proj.Id, x.proj.Name } into g
            select new {
              g.Key.Id,
              g.Key.Name,
              Total = g.Select(y => y.subv.SubValueSum).Sum()
            } into y
            orderby y.Total descending
            select y;
var result = query.Take(10);
Run Code Online (Sandbox Code Playgroud)

基本思想是在受where子句限制的子值上加入项目.要执行左连接,您需要DefaultIfEmpty()但您已经知道了.

然后将连接的值(x)分组,并SubValueSum在每个组中执行求和.

最后,订购并TOP(10)应用.

生成的SQL仍然包含子查询,但我希望它与查询生成的SQL相比更高效:

SELECT TOP (10)
    [Project1].[Id] AS [Id],
    [Project1].[Name] AS [Name],
    [Project1].[C1] AS [C1]
    FROM ( SELECT
        [GroupBy1].[A1] AS [C1],
        [GroupBy1].[K1] AS [Id],
        [GroupBy1].[K2] AS [Name]
        FROM ( SELECT
            [Extent1].[Id] AS [K1],
            [Extent1].[Name] AS [K2],
            SUM([Extent2].[SubValueSum]) AS [A1]
            FROM  [dbo].[Project] AS [Extent1]
            LEFT OUTER JOIN [dbo].[SubValue] AS [Extent2] ON ([Extent2].[Created] >= @p__linq__0) AND ([Extent2].[Created] <= @p__linq__1) AND ([Extent2].[StatusId] IN (1,2)) AND ([Extent1].[Id] = [Extent2].[ProjectId])
            GROUP BY [Extent1].[Id], [Extent1].[Name]
        )  AS [GroupBy1]
    )  AS [Project1]
    ORDER BY [Project1].[C1] DESC
Run Code Online (Sandbox Code Playgroud)