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

在HotChocolate解析器中获取当前Poll实例ID并实现userHasAnswered字段

问题解答

1. 获取当前Poll的ID

在Hot Chocolate的字段解析器中,你可以通过context.Parent<Poll>()获取当前正在处理的Poll实体实例,直接从中读取Id属性,修正后的代码如下:

public class PollType : ObjectType<Poll>
{
    protected override void Configure(IObjectTypeDescriptor<Poll> descriptor)
    {
        descriptor
            .Field("userHasAnswered")
            .Resolve(context =>
            {
                var currentPoll = context.Parent<Poll>(); // 获取当前Poll实例
                var dataContext = context.Service<DataContext>();
                var userId = context.Service<IHttpContextAccessor>().HttpContext.User.GetUserId();
                
                return dataContext.Answers.Any(x => x.UserId == userId && x.Question.PollId == currentPoll.Id);
            });
    }
}

2. 当前实现的问题与优化方案

当前方案的不足

  • N+1查询问题:如果一次查询返回多个Poll,每个Poll都会触发单独的Answers.Any()查询,数据库请求次数会随返回的Poll数量线性增长,性能低下。
  • 业务逻辑耦合:判断用户是否参与投票的逻辑直接写在解析器中,难以单独编写单元测试,不符合关注点分离的设计原则。
  • 依赖Web环境:直接依赖IHttpContextAccessor,导致代码无法脱离Web环境进行测试,灵活性不足。

更好的实现方案

方案一:使用DataLoader解决N+1问题

Hot Chocolate的DataLoader支持批量查询,可以将多个Poll的判断请求合并为一次数据库查询:

首先定义批量查询的DataLoader:

public class UserPollAnswerStatusDataLoader : BatchDataLoader<int, bool>
{
    private readonly DataContext _dbContext;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public UserPollAnswerStatusDataLoader(
        IBatchScheduler batchScheduler,
        DataContext dbContext,
        IHttpContextAccessor httpContextAccessor) 
        : base(batchScheduler)
    {
        _dbContext = dbContext;
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async Task<IReadOnlyDictionary<int, bool>> LoadBatchAsync(
        IReadOnlyList<int> pollIds, 
        CancellationToken cancellationToken)
    {
        var userId = _httpContextAccessor.HttpContext.User.GetUserId();
        if (userId == null)
        {
            return pollIds.ToDictionary(id => id, _ => false);
        }

        // 批量查询用户参与过的Poll ID
        var answeredPollIds = await _dbContext.Answers
            .Where(a => a.UserId == userId && pollIds.Contains(a.Question.PollId))
            .Select(a => a.Question.PollId)
            .Distinct()
            .ToListAsync(cancellationToken);

        return pollIds.ToDictionary(id => id, answeredPollIds.Contains);
    }
}

然后在解析器中使用该DataLoader:

public class PollType : ObjectType<Poll>
{
    protected override void Configure(IObjectTypeDescriptor<Poll> descriptor)
    {
        descriptor
            .Field("userHasAnswered")
            .Resolve(async context =>
            {
                var currentPoll = context.Parent<Poll>();
                var dataLoader = context.DataLoader<UserPollAnswerStatusDataLoader>();
                return await dataLoader.LoadAsync(currentPoll.Id);
            });
    }
}

方案二:将业务逻辑抽离到服务层

把判断用户是否参与投票的逻辑封装到独立的业务服务中,让解析器只负责调用服务,保持职责单一:

public interface IPollService
{
    Task<bool> HasUserAnsweredPollAsync(int pollId, int userId);
}

public class PollService : IPollService
{
    private readonly DataContext _dbContext;

    public PollService(DataContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<bool> HasUserAnsweredPollAsync(int pollId, int userId)
    {
        return await _dbContext.Answers
            .AnyAsync(a => a.UserId == userId && a.Question.PollId == pollId);
    }
}

修改解析器调用服务:

public class PollType : ObjectType<Poll>
{
    protected override void Configure(IObjectTypeDescriptor<Poll> descriptor)
    {
        descriptor
            .Field("userHasAnswered")
            .Resolve(async context =>
            {
                var currentPoll = context.Parent<Poll>();
                var pollService = context.Service<IPollService>();
                var userId = context.Service<IHttpContextAccessor>().HttpContext.User.GetUserId();
                
                return await pollService.HasUserAnsweredPollAsync(currentPoll.Id, userId);
            });
    }
}

方案三:移除对IHttpContextAccessor的直接依赖

Hot Chocolate提供了直接获取当前用户ClaimsPrincipal的方式,可以替代IHttpContextAccessor,让代码更易测试:

public class PollType : ObjectType<Poll>
{
    protected override void Configure(IObjectTypeDescriptor<Poll> descriptor)
    {
        descriptor
            .Field("userHasAnswered")
            .Resolve(async context =>
            {
                var currentPoll = context.Parent<Poll>();
                var user = context.User; // 直接获取当前用户ClaimsPrincipal
                var userId = user.GetUserId();
                var pollService = context.Service<IPollService>();
                
                return await pollService.HasUserAnsweredPollAsync(currentPoll.Id, userId);
            });
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 07:52:53