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
相关产品推荐
相关产品推荐

