如何使 EF Core 3 以与 EF Core 7 相同的方式翻译外键分组?

huangapple go评论58阅读模式
英文:

How can I make EF Core 3 translate the group by foreign key the same way efcore7 does it?

问题

我有以下的实体和数据库上下文:

public class Item
{
    public int Id { get; set; }

    public int? ReceiptId { get; set; }
    public int ItemTypeId { get; set; }
}

public class ItemType
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Item> Items { get; set; }
}

public class Receipt
{
    public int Id { get; set; }
    public string ReceiptInfo { get; set; }

    public List<Item> Items { get; set; }
}

我试图获取收据的列表,但不想包含它们包含的项目,而是想要它们包含“itemType”和每种项目的数量。我编写了以下的 LINQ 查询,它有效:

var result = _databaseContext.Receipts.Select(r => new
{
    r.Id,
    r.ReceiptInfo,
    ItemInfo = r.Items.GroupBy(item => item.ItemTypeId)
                      .Select(group => new
                      {
                          IdItemType = group.Key,
                          AmountOfItems = group.Count(),
                      }).ToList()
});

在 EF Core 7 中,它被转换为以下的 SQL 查询:

SELECT [r].[Id], [r].[ReceiptInfo], [t].[IdItemType], [t].[AmountOfItems]
FROM [Receipts] AS [r]
OUTER APPLY 
    (SELECT [i].[ItemTypeId] AS [IdItemType], COUNT(*) AS [AmountOfItems]
     FROM [Items] AS [i]
     WHERE [r].[Id] = [i].[ReceiptId]
     GROUP BY [i].[ItemTypeId]) AS [t]
ORDER BY [r].[Id]

然而,我需要在一个不支持 EF Core 3.1 以前版本的旧项目中执行此操作。在那里,查询被转换为不同的方式,并且我会收到以下错误:

选择列表中的列 'Receipts.Id' 无效,因为它既不包含在聚合函数中,也不包含在 GROUP BY 子句中。

英文:

I have the following entities and a database context

public class Item
{
    public int Id { get; set; }

    public int? ReceiptId { get; set; }
    public int ItemTypeId { get; set; }
}

public class ItemType
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List&lt;Item&gt; Items { get; set; }
}

public class Receipt
{
    public int Id { get; set; }
    public string ReceiptInfo { get; set; }

    public List&lt;Item&gt; Items { get; set; }
}

I'm trying to get a the list of receipts, but instead of containing the items they contain, I want them to have the itemType and the amount of items for each. I have written the following linq query, which works:

var result = _databaseContext.Receipts.Select(r =&gt; new
            {
                r.Id,
                r.ReceiptInfo,
                ItemInfo = r.Items.GroupBy(item =&gt; item.ItemTypeId)
                                  .Select(group =&gt; new
                                  {
                                      IdItemType = group.Key,
                                      AmountOfItems = group.Count(),
                                  }).ToList()

            });

With EF Core 7, it is translated to the following SQL query:

SELECT [r].[Id], [r].[ReceiptInfo], [t].[IdItemType], [t].[AmountOfItems]
FROM [Receipts] AS [r]
OUTER APPLY 
    (SELECT [i].[ItemTypeId] AS [IdItemType], COUNT(*) AS [AmountOfItems]
     FROM [Items] AS [i]
     WHERE [r].[Id] = [i].[ReceiptId]
     GROUP BY [i].[ItemTypeId]) AS [t]
ORDER BY [r].[Id]

Yet, I need to do this in an older project which doesn't support a version older than 3.1 for EF Core.

There it translates the query differently and I get this error

> Column 'Receipts.Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause

答案1

得分: 1

在 EF Core 3.1 的情况下,您需要在客户端端对加载的详细项进行后处理:

var rawData = _databaseContext.Receipts.Select(r => new
    {
        r.Id,
        r.ReceiptInfo,
        RawItemInfo = r.Items.Select(item => new
        {
            IdItemType = item.ItemTypeId
        }).ToList()
    })
    .ToList();

var result = rawData
    .Select(r => new
    {
        r.Id,
        r.ReceiptInfo,
        ItemInfo = r.RawItemInfo
            .GroupBy(item => item.ItemTypeId)
            .Select(group => new
            {
                IdItemType = group.Key,
                AmountOfItems = group.Count(),
            }).ToList()
    });
英文:

In case of EF Core 3.1, you have to postprocess loaded detail items on the client side:

var rawData = _databaseContext.Receipts.Select(r =&gt; new
    {
        r.Id,
        r.ReceiptInfo,
        RawItemInfo = r.Items.Select(item =&gt; new
        {
            IdItemType = item.ItemTypeId
        }).ToList()
    })
    .ToList();

var result = rawData
    .Select(r =&gt; new
    {
        r.Id,
        r.ReceiptInfo,
        ItemInfo = r.RawItemInfo
            .GroupBy(item =&gt; item.ItemTypeId)
            .Select(group =&gt; new
            {
                IdItemType = group.Key,
                AmountOfItems = group.Count(),
            }).ToList()
    });

答案2

得分: 1

如您所见,EFC 7中的GroupBy支持已经大幅改进。EFC 3仅支持在查询根部分使用聚合操作的GroupBy。

因此,要在EFC 3中运行它,您需要将查询强制转换为受支持的形式。为了获得相同的分组级别,查询从Items开始,对三个元素进行分组和聚合,而不是两个:

var result = _databaseContext.Items
    .GroupBy(item => new { item.ReceiptId, item.Receipt.ReceiptInfo, item.ItemTypeId })
    .Select(group => new
    {
        Id = group.Key.ReceiptId,
        ReceiptInfo = group.Key.ReceiptInfo,
        IdItemType = group.Key.ItemTypeId,
        NrOfItems = group.Count(),
    })

这将返回与原始查询相同的数据,并在数据库中进行数据的聚合。要获得相同的结果形状,需要在内存中进行一些后处理(即在调用AsEnumerable()后):

    .AsEnumerable()
    .GroupBy(x => new { x.Id, x.Receipt.ReceiptInfo })
    .Select(g => new
    {
        g.Key.Id,
        g.Key.ReceiptInfo,
        ItemInfo = g.Select(x => new { x.IdItemType, x.NrOfItems })
       
    });

这需要添加一个名为Item.Receipt的导航属性。

英文:

As you see, GroupBy support has improved drastically in EFC 7. EFC 3 only supports GroupBy with aggregates at the query root.

Therefore, to make it run in EFC 3 you need to force the query into the supported shape. To get the same grouping level, the query starts at Items and groups + aggregates once over three elements instead of two:

var result = _databaseContext.Items
    .GroupBy(item =&gt; new { item.ReceiptId, item.Receipt.ReceiptInfo, item.ItemTypeId })
    .Select(group =&gt; new
    {
        Id = group.Key.ReceiptId,
        ReceiptInfo = group.Key.ReceiptInfo,
        IdItemType = group.Key.ItemTypeId,
        NrOfItems = group.Count(),
    })

That returns the same data as the original query and does the reduction of data (aggregate) in the database. To get the same result shape, it needs some post-processing in-memory (i.e. after calling AsEnumerable()):

    .AsEnumerable()
    .GroupBy(x =&gt; new { x.Id, x.Receipt.ReceiptInfo })
    .Select(g =&gt; new
    {
        g.Key.Id,
        g.Key.ReceiptInfo,
        ItemInfo = g.Select(x =&gt; new { x.IdItemType, x.NrOfItems })
       
    });

This requires adding a navigation property Item.Receipt.

huangapple
  • 本文由 发表于 2023年2月19日 21:15:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/75500389.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定