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

HotChocolate 13:如何获取未在查询中指定字段的值

问题描述

我正在使用HotChocolate 13,现有模型FArticle(其中ArRef为ID字段),代码如下:

public partial class FArticle
{
    public string ArRef { get; set; } = null!;

    public string? SomeValue { get; set; }

    public static void ConfigureModelBuilder(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<FArticle>(entity =>
        {
            entity.HasIndex(e => e.ArRef, "UKA_F_ARTICLE_AR_Ref").IsUnique();
            entity.Property(e => e.SomeValue)
                .HasMaxLength(69)
                .IsUnicode(false)
                .HasColumnName("SomeValue");
            entity.Property(e => e.ArRef)
                .HasMaxLength(19)
                .IsUnicode(false)
                .HasColumnName("AR_Ref");
        });
    }
}

我的Query定义如下:

public class Query
{
    [UseFArticleMapping]
    [UseOffsetPaging(MaxPageSize = 100, IncludeTotalCount = true, DefaultPageSize = 20)]
    [UseProjection]
    [UseFiltering]
    [UseSorting]
    public IEnumerable<FArticle> GetFArticles([Service] DbContext context) =>
        context.FArticles;
}

我添加了自定义中间件UseFArticleMapping。当执行如下查询时:

{
    fArticles(
        skip: 0
        take: 1
    ) {
        items {
            someValue
        }
    }
}

在中间件UseFArticleMapping中可以获取FArticle列表,代码如下:

public class UseFArticleMappingAttribute : ObjectFieldDescriptorAttribute
{
    public UseFArticleMappingAttribute([CallerLineNumber] int order = 0)
    {
        Order = order;
    }

    protected override void OnConfigure(
        IDescriptorContext context,
        IObjectFieldDescriptor descriptor,
        MemberInfo member
    )
    {
        descriptor.Use(next => async middlewareContext =>
        {
            await next(middlewareContext);
            object? result = middlewareContext.Result;
            if (result is CollectionSegment<FArticle> fArticles)
            {
                foreach (var fArticle in fArticles.Items)
                {
                    fArticle.ArRef // fArticle.ArRef is null
                }
            }
        });
    }
}

但由于未在查询中指定arRef字段,仅请求了someValue,导致fArticle.ArRef返回null。请问如何让HotChocolate加载ArRef字段,而无需在查询中指定它,以便在中间件中访问?

解决方法

方法1:强制在选择集添加ArRef字段

调整自定义中间件逻辑,在执行下游中间件前,手动将ArRef字段加入查询选择集,确保UseProjection生成SQL时会包含该字段。同时要保证中间件在UseProjection之前执行:

public class UseFArticleMappingAttribute : ObjectFieldDescriptorAttribute
{
    // 设置Order为-1,确保在UseProjection(默认Order=0)之前执行
    public UseFArticleMappingAttribute([CallerLineNumber] int order = -1)
    {
        Order = order;
    }

    protected override void OnConfigure(
        IDescriptorContext context,
        IObjectFieldDescriptor descriptor,
        MemberInfo member
    )
    {
        descriptor.Use(next => async middlewareContext =>
        {
            // 强制添加ArRef到选择集
            var selectionSet = middlewareContext.Selection.SelectionSet;
            var arRefField = selectionSet.Type.Fields.FirstOrDefault(f => 
                f.Name.Equals("arRef", StringComparison.OrdinalIgnoreCase));
            
            if (arRefField != null && !selectionSet.Selections.Any(s => 
                s.Field.Name.Equals("arRef", StringComparison.OrdinalIgnoreCase)))
            {
                selectionSet.Selections.Add(
                    selectionSet.NewSelection(arRefField)
                );
            }

            await next(middlewareContext);
            
            // 后续处理逻辑不变
            object? result = middlewareContext.Result;
            if (result is CollectionSegment<FArticle> fArticles)
            {
                foreach (var fArticle in fArticles.Items)
                {
                    // 此时ArRef已被加载
                    var id = fArticle.ArRef;
                }
            }
        });
    }
}

方法2:禁用自动投影,手动指定字段

如果不需要动态投影的灵活性,可以移除[UseProjection]特性,在查询方法中手动指定要加载的字段,强制EF Core查询ArRef:

public class Query
{
    [UseFArticleMapping]
    [UseOffsetPaging(MaxPageSize = 100, IncludeTotalCount = true, DefaultPageSize = 20)]
    [UseFiltering]
    [UseSorting]
    public IQueryable<FArticle> GetFArticles([Service] DbContext context) =>
        context.FArticles.Select(a => new FArticle 
        { 
            ArRef = a.ArRef, 
            SomeValue = a.SomeValue 
        });
}

这种方式的缺点是后续模型字段变更时,需要手动更新选择逻辑。

方法3:自定义投影访问器全局强制包含字段

创建自定义ProjectionNodeVisitor,在投影阶段自动为FArticle类型添加ArRef字段:

public class FArticleProjectionVisitor : ProjectionNodeVisitor
{
    protected override INodeVisitorAction Enter(
        ObjectFieldNode node, 
        IProjectionVisitorContext context)
    {
        if (context.Type is ObjectType objectType && objectType.ClrType == typeof(FArticle))
        {
            var arRefField = objectType.Fields.First(f => f.Name == "arRef");
            if (!context.SelectedFields.Contains(arRefField))
            {
                context.SelectedFields.Add(arRefField);
            }
        }
        return base.Enter(node, context);
    }
}

然后在服务注册时替换默认的投影访问器:

services.AddGraphQLServer()
    .AddQueryType<Query>()
    .AddProjectionVisitor<FArticleProjectionVisitor>();

这种方式适合全局需要强制包含某些字段的场景,逻辑更优雅。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 19:40:11