C#/HotChocolate如何实现支持全局对象标识的多类型统一搜索
HotChocolate 无中间类型统一搜索实现方案
你给出的目标查询写法完全可以实现,而且不需要保留中间SearchResult类型,同时能原生支持全局对象标识能力,下面是两种可落地的实现方案,按需选择即可。
方案1:根字段独立传参(完全匹配你期望的查询语法)
这个方案不需要修改现有查询的根结构,给每个实体对应的根查询字段增加搜索参数即可,配合HotChocolate内置的分页特性,直接返回你要的nodes结构,自动兼容全局ID。
首先给需要支持搜索和全局标识的实体类添加[Node]特性,HotChocolate会自动为其生成全局ID、配套的node查询入口:
// 用户/研究者实体 [Node] public class User { public int Id { get; set; } public string Name { get; set; } public string About { get; set; } } // 服务实体 [Node] public class Service { public int Id { get; set; } public string Name { get; set; } }
然后实现查询逻辑,不需要定义SearchResult类,直接为两个根字段分别添加搜索过滤逻辑:
[ExtendObjectType("Query")] public class SearchQueries { [UseDbContext(typeof(ApplicationDbContext))] [UsePaging] // 自动生成nodes、pageInfo分页结构 public IQueryable<Service> GetServices( [Argument("token")] string? token, [ScopedService] ApplicationDbContext dbContext) { var query = dbContext.Services.AsQueryable(); if (!string.IsNullOrWhiteSpace(token)) { query = query.Where(s => s.Name.Contains(token)); } return query; } [UseDbContext(typeof(ApplicationDbContext))] [UsePaging] public IQueryable<User> GetResearchers( [Argument("token")] string? token, [ScopedService] ApplicationDbContext dbContext) { var query = dbContext.Users.AsQueryable(); if (!string.IsNullOrWhiteSpace(token)) { query = query.Where(s => s.Name.Contains(token)); } return query; } }
这个方案的特点:
- 完全匹配你写的目标查询语法,前端可以按需指定要搜索的实体类型,没有请求的字段不会触发对应表的数据库查询,性能比你之前不管前端要不要都同时查用户、服务两张表的实现更好
- 原生支持全局ID、分页、排序等HotChocolate内置能力
- 缺点是后续新增搜索实体类型时,需要新增对应的根查询字段。
方案2:Union类型单入口搜索(保留单一搜索端点)
如果你需要保留单个搜索入口的设计,又不想定义固定字段的SearchResult中间类,可以用GraphQL Union类型返回混合类型的搜索结果,同样支持全局对象标识。
首先定义搜索结果的Union标记接口:
[UnionType("SearchResultItem")] public interface ISearchResultItem { // 无需定义属性,HotChocolate会自动将实现该接口的实体类型加入Union } // 实体类实现该接口即可 [Node] public class User : ISearchResultItem { public int Id { get; set; } public string Name { get; set; } public string About { get; set; } } [Node] public class Service : ISearchResultItem { public int Id { get; set; } public string Name { get; set; } }
单入口搜索的查询实现:
[ExtendObjectType("Query")] public class SearchQueries { [UseDbContext(typeof(ApplicationDbContext))] [UsePaging] public async Task<IEnumerable<ISearchResultItem>> Search( [Argument("token")] string token, [ScopedService] ApplicationDbContext dbContext, CancellationToken cancellationToken) { var matchedUsers = await dbContext.Users .Where(s => s.Name.Contains(token)) .ToArrayAsync(cancellationToken); var matchedServices = await dbContext.Services .Where(s => s.Name.Contains(token)) .ToArrayAsync(cancellationToken); // 可在此处添加匹配度排序、结果截断等逻辑 return matchedUsers.Cast<ISearchResultItem>() .Concat(matchedServices); } }
对应的查询写法通过内联片段取不同类型的字段:
query Search($token: String!){ search(token: $token) { nodes { __typename ... on Service { id # 自动返回全局格式ID name } ... on User { id name about } } } }
这个方案的特点:
- 保留单一搜索入口,新增搜索实体时只要让实体实现
ISearchResultItem接口、添加[Node]特性即可,不需要修改搜索入口的返回类型定义 - 同样原生支持全局ID能力
- 缺点是返回的是混合类型集合,前端需要通过
__typename字段区分实体类型。
全局对象标识说明
只要给实体类标记[Node]特性,HotChocolate会自动完成以下能力:
- 将数据库原始主键自动编码为全局唯一的字符串ID(格式为
类型名+主键的Base64编码,全局唯一不重复) - 自动生成根级
node(id: ID!)查询字段,支持通过全局ID直接查询任意类型的实体 - 所有返回该实体的位置,都会默认返回全局格式的ID字段,不需要手动做ID转换。
内容的提问来源于stack exchange,提问作者okolobaxa
相关产品推荐
相关产品推荐

