如何在 Entity Framework Group By 结果中包含属性

Posted

技术标签:

【中文标题】如何在 Entity Framework Group By 结果中包含属性【英文标题】:How do I include attribute in Entity Framework Group By result 【发布时间】:2021-11-08 11:15:26 【问题描述】:

假设我有一个位置表,其中包含位置 ID 和位置名称。假设我想获得每个位置的收入(在这个简单的场景中,我什至可能不需要GroupBy - 但请假设我需要!)

var revenues = await _context.SaleTransaction.GroupBy(s => s.LocationId)
    .Select(x => new LocationDTO 
        LocationId = x.Key,
        LocationName = ??? 
        Revenues = x.Sum(i => i.Amount)
    ).ToListAsync();

我试图作弊

LocationName = x.Select(i => i.Location.LocationName).First()

因为此 ID 的所有位置名称都相同。但 EF 无法翻译 First(),除非我使用 AsEnumerable() 并将整个销售表放入应用程序内存中。

或者我可以第二次遍历结果:

foreach(var revenue in revenues) 
    revenue.LocationName = _context.Location.Find(revenue.LocationId).LocationName;

鉴于位置的数量是固定的(并且相对较少),它可能是最好的方法。尽管如此,对于每个位置O(n) 或将整个位置列表拉入内存都不是很好。也许有一种方法可以将 LocationName(和其他一些属性)分配为 GroupBy 语句的一部分。

我正在使用 EF Core 5;或者,如果 EF Core 6 中出现了某些东西 - 那也可以。

【问题讨论】:

我认为从位置表而不是 SaleTransaction 开始查询会更好 @SinaRiani 好的。想解释一下原因吗? 请告诉我,Location 和 SaleTransaction 之间的关系类型是什么? “一对一”还是“一对多”? Location 和 SaleTransaction 的关系类型?严重地?当然是一对多 可以解决您的问题的方法是按 id 和名称分组参见 ***.com/questions/2421388/… 【参考方案1】:

我可以简要地看到,您需要一个 linq 连接查询才能加入搜索。使用 EF linq 查询意味着它们在使用之前不会被加载到内存中,因此它可以解决加载整个表的问题。

你可以这样写:

var revenues = await _context.SaleTransactions.Join(_context.Locations, s => s.LocationId, l => l.Id, (s, l) => new LocationId = s.LocationId, LocationName = l.LocationName, Revenues = s.Sum(i => i.Amount));

我会将整个小提琴与您可能的模型的模拟联系起来 https://dotnetfiddle.net/BGJmjj

【讨论】:

不,加入绝对不是要走的路。可以(并且应该)使用导航属性。 我喜欢!我对加入 EF 持怀疑态度 - 它经常反映糟糕的设计(我 具有 从 SaleTransaction 到 Location 的导航属性);但在这种情况下,这可能是正确的方法。让我玩一下吧 @GertArnold 在 EF Core 中很容易包含导航属性(尤其是在 .net 5 中),您可以再包含一个 Location 类型的属性,无论如何您都必须保留它的 Id,或者在一个与可能的关系,所以我认为这不是什么大问题 什么不是问题?在导航属性可用时使用 join 对我来说是个问题。【参考方案2】:

您可以按多个值进行分组。例如;

var revenues = await _context.SaleTransaction
    .GroupBy(s => new 
        s.LocationId, 
        s.Location.Name 
    )
    .Select(x => new LocationDTO 
        LocationId = x.Key.LocationId,
        LocationName = x.Key.Name,
        Revenues = x.Sum(i => i.Amount)
    ).ToListAsync();

虽然您似乎正在计算每个位置的总数,但在这种情况下,您可以改为围绕位置构建查询。

var revenues = await _context.Location
    .Select(x => new LocationDTO 
        LocationId = x.Id,
        LocationName = x.Name,
        Revenues = x.SaleTransactions.Sum(i => i.Amount)
    ).ToListAsync();

【讨论】:

我的 LINQ 语句要复杂得多;正如我在 OP 中所说的那样——我意识到通过我所做的简化,我什至不需要GroupBy——但相信我……我愿意;)第一个陈述——想象有十几个位置属性;有些可能非常复杂(例如,地址、经理等)。所以,虽然我意识到我可以将所有“卷心菜”放入 GroupBy 语句中 - 然后我宁愿遍历结果列表 您可以选择(或分组)整个实体,例如.Select(x => new x.Location, ... )。尽管您可能需要显式加载拥有的类型。或许你应该确保你的例子更真实。【参考方案3】:
var revenues = await _context.Location
.Select(x => new LocationDTO 
    LocationId = x.Id,
    LocationName = x.Name,
    Revenues = x.SaleTransactions.Sum(i => i.Amount)
).ToListAsync();

有一个例子: .NetFiddle

【讨论】:

谢谢。正如我所说,在这个简单的场景中,我什至可能不需要 GroupBy - 但请假设我需要! 试试LocationName = x.First().Location.LocationId

以上是关于如何在 Entity Framework Group By 结果中包含属性的主要内容,如果未能解决你的问题,请参考以下文章

如何使用Entity Framework仅更新一个字段?

如何在 Entity Framework Core 中运行存储过程?

如何在 Entity Framework Core 中运行存储过程?

如何在 Entity Framework Code First 中使属性唯一

如何在 Entity Framework Core 2 中播种?

我应该如何在 Entity Framework 6 中播种数据