EF Core 2.1在子查询和分组后聚合时进行本地评估

时间:2018-07-13 09:30:26

标签: c# sql-server linq entity-framework-core

因此,EF Core 2.1在SQL服务器上(使用SQL提供程序时)评估GroupBy LINQ表达式。

这太棒了 当查询变得更加复杂时,我遇到了一个问题。

这些查询使用的模型是:

public class Invoice
{
    public string Status {get; set;}
    public string InvoiceType {get; set;}
    public decimal InvoicePayments {get; set;}
    public decimal EligibleValue {get; set;}
}

此LINQ语句在SQL Server中完全运行:

data
    .GroupBy(i => new { i.Status, i.InvoiceType })
    .Select(i => new 
    {
        i.Key, 
        Count = i.Count(), 
        Total = i.Sum(x => x.EligibleValue)
    });

并生成以下SQL

SELECT 
    [i].[Status], 
    [i].[InvoiceType], 
    COUNT(*) AS [Count], 
    SUM([i].[EligibleValue]) AS [Col1]
FROM [Invoice] AS [i]
GROUP BY [i].[Status], [i].[InvoiceType]

此LINQ语句有效,但在内存中执行GroupBy

data
    .GroupBy(i => new { i.Status, i.InvoiceType })
    .Select(i => new 
    { 
        i.Key, 
        Count = i.Count(), 
        TotalLessThan100 = i.Where(x => x.InvoicePayments < 100).Sum(y => y.EligibleValue),
        TotalLessThan500 = i.Where(x => x.InvoicePayments < 500).Sum(z => z.EligibleValue)
    });

我在“输出”窗口中收到一些警告:

    The LINQ expression 'GroupBy(new <>f__AnonymousType0`2(Status = [i].Status, InvoiceType = [i].InvoiceType), [i])' could not be translated and will be evaluated locally.

The LINQ expression 'Count()' could not be translated and will be evaluated locally.

The LINQ expression 'where ([x].InvoicePayments < 100)' could not be translated and will be evaluated locally.

The LINQ expression 'where ([x].InvoicePayments < 500)' could not be translated and will be evaluated locally.

The LINQ expression 'Sum()' could not be translated and will be evaluated locally.

生成的SQL没有GroupBy,只有初始查询。

有什么方法可以定义此查询以在SQL Server上完全执行?

2 个答案:

答案 0 :(得分:8)

要遵循的第一条规则是避免在Where结果上使用Count和谓词版本GroupBy,并在可能的情况下使用条件Sum。 EF6能够翻译这种结构,但是SQL效率很低。

因此,通常您需要这样重写查询:

data
    .GroupBy(i => new { i.Status, i.InvoiceType })
    .Select(g => new
    {
        g.Key,
        Count = g.Count(),
        TotalLessThan100 = g.Sum(i => i.InvoicePayments < 100 ? i.EligibleValue : 0),
        TotalLessThan500 = g.Sum(i => i.InvoicePayments < 500 ? i.EligibleValue : 0)
    });

但是,EF Core 2.1 GroupBy的翻译改进不包含Sum,而不仅仅是简单的属性选择器,因此上述内容仍使用客户端评估。它很可能会在将来的某些发行版中得到修复,但是在此之前,可以使用以下技巧-在Select之前添加中间投影(GroupBy),其中包含稍后需要的所有字段,包括计算所得的字段,然后在GroupBy之后将它们用于聚合:

data
    .Select(i => new
    {
        i.Status,
        i.InvoiceType,
        LessThan100 = i.InvoicePayments < 100 ? i.EligibleValue : 0,
        LessThan500 = i.InvoicePayments < 500 ? i.EligibleValue : 0,
    })
    .GroupBy(i => new { i.Status, i.InvoiceType })
    .Select(g => new
    {
        g.Key,
        Count = g.Count(),
        TotalLessThan100 = g.Sum(i => i.LessThan100),
        TotalLessThan500 = g.Sum(i => i.LessThan500)
    });

翻译为:

SELECT [i].[Status], [i].[InvoiceType], COUNT(*) AS [Count], SUM(CASE
    WHEN [i].[InvoicePayments] < 100.0
    THEN [i].[EligibleValue] ELSE 0.0
END) AS [TotalLessThan100], SUM(CASE
    WHEN [i].[InvoicePayments] < 500.0
    THEN [i].[EligibleValue] ELSE 0.0
END) AS [TotalLessThan500]
FROM [Invoice] AS [i]
GROUP BY [i].[Status], [i].[InvoiceType]

答案 1 :(得分:1)

https://blogs.msdn.microsoft.com/dotnet/2018/05/30/announcing-entity-framework-core-2-1/

“在大多数情况下,我们现在支持将其转换为SQL GROUP BY子句。”

大概您的案子不是'普通'的吗?

我在2.0中有这个问题。我通过手工制作许多方法来生成SQL来解决它。是的,它是一个PITA,但出于其他各种原因,我想坚持使用Core。