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

使用GroupBy/Select时Mapster ProjectToType报错,求解决方法

解决Mapster ProjectToType在GroupBy/Select后报EmptyProjectionMember的问题

错误原因

这个错误是因为EF Core执行GroupBy后再Select(x => x.First())的查询,会生成特殊的表达式树结构,Mapster在解析该结构时无法正确识别投影成员,导致抛出KeyNotFoundException: The given key 'EmptyProjectionMember' was not present in the dictionary。

解决办法

方法1:先查询实体到内存,再映射到DTO

将EF查询提前执行(用ToListAsync),把数据拉到内存后,再用Mapster的Adapt方法映射到DTO,绕开EF查询阶段的表达式解析问题:

public async Task<IList<T>> GetDocuments<T>(string? slug, string? languageId)
{
    var iq = _dataContext.Documents
        .Where(x => slug == null || x.Slug == slug);

    if (languageId != null)
    {
        // 先从数据库获取实体列表
        var documents = await iq
            .GroupBy(x => x.Slug)
            .Select(x => x
                .OrderByDescending(y => y.LanguageId == languageId)
                .ThenByDescending(y => y.LanguageId == "da")
                .ThenBy(y => y.Name)
                .First())
            .AsNoTracking()
            .ToListAsync();
        
        // 内存中映射到DTO
        return documents.Adapt<IList<T>>();
    }
    else
    {
        return await iq
            .OrderBy(x => x.Name)
            .ThenBy(x => x.LanguageId)
            .AsNoTracking()
            .ProjectToType<T>()
            .ToListAsync();
    }
}

方法2:用窗口函数替代GroupBy+First,保持可解析的IQueryable

使用EF Core的窗口函数RowNumber()来实现每个Slug下的优先级筛选,这样查询返回的仍然是IQueryable<Document>,Mapster可以正常解析投影:

public async Task<IList<T>> GetDocuments<T>(string? slug, string? languageId)
{
    var iq = _dataContext.Documents
        .Where(x => slug == null || x.Slug == slug);

    if (languageId != null)
    {
        // 用RowNumber窗口函数标记每个Slug下文档的优先级
        iq = from doc in iq
             select new
             {
                 Doc = doc,
                 Rank = EF.Functions.RowNumber()
                     .Over(
                         PartitionBy: doc.Slug,
                         OrderBy: doc.LanguageId == languageId ? 0 : 1, // 目标语言优先级最高
                         ThenBy: doc.LanguageId == "da" ? 0 : 1, // 丹麦语次之
                         ThenBy: doc.Name
                     )
             }
             .Where(x => x.Rank == 1) // 只取每个Slug下优先级第一的文档
             .Select(x => x.Doc);
    }
    else
    {
        iq = iq.OrderBy(x => x.Name).ThenBy(x => x.LanguageId);
    }

    return await iq.AsNoTracking().ProjectToType<T>().ToListAsync();
}

关键说明

  • 方法1适合数据量不大的场景,实现简单,但会把符合条件的GroupBy结果全部拉到内存。
  • 方法2性能更优,所有筛选逻辑都在数据库端执行,返回的数据量更少,推荐在数据量较大时使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 15:53:10