You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GroupBy后无法访问Include关联表数据的替代方案咨询

问题分析与解决方案

你的核心问题是EF Core中Include在GroupBy后失效,这是因为Include仅作用于初始查询的UserProducts实体,而GroupBy是在数据库层面执行的,分组后的结果不会保留原实体的导航属性。另外你当前用g.Select(x => x.Product.Name)的写法会返回集合,且因EF Core的查询转换逻辑,导致关联数据无法正确获取。

下面是两种无需提前调用ToList()的最优方案:

方案一:直接利用分组内的导航属性(最简写法)

由于你是按UserId和ProductId分组,同一分组内的所有UserProducts记录对应的User和Product必然是同一个,因此可以直接取分组内第一条记录的导航属性,EF Core会自动生成关联查询的SQL:

[HttpGet]
public async Task<IActionResult> GetMostPurchased()
{
    var mostRequestUsers = await dbContext.UserProducts 
        // 无需Include,EF Core会自动根据导航属性生成Join语句
        .GroupBy(x => new { x.UserId, x.ProductId })
        .Select(g => new
        {
            MostPurchased = g.Key.ProductId,
            UserId = g.Key.UserId,
            Count = g.Count(),
            // 同一分组内的User/Product唯一,直接取第一条的属性
            ProductName = g.First().Product.ProductName,
            ProductPrice = g.First().Product.Price,
            ProductDesc = g.First().Product.Desc,
            UserFirstName = g.First().User.FirstName,
            UserLastName = g.First().User.LastName,
            UserPhoneNumber = g.First().User.UserPhoneNumber
        })
        .GroupBy(x => x.UserId)
        .Select(g => g.OrderByDescending(t => t.Count).FirstOrDefault())
        .ToListAsync();

    return Ok(mostRequestUsers);
}

方案二:分步统计+显式关联(更清晰的复杂场景写法)

如果后续逻辑更复杂,可以先统计购买次数,再显式关联User和Product表,逻辑更直观:

[HttpGet]
public async Task<IActionResult> GetMostPurchased()
{
    // 第一步:统计每个用户对每个产品的购买次数
    var purchaseCounts = dbContext.UserProducts
        .GroupBy(x => new { x.UserId, x.ProductId })
        .Select(g => new
        {
            g.Key.UserId,
            g.Key.ProductId,
            Count = g.Count()
        });

    // 第二步:关联用户、产品表,再按用户取购买次数最多的产品
    var mostRequestUsers = await purchaseCounts
        .Join(dbContext.Users, pc => pc.UserId, u => u.Id, (pc, u) => new { pc, u })
        .Join(dbContext.Products, pu => pu.pc.ProductId, p => p.Id, (pu, p) => new
        {
            pu.pc.UserId,
            pu.pc.ProductId,
            pu.pc.Count,
            UserFirstName = pu.u.FirstName,
            UserLastName = pu.u.LastName,
            UserPhoneNumber = pu.u.UserPhoneNumber,
            ProductName = p.ProductName,
            ProductPrice = p.Price,
            ProductDesc = p.Desc
        })
        .GroupBy(x => x.UserId)
        .Select(g => g.OrderByDescending(t => t.Count).FirstOrDefault())
        .ToListAsync();

    return Ok(mostRequestUsers);
}

关键说明

  1. 放弃Include:Include仅用于加载查询返回的实体的导航属性,而你最终返回的是匿名类型,EF Core会在Select中访问导航属性时自动生成JOIN语句,无需额外Include。
  2. 避免内存分组:提前ToList()会把所有UserProducts数据加载到内存再分组,数据量大时性能极差,上述方案所有逻辑都在数据库层面执行,性能最优。
  3. 分组内数据唯一性:按UserId+ProductId分组后,组内所有记录的User和Product是同一个,因此用First()而非Select获取单值即可。

内容的提问来源于stack exchange,提问作者yasara6647

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 13:55:17