Linq 2 SQL查询优化

时间:2014-12-12 11:18:18

标签: c# linq linq-to-sql

我遇到了一个linq to sql查询的真正问题,很可能是因为我的skilz有点缺乏,但如果有人可以看一看,我会很感激,看看我如何优化查询作为SQL正在生成的是可怕的

查询正在进行的操作是尝试获取销售明细表上加入位置的库存概览,以了解上次销售的时间以及基于部门ID的该位置的库存数量(即部门中的所有产品)因此Max()和Sum()

var query1 = (
            from sl in slRepo
            where sl.Product.bsDepartmentId < 90 && sl.Product.SubDepartment.Department.Id == departmentId
            group sl by sl.Location into gbsl
            select new
            {
                ProductId = gbsl.FirstOrDefault().Product.Id,
                LocationId = gbsl.FirstOrDefault().Location.Id,
                Location = gbsl.FirstOrDefault().Location.Name,
                Quantity = gbsl.Sum(x => x.CurrentStock),
                LastInvoice = gbsl.Max(x => x.LastInvoice)
            });

var query2 = (
            from sd in sdRepo
            where sd.Product.SubDepartment.Department.Id == departmentId
            group sd by sd.Location.Id into g
            select new
            {
                LocationId = g.FirstOrDefault().Location.Id,
                LastSale = g.Max(x => x.TransactionDate)
            });

var query3 = (
            from q1 in query1
            join q2 in query2 on q1.LocationId equals q2.LocationId into temp
            from j in temp.DefaultIfEmpty()
            select new XStockOverviewDto
            {
                Location = q1.Location,
                Quantity = q1.Quantity,
                LastInvoice = q1.LastInvoice,
                LastSale = j.LastSale
            });

        return query3;

然后从中生成的SQL就是...... 这似乎是使用相同的数据集分别查询MAX和SUM时,它应该一起完成,使查询变得复杂,实际查询需要很长时间

SELECT 
1 AS [C1], 
[Project10].[Name] AS [Name], 
[Project10].[C2] AS [C2], 
[Project10].[C3] AS [C3], 
 CAST( [Project16].[C2] AS datetime2) AS [C4]
FROM   (SELECT 
    [Project9].[Name] AS [Name], 
    [Project9].[C1] AS [C1], 
    [Project9].[C2] AS [C2], 
    (SELECT 
        MAX([Filter9].[LastInvoice]) AS [A1]
        FROM ( SELECT [Extent18].[LastInvoice] AS [LastInvoice], [Extent20].[Department_Id] AS [Department_Id], [Extent21].[Id] AS [Id1]
            FROM    [dbo].[XStockLevels] AS [Extent18]
            INNER JOIN [dbo].[XProducts] AS [Extent19] ON [Extent18].[Product_Id] = [Extent19].[Id]
            LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent20] ON [Extent19].[SubDepartment_Id] = [Extent20].[Id]
            LEFT OUTER JOIN [dbo].[XLocations] AS [Extent21] ON [Extent18].[Location_Id] = [Extent21].[Id]
            WHERE [Extent19].[bsDepartmentId] < 90
        )  AS [Filter9]
        WHERE ([Filter9].[Department_Id] = @p__linq__0) AND (([Project9].[Id] = [Filter9].[Id1]) OR (([Project9].[Id] IS NULL) AND ([Filter9].[Id1] IS NULL)))) AS [C3]
    FROM ( SELECT 
        [Project8].[Id] AS [Id], 
        [Project8].[Name] AS [Name], 
        [Project8].[C1] AS [C1], 
        (SELECT 
            SUM([Filter7].[CurrentStock]) AS [A1]
            FROM ( SELECT [Extent14].[CurrentStock] AS [CurrentStock], [Extent16].[Department_Id] AS [Department_Id], [Extent17].[Id] AS [Id2]
                FROM    [dbo].[XStockLevels] AS [Extent14]
                INNER JOIN [dbo].[XProducts] AS [Extent15] ON [Extent14].[Product_Id] = [Extent15].[Id]
                LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent16] ON [Extent15].[SubDepartment_Id] = [Extent16].[Id]
                LEFT OUTER JOIN [dbo].[XLocations] AS [Extent17] ON [Extent14].[Location_Id] = [Extent17].[Id]
                WHERE [Extent15].[bsDepartmentId] < 90
            )  AS [Filter7]
            WHERE ([Filter7].[Department_Id] = @p__linq__0) AND (([Project8].[Id] = [Filter7].[Id2]) OR (([Project8].[Id] IS NULL) AND ([Filter7].[Id2] IS NULL)))) AS [C2]
        FROM ( SELECT 
            [Project7].[Id] AS [Id], 
            [Extent13].[Name] AS [Name], 
            [Project7].[C1] AS [C1]
            FROM   (SELECT 
                [Project5].[Id] AS [Id], 
                [Project5].[C1] AS [C1], 
                (SELECT TOP (1) 
                    [Filter5].[Location_Id] AS [Location_Id]
                    FROM ( SELECT [Extent9].[Location_Id] AS [Location_Id], [Extent11].[Department_Id] AS [Department_Id], [Extent12].[Id] AS [Id3]
                        FROM    [dbo].[XStockLevels] AS [Extent9]
                        INNER JOIN [dbo].[XProducts] AS [Extent10] ON [Extent9].[Product_Id] = [Extent10].[Id]
                        LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent11] ON [Extent10].[SubDepartment_Id] = [Extent11].[Id]
                        LEFT OUTER JOIN [dbo].[XLocations] AS [Extent12] ON [Extent9].[Location_Id] = [Extent12].[Id]
                        WHERE [Extent10].[bsDepartmentId] < 90
                    )  AS [Filter5]
                    WHERE ([Filter5].[Department_Id] = @p__linq__0) AND (([Project5].[Id] = [Filter5].[Id3]) OR (([Project5].[Id] IS NULL) AND ([Filter5].[Id3] IS NULL)))) AS [C2]
                FROM ( SELECT 
                    [Project4].[Id] AS [Id], 
                    [Project4].[C1] AS [C1]
                    FROM ( SELECT 
                        [Project2].[Id] AS [Id], 
                        (SELECT TOP (1) 
                            [Filter3].[Location_Id] AS [Location_Id]
                            FROM ( SELECT [Extent5].[Location_Id] AS [Location_Id], [Extent7].[Department_Id] AS [Department_Id], [Extent8].[Id] AS [Id4]
                                FROM    [dbo].[XStockLevels] AS [Extent5]
                                INNER JOIN [dbo].[XProducts] AS [Extent6] ON [Extent5].[Product_Id] = [Extent6].[Id]
                                LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent7] ON [Extent6].[SubDepartment_Id] = [Extent7].[Id]
                                LEFT OUTER JOIN [dbo].[XLocations] AS [Extent8] ON [Extent5].[Location_Id] = [Extent8].[Id]
                                WHERE [Extent6].[bsDepartmentId] < 90
                            )  AS [Filter3]
                            WHERE ([Filter3].[Department_Id] = @p__linq__0) AND (([Project2].[Id] = [Filter3].[Id4]) OR (([Project2].[Id] IS NULL) AND ([Filter3].[Id4] IS NULL)))) AS [C1]
                        FROM ( SELECT 
                            [Distinct1].[Id] AS [Id]
                            FROM ( SELECT DISTINCT 
                                [Filter1].[Id5] AS [Id], 
                                [Filter1].[bsLocationId] AS [bsLocationId], 
                                [Filter1].[Name] AS [Name], 
                                [Filter1].[Code1] AS [Code1], 
                                [Filter1].[Code2] AS [Code2], 
                                [Filter1].[Code3] AS [Code3], 
                                [Filter1].[Code4] AS [Code4], 
                                [Filter1].[Code5] AS [Code5], 
                                [Filter1].[Code6] AS [Code6], 
                                [Filter1].[Code7] AS [Code7], 
                                [Filter1].[Code8] AS [Code8], 
                                [Filter1].[bsCompanyId] AS [bsCompanyId], 
                                [Filter1].[Group] AS [Group]
                                FROM ( SELECT [Extent3].[Department_Id] AS [Department_Id], [Extent4].[Id] AS [Id5], [Extent4].[bsLocationId] AS [bsLocationId], [Extent4].[Name] AS [Name], [Extent4].[Code1] AS [Code1], [Extent4].[Code2] AS [Code2], [Extent4].[Code3] AS [Code3], [Extent4].[Code4] AS [Code4], [Extent4].[Code5] AS [Code5], [Extent4].[Code6] AS [Code6], [Extent4].[Code7] AS [Code7], [Extent4].[Code8] AS [Code8], [Extent4].[bsCompanyId] AS [bsCompanyId], [Extent4].[Group] AS [Group]
                                    FROM    [dbo].[XStockLevels] AS [Extent1]
                                    INNER JOIN [dbo].[XProducts] AS [Extent2] ON [Extent1].[Product_Id] = [Extent2].[Id]
                                    LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent3] ON [Extent2].[SubDepartment_Id] = [Extent3].[Id]
                                    LEFT OUTER JOIN [dbo].[XLocations] AS [Extent4] ON [Extent1].[Location_Id] = [Extent4].[Id]
                                    WHERE [Extent2].[bsDepartmentId] < 90
                                )  AS [Filter1]
                                WHERE [Filter1].[Department_Id] = @p__linq__0
                            )  AS [Distinct1]
                        )  AS [Project2]
                    )  AS [Project4]
                )  AS [Project5] ) AS [Project7]
            LEFT OUTER JOIN [dbo].[XLocations] AS [Extent13] ON [Project7].[C2] = [Extent13].[Id]
        )  AS [Project8]
    )  AS [Project9] ) AS [Project10]
LEFT OUTER JOIN  (SELECT 
    [Project15].[C1] AS [C1], 
    (SELECT 
        MAX([Extent28].[TransactionDate]) AS [A1]
        FROM   [dbo].[XSalesDetails] AS [Extent28]
        LEFT OUTER JOIN [dbo].[XProducts] AS [Extent29] ON [Extent28].[Product_Id] = [Extent29].[Id]
        INNER JOIN [dbo].[XSubDepartments] AS [Extent30] ON [Extent29].[SubDepartment_Id] = [Extent30].[Id]
        WHERE ([Extent30].[Department_Id] = @p__linq__1) AND (([Project15].[Location_Id] = [Extent28].[Location_Id]) OR (([Project15].[Location_Id] IS NULL) AND ([Extent28].[Location_Id] IS NULL)))) AS [C2]
    FROM ( SELECT 
        [Project14].[Location_Id] AS [Location_Id], 
        [Project14].[C1] AS [C1]
        FROM ( SELECT 
            [Project12].[Location_Id] AS [Location_Id], 
            (SELECT TOP (1) 
                [Extent25].[Location_Id] AS [Location_Id]
                FROM   [dbo].[XSalesDetails] AS [Extent25]
                LEFT OUTER JOIN [dbo].[XProducts] AS [Extent26] ON [Extent25].[Product_Id] = [Extent26].[Id]
                INNER JOIN [dbo].[XSubDepartments] AS [Extent27] ON [Extent26].[SubDepartment_Id] = [Extent27].[Id]
                WHERE ([Extent27].[Department_Id] = @p__linq__1) AND (([Project12].[Location_Id] = [Extent25].[Location_Id]) OR (([Project12].[Location_Id] IS NULL) AND ([Extent25].[Location_Id] IS NULL)))) AS [C1]
            FROM ( SELECT 
                @p__linq__1 AS [p__linq__1], 
                [Distinct2].[Location_Id] AS [Location_Id]
                FROM ( SELECT DISTINCT 
                    [Extent22].[Location_Id] AS [Location_Id]
                    FROM   [dbo].[XSalesDetails] AS [Extent22]
                    LEFT OUTER JOIN [dbo].[XProducts] AS [Extent23] ON [Extent22].[Product_Id] = [Extent23].[Id]
                    INNER JOIN [dbo].[XSubDepartments] AS [Extent24] ON [Extent23].[SubDepartment_Id] = [Extent24].[Id]
                    WHERE [Extent24].[Department_Id] = @p__linq__1
                )  AS [Distinct2]
            )  AS [Project12]
        )  AS [Project14]
    )  AS [Project15] ) AS [Project16] ON ([Project10].[C1] = [Project16].[C1]) OR (([Project10].[C1] IS NULL) AND ([Project16].[C1] IS NULL))

我实际上尝试转换为链接的查询是

DECLARE @DeptId INT = 1;

SELECT Location_Id, XLocations.Name, CurrentStock, LastInvoice, LastSale
FROM
(SELECT Location_Id, SUM(CurrentStock) AS CurrentStock, MAX(LastInvoice) AS LastInvoice, MAX(LastSale) AS LastSale
   FROM XStockLevels
LEFT JOIN 
(SELECT XProducts.bsDepartmentId, XProducts.bsSubDepartmentId, XProducts.bsItemId, XProducts.Id, LastSale
   FROM XProducts 
  INNER JOIN XSubDepartments ON XProducts.SubDepartment_Id = XSubDepartments.Id
   LEFT JOIN (SELECT Product_Id, MAX(TransactionDate) AS LastSale FROM XSalesDetails GROUP BY Product_Id) XSD ON XSD.Product_Id = XProducts.Id
  WHERE XProducts.bsDepartmentId<90 AND XSubDepartments.Department_Id = @DeptId) XP
ON XStockLevels.Product_Id = XP.Id
GROUP BY Location_Id) Z 
INNER JOIN XLocations ON Z.Location_Id = XLocations.Id

所以我认为有一些真正的优化需要发生,我真的似乎无法弄清楚我在linq查询中做错了什么。

干杯 乔。

4 个答案:

答案 0 :(得分:1)

是的,当你的查询变得复杂时,linq to sql和其他类似的生成器往往会失败。

您的选择是手动绕过linq执行查询或在数据库上创建函数并执行linq形式。

答案 1 :(得分:1)

SQL Server查询优化器通常会对其进行排序。

首先通过将“生成的”SQL放入SQL管理工作室并询问查询计划来查看生成的查询计划。如果您不习惯,查询计划将需要一些理解,但值得学习这项技能。

然后,如果您仍然发现生成的SQL存在问题,请检查您是否有正确的索引等。

然后,如果所有其他方法都失败了,您需要创建一个存储过程。或者对数据库执行多个Ling to SQL查询,并将结果合并到C#中。

答案 2 :(得分:1)

要改进的第一件事是替换所有表达式,如...

gbsl.FirstOrDefault().Location.Id

...通过

gbsl.Key.Id

因为它具有相同的效果,但FirstOrDefault()被翻译成非常昂贵的SQL。

此外,在第一个LINQ查询中,您应该从投影中删除ProductIdLocation属性,因为它们不会在任何地方使用。

我很确定如果你做了这些事情,你会更接近你最终获得的查询,并且它更具可读性。

稍微偏离主题(因为它在这里没有发挥作用,但是经过微小的改变之后)可能是LINQ-to-SQL在分组时非常效率低下。如果您执行“错误”(请参阅​​下文),它可能会执行GROUP BY查询,然后查询每个组的 以填充这些组。

这是因为在SQL中,group by是“破坏性的”:它不会从查询表中返回完整记录。如果你这样做

SELECT a, SUM(b)
FROM   tableA
GROUP BY a

您将获得一个由两个值组成的结果集,而不是tableA中的其他值。但是,在LINQ中,您通常不仅对聚合感兴趣,而且您可能只想通过某些属性对完整实体进行分组,例如:在这个简单示例中:

from a in As
group a by a.Type into g
select new { Type = g.Key, As = g }

这将首先执行GROUP BY a.Type查询,然后执行尽可能多的查询以获取每个组的实体。对于这些类型的分组,首先获取数据(一个查询)并在内存中进行分组几乎总是更好。

(这是Entity Framework击败LINQ-to-SQL wrt查询生成的罕见区域之一)

答案 3 :(得分:0)

前两个答案实际上并未回答我的问题......但感谢回复

这是我最终做的事情。

获取LINQPad

获得Linqer试用版(我认为完整版约为30英镑)

在Linqer中反编译SQL查询,并查看代码(它非常混乱,需要大量清理,但它给了我一些工作) - 将代码复制到LINQPad并开始调整代码以查看更好地检查SQL和编译器。

现在我的代码如下:

    var query5 = (
        from sl in (
            (from xsl in slRepo
                where
                    xsl.Product.bsDepartmentId < 90 &&
                    xsl.Product.SubDepartment.Department.Id == 1
                group xsl by new
                {
                    xsl.Location.Id
                }
                into g
                select new
                {
                    g.Key,
                    LocationId = g.Key.Id,
                    CurrentStock = (int?) g.Sum(p => p.CurrentStock),
                    LastInvoice = g.Max(p => p.LastInvoice)
                }))
        join ls in (
            (from xsd in sdRepo
                join xp in prodRepo on xsd.Product.Id equals xp.Id into xpJoin
                from xp in xpJoin.DefaultIfEmpty()
                join xs in subDepRepo on xp.SubDepartment.Id equals xs.Id into xsJoin
                from xs in xsJoin.DefaultIfEmpty()
                where
                    xp.bsDepartmentId < 90 &&
                    xs.Department.Id == 1
                group xsd by new
                {
                    xsd.Location.Id
                }
                into g
                select new
                {
                    g.Key,
                    LastSale = (DateTime?)g.Max(p => p.TransactionDate)
                })) on sl.Key equals ls.Key into lsJoin
        from ls in lsJoin.DefaultIfEmpty()
        join l in locRepo on sl.LocationId equals l.Id
        select new XStockOverviewDto
        {
            Location = l.Name,
            Quantity = sl.CurrentStock,
            LastInvoice = sl.LastInvoice,
            LastSale = ls.LastSale
        });

并且不要误会我的意思,它不是可读的,但我相信这可以进一步分解并清理更易读

但SQL现在看起来像这样,运行速度比注册时间

更快
SELECT 
    1 AS [C1], 
    [Extent7].[Name] AS [Name], 
    [GroupBy1].[A1] AS [C2], 
    [GroupBy1].[A2] AS [C3], 
    CASE WHEN ([Project1].[C2] IS NULL) THEN CAST(NULL AS datetime2) ELSE  CAST( [Project1].[C1] AS datetime2) END AS [C4]
    FROM    (SELECT 
        [Filter1].[Location_Id] AS [K1], 
        SUM([Filter1].[CurrentStock]) AS [A1], 
        MAX([Filter1].[LastInvoice]) AS [A2]
        FROM ( SELECT [Extent1].[CurrentStock] AS [CurrentStock], [Extent1].[LastInvoice] AS [LastInvoice], [Extent1].[Location_Id] AS [Location_Id], [Extent3].[Department_Id] AS [Department_Id]
            FROM   [dbo].[XStockLevels] AS [Extent1]
            INNER JOIN [dbo].[XProducts] AS [Extent2] ON [Extent1].[Product_Id] = [Extent2].[Id]
            INNER JOIN [dbo].[XSubDepartments] AS [Extent3] ON [Extent2].[SubDepartment_Id] = [Extent3].[Id]
            WHERE [Extent2].[bsDepartmentId] < 90
        )  AS [Filter1]
        WHERE 1 = [Filter1].[Department_Id]
        GROUP BY [Filter1].[Location_Id] ) AS [GroupBy1]
    LEFT OUTER JOIN  (SELECT 
        [GroupBy2].[K1] AS [Location_Id], 
        [GroupBy2].[A1] AS [C1], 
        1 AS [C2]
        FROM ( SELECT 
            [Filter3].[Location_Id] AS [K1], 
            MAX([Filter3].[TransactionDate]) AS [A1]
            FROM ( SELECT [Extent4].[TransactionDate] AS [TransactionDate], [Extent4].[Location_Id] AS [Location_Id], [Extent6].[Department_Id] AS [Department_Id]
                FROM   [dbo].[XSalesDetails] AS [Extent4]
                INNER JOIN [dbo].[XProducts] AS [Extent5] ON [Extent4].[Product_Id] = [Extent5].[Id]
                INNER JOIN [dbo].[XSubDepartments] AS [Extent6] ON [Extent5].[SubDepartment_Id] = [Extent6].[Id]
                WHERE [Extent5].[bsDepartmentId] < 90
            )  AS [Filter3]
            WHERE 1 = [Filter3].[Department_Id]
            GROUP BY [Filter3].[Location_Id]
        )  AS [GroupBy2] ) AS [Project1] ON ([GroupBy1].[K1] = [Project1].[Location_Id]) OR (([GroupBy1].[K1] IS NULL) AND ([Project1].[Location_Id] IS NULL))
    INNER JOIN [dbo].[XLocations] AS [Extent7] ON [GroupBy1].[K1] = [Extent7].[Id]

由于 乔。