2016-10-01 156 views
4

我坚持EF的性能问题。实体框架组通过Sql生成

 using (var context = new CustomDbContext()) 
     { 
      var result = context. 
       TransactionLines 
       .Where(x => 
        x.Transaction.TransactionTypeId == 1433 && 
        (x.Transaction.Eob.EobBatchId == null || x.Transaction.Eob.EobBatch.Status == EobBatchStatusEnum.Completed) 
       ) 
       .GroupBy(x => x.VisitLine.ProcedureId) 
       .Select(x => new 
       { 
        Id = x.Key, 
        PaidAmount = x.Sum(t => t.PaidAmount), 
        Code = context.Procedures.Where(h => h.Id == x.Key).Select(h => h.Code).FirstOrDefault() 
       }).ToArray(); 
     } 

EF产生下一个SQL:

SELECT 
1 AS [C1], 
[Project6].[ProcedureId] AS [ProcedureId], 
[Project6].[C2] AS [C2], 
[Project6].[C1] AS [C3] 
FROM (SELECT 
    [Project5].[ProcedureId] AS [ProcedureId], 
    [Project5].[C1] AS [C1], 
    (SELECT 
     SUM([Extent7].[PaidAmount]) AS [A1] 
     FROM  [dbo].[TransactionLines] AS [Extent7] 
     INNER JOIN [dbo].[Transactions] AS [Extent8] ON [Extent7].[TransactionId] = [Extent8].[Id] 
     LEFT OUTER JOIN [dbo].[Eobs] AS [Extent9] ON [Extent8].[EobId] = [Extent9].[Id] 
     LEFT OUTER JOIN [dbo].[EobBatches] AS [Extent10] ON [Extent9].[EobBatchId] = [Extent10].[Id] 
     LEFT OUTER JOIN [dbo].[VisitLines] AS [Extent11] ON [Extent7].[VisitLineId] = [Extent11].[Id] 
     WHERE (([Extent9].[EobBatchId] IS NULL) OR (1 = [Extent10].[Status])) AND ([Extent8].[TransactionTypeId] = 1433) AND (([Project5].[ProcedureId] = [Extent11].[ProcedureId]) OR (([Project5].[ProcedureId] IS NULL) AND ([Extent11].[ProcedureId] IS NULL)))) AS [C2] 
    FROM (SELECT 
     [Project4].[ProcedureId] AS [ProcedureId], 
     [Project4].[C1] AS [C1] 
     FROM (SELECT 
      [Project2].[ProcedureId] AS [ProcedureId], 
      (SELECT TOP (1) 
       [Extent6].[Code] AS [Code] 
       FROM [dbo].[Procedures] AS [Extent6] 
       WHERE [Extent6].[Id] = [Project2].[ProcedureId]) AS [C1] 
      FROM (SELECT 
       [Distinct1].[ProcedureId] AS [ProcedureId] 
       FROM (SELECT DISTINCT 
        [Extent5].[ProcedureId] AS [ProcedureId] 
        FROM  [dbo].[TransactionLines] AS [Extent1] 
        INNER JOIN [dbo].[Transactions] AS [Extent2] ON [Extent1].[TransactionId] = [Extent2].[Id] 
        LEFT OUTER JOIN [dbo].[Eobs] AS [Extent3] ON [Extent2].[EobId] = [Extent3].[Id] 
        LEFT OUTER JOIN [dbo].[EobBatches] AS [Extent4] ON [Extent3].[EobBatchId] = [Extent4].[Id] 
        LEFT OUTER JOIN [dbo].[VisitLines] AS [Extent5] ON [Extent1].[VisitLineId] = [Extent5].[Id] 
        WHERE (([Extent3].[EobBatchId] IS NULL) OR (1 = [Extent4].[Status])) AND ([Extent2].[TransactionTypeId] = 1433) 
       ) AS [Distinct1] 
      ) AS [Project2] 
     ) AS [Project4] 
    ) AS [Project5] 
) AS [Project6] 

查询持续时间为约3秒。 如果使用Group By直接编写sql查询,则查询持续时间为1.5秒,并且使用半个CPU资源。

SELECT sq.ProcedureId, SUM(sq.PaidAmount), (SELECT TOP(1) Procedures.Code From Procedures Where Procedures.Id = sq.ProcedureId) as Code 
FROM(
    SELECT [Extent5].[ProcedureId] AS [ProcedureId],[Extent1].PaidAmount as [PaidAmount] 
    FROM  [dbo].[TransactionLines] AS [Extent1] 
    INNER JOIN [dbo].[Transactions] AS [Extent2] ON [Extent1].[TransactionId] = [Extent2].[Id] 
    LEFT OUTER JOIN [dbo].[Eobs] AS [Extent3] ON [Extent2].[EobId] = [Extent3].[Id] 
    LEFT OUTER JOIN [dbo].[EobBatches] AS [Extent4] ON [Extent3].[EobBatchId] = [Extent4].[Id] 
    LEFT OUTER JOIN [dbo].[VisitLines] AS [Extent5] ON [Extent1].[VisitLineId] = [Extent5].[Id] 
    WHERE (([Extent3].[EobBatchId] IS NULL) OR (1 = [Extent4].[Status])) AND ([Extent2].[TransactionTypeId] = 1433) 
) sq 
GROUP BY sq.ProcedureId 

我写了不同的linqs,但仍然无法强制EF生成GroupBy而不是子查询。 理想情况下,我不想使用函数或手动编写sql,因为我在构建linq逻辑中有很多条件。

是否有可能强制EF生成SQL,就像在linq中写入一样?

+1

首先尝试使用显式连接重写您的LINQ查询。 – Evk

回答

1

尝试通过包括CodeGroupBy子句中避免

context.Procedures.Where(h => h.Id == x.Key).Select(h => h.Code).FirstOrDefault() 

- 我知道这似乎是多余的,但EF是已知有涉及比使用密钥存取和骨料等一些问题,翻译对分组业务:

//... 
.GroupBy(x => new { Id = x.VisitLine.ProcedureId, x.VisitLine.Procedure.Code }) 
.Select(x => new 
{ 
    Id = x.Key.Id, 
    PaidAmount = x.Sum(t => t.PaidAmount), 
    Code = x.Key.Code 
}).ToArray(); 

更新:上述生成以下SQL在我的测试环境(最新EF6.1.3):

SELECT 
    1 AS [C1], 
    [GroupBy1].[K1] AS [ProcedureId], 
    [GroupBy1].[A1] AS [C2], 
    [GroupBy1].[K2] AS [Code] 
    FROM (SELECT 
     [Extent5].[ProcedureId] AS [K1], 
     [Extent6].[Code] AS [K2], 
     SUM([Filter1].[PaidAmount]) AS [A1] 
     FROM (SELECT [Extent1].[VisitLineId] AS [VisitLineId], [Extent1].[PaidAmount] AS [PaidAmount] 
      FROM [dbo].[TransactionLine] AS [Extent1] 
      INNER JOIN [dbo].[Transaction] AS [Extent2] ON [Extent1].[TransactionId] = [Extent2].[Id] 
      LEFT OUTER JOIN [dbo].[Eob] AS [Extent3] ON [Extent2].[EobId] = [Extent3].[Id] 
      LEFT OUTER JOIN [dbo].[EobBatch] AS [Extent4] ON [Extent3].[EobBatchId] = [Extent4].[Id] 
      WHERE (1433 = [Extent2].[TransactionTypeId]) AND ([Extent3].[EobBatchId] IS NULL OR [Extent4].[Status] = 1)) AS [Filter1] 
     LEFT OUTER JOIN [dbo].[VisitLine] AS [Extent5] ON [Filter1].[VisitLineId] = [Extent5].[Id] 
     LEFT OUTER JOIN [dbo].[Procedure] AS [Extent6] ON [Extent5].[ProcedureId] = [Extent6].[Id] 
     GROUP BY [Extent5].[ProcedureId], [Extent6].[Code] 
    ) AS [GroupBy1] 

这比我预期的要好得多。

更新2: EF是一个奇怪的野兽。使用双投影产生所期望的结果:

//... 
.GroupBy(x => x.VisitLine.ProcedureId) 
.Select(x => new 
{ 
    Id = x.Key, 
    PaidAmount = x.Sum(t => t.PaidAmount), 
}) 
.Select(x => new 
{ 
    x.Id, 
    x.PaidAmount, 
    Code = context.Procedures.Where(h => h.Id == x.Id).Select(h => h.Code).FirstOrDefault() 
}).ToArray(); 

产生以下:

SELECT 
    1 AS [C1], 
    [Project2].[ProcedureId] AS [ProcedureId], 
    [Project2].[C1] AS [C2], 
    [Project2].[C2] AS [C3] 
    FROM (SELECT 
     [GroupBy1].[A1] AS [C1], 
     [GroupBy1].[K1] AS [ProcedureId], 
     (SELECT TOP (1) 
      [Extent6].[Code] AS [Code] 
      FROM [dbo].[Procedure] AS [Extent6] 
      WHERE [Extent6].[Id] = [GroupBy1].[K1]) AS [C2] 
     FROM (SELECT 
      [Extent5].[ProcedureId] AS [K1], 
      SUM([Filter1].[PaidAmount]) AS [A1] 
      FROM (SELECT [Extent1].[VisitLineId] AS [VisitLineId], [Extent1].[PaidAmount] AS [PaidAmount] 
       FROM [dbo].[TransactionLine] AS [Extent1] 
       INNER JOIN [dbo].[Transaction] AS [Extent2] ON [Extent1].[TransactionId] = [Extent2].[Id] 
       LEFT OUTER JOIN [dbo].[Eob] AS [Extent3] ON [Extent2].[EobId] = [Extent3].[Id] 
       LEFT OUTER JOIN [dbo].[EobBatch] AS [Extent4] ON [Extent3].[EobBatchId] = [Extent4].[Id] 
       WHERE (1433 = [Extent2].[TransactionTypeId]) AND ([Extent3].[EobBatchId] IS NULL OR [Extent4].[Status] = 1)) AS [Filter1] 
      LEFT OUTER JOIN [dbo].[VisitLine] AS [Extent5] ON [Filter1].[VisitLineId] = [Extent5].[Id] 
      GROUP BY [Extent5].[ProcedureId] 
     ) AS [GroupBy1] 
    ) AS [Project2] 

P.S.如果还不清楚,你的具体问题的答案

是否有可能强制EF生成SQL,就像在linq中写入一样?

是没有。相反,您应该以某种方式编写LINQ查询以获得所需(或更近)的SQL查询。

+0

将第二列添加到group by不会强制EF生成GroupBy,它会将其他列添加到子查询中并向其中添加额外的筛选器,这会显着降低当前查询的性能。在我的例子中使用子查询对于过程代码更好 – Itan

+0

问题不在于从过程获取代码,而是使用EF生成带有相同过滤器的少数子查询,这比使用GroupBy方法效率低得多。 – Itan

+0

我不是说子查询效率低下 - 我试图使用不会导致EF生成不必要的子查询的LINQ构造。你确定建议的修改不会转化为更好的SQL吗?我无法测试,因为没有模型类。 –