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

HotChocolate v13中[UseProjections]与DataLoaders协同失效问题求助

问题描述

我使用以下GraphQL查询,希望仅从数据库提取Host对象的hostId、hostName和siteId三个字段:

query {
  listTenants {
    totalCount
    items {
      tenantId
      name
      sites {
        totalCount
        items {
          siteId
          cmxName
          cmxState
          hosts(
            order: { hostId: ASC }
            where: { hostName: { neq: "ans" } }
            skip: 4
            take: 2
          ) {
            totalCount
            items {
              hostId
              hostName
              siteId
            }
          }
        }
      }
    }
  }
}

我通过扩展Site对象类型实现Hosts查询:

namespace dataGraphAPI.Types.Nodes
{
    [ExtendObjectType<Site>]
    public static class SiteNode
    {
        [GraphQLName(SchemaConstants.Hosts)]
        [ListQueries]
        public static async Task<IEnumerable<Host>> GetHostsAsync(
            [Parent] Site site,
            IHostsBySiteIdDataLoader dataLoader,
            CancellationToken ct)
            =>  await dataLoader.LoadAsync(site.SiteId.ToString(), ct);
    }
}

ListQueries属性配置了分页、投影、过滤和排序:

using HotChocolate.Types.Descriptors;
using System.Reflection;

namespace dataGraphAPI.Types
{
    public sealed class ListQueriesAttribute : ObjectFieldDescriptorAttribute
    {
        protected override void OnConfigure(IDescriptorContext context, IObjectFieldDescriptor descriptor, MemberInfo member)
        {
            ApplyAttribute(
                context, 
                descriptor, 
                member, 
                new UseOffsetPagingAttribute() 
                { 
                    IncludeTotalCount = true,
                });

            ApplyAttribute(
                context,
                descriptor,
                member,
                new UseProjectionAttribute());

            ApplyAttribute(
                context,
                descriptor,
                member,
                new UseFilteringAttribute());

            ApplyAttribute(
                context,
                descriptor,
                member,
                new UseSortingAttribute());
        }
    }
}

DataLoader实现如下:

[DataLoader]
internal static async Task<ILookup<string, Host>> GetHostsBySiteIdAsync(IReadOnlyList<string> siteIds, CmxDbContext dbContext, CancellationToken ct)
        {
            var hosts = dbContext.Hosts
                .Where(x => siteIds.Contains(x.SiteId.ToString()));

            return hosts.ToLookup(x => x.SiteId.ToString()!);
        }

Program.cs的GraphQL配置:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<CmxDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("CMXContext"))
           .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
           .LogTo(Console.WriteLine, LogLevel.Information));

builder.Services
                .AddGraphQLServer()
                .AddTypes()
                .AddType<AggregateResult>()
                .AddType<CountResult>()
                .AddType<DistinctResult>()
                .AddDirectiveType<AggregateDirectiveType>()
                .AddDirectiveType<CountDirectiveType>()
                .AddDirectiveType<DistinctDirectiveType>()
                .AddFiltering()
                .AddSorting()
                .AddProjections()
                .RegisterDbContext<CmxDbContext>();

var app = builder.Build();

app.MapGraphQL();

app.Run();

目前遇到的问题:UseProjection属性未生效,数据库返回了Host的所有字段而非指定的三个。我知道UseProjection对IQueryable有效,但DataLoader无法返回IQueryable,请问如何让投影功能与DataLoader协同工作?


解决方案

核心思路是让HotChocolate把GraphQL投影逻辑传递到数据库查询层面,同时保留DataLoader的批量加载能力,推荐以下简洁实现方式:

推荐方案:使用UseDataLoader属性替代手动调用DataLoader

这种方式无需手动处理投影传递,HotChocolate会自动将投影、过滤、排序转换为EF Core查询语句,同时通过DataLoader批量加载数据:

1. 修改SiteNode的GetHosts方法

将方法返回类型改为IQueryable<Host>,并添加UseDataLoader属性:

namespace dataGraphAPI.Types.Nodes
{
    [ExtendObjectType<Site>]
    public static class SiteNode
    {
        [GraphQLName(SchemaConstants.Hosts)]
        [ListQueries] // 保留原有分页、投影、过滤、排序配置
        [UseDataLoader(typeof(HostsBySiteIdDataLoader))] // 启用批量加载
        public static IQueryable<Host> GetHosts([Parent] Site site, CmxDbContext dbContext)
        {
            // 返回当前Site对应的Host查询IQueryable,后续由HotChocolate处理投影、过滤和批量加载
            return dbContext.Hosts.Where(x => x.SiteId == site.SiteId);
        }
    }
}

2. 优化DataLoader实现

使用SiteId的原始类型(比如Guid,避免字符串转换的性能损耗),批量查询时直接匹配原始值:

[DataLoader]
internal static async Task<ILookup<Guid, Host>> GetHostsBySiteIdAsync(IReadOnlyList<Guid> siteIds, CmxDbContext dbContext, CancellationToken ct)
{
    var hosts = await dbContext.Hosts
        .Where(x => siteIds.Contains(x.SiteId))
        .ToListAsync(ct);
    return hosts.ToLookup(x => x.SiteId);
}

3. 效果验证

执行GraphQL查询时,HotChocolate会自动完成以下操作:

  1. 解析查询中的投影字段,生成EF Core的Select表达式,仅查询hostId、hostName、siteId
  2. 收集所有请求的SiteId,通过DataLoader批量查询数据库
  3. 将批量查询结果分配给对应的Site请求,仅返回投影指定的字段

原方案失效原因

之前手动调用DataLoader时,UseProjection无法将投影逻辑传递到DataLoader内部的EF Core查询中,DataLoader返回的IEnumerable<Host>只能在内存中做投影,导致数据库加载了Host的所有字段。而使用UseDataLoader属性时,HotChocolate会先处理投影、过滤、排序逻辑,再执行批量查询,确保这些操作都在数据库层面完成。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 06:36:48