为什么 Resolver 在 Query 和 Mutation 中的行为不同?

Why does Resolver behave differently in Query vs Mutation?

我一直在为下面描述的 issue/behaviour 苦苦挣扎,现在似乎无法弄清楚发生了什么。这一切都是基于 inherited/adapted 代码,所以我完全有可能遗漏了一些基本的东西。自从我问过关于 SO 的问题以来已经有好几年了,所以如果我不符合当前对格式和 content/detail.

的期望,请记住这一点

我有多个域 类 涉及这个问题,但在高层次上,我想做的是计算具有特定角色的用户数量一组。

我在执行 GraphQL 查询时看到的行为(见下文)是所讨论的属性 (capacityUsed) 按预期填充,这让我相信接线是正确的。

但是,当我尝试访问 GraphQL 变更中的属性时,未检索到填充该值的基础数据,因此返回默认值,这不准确或我想要的。

最终我想要做的 是访问(并根据)GroupRoleCapacity.CapacityUsed 属性。我想弄清楚突变的 config/setup 有什么问题,以便我的解析器在两种情况下都能按预期工作。

对于可能导致此问题的任何帮助或见解,我将不胜感激。我已经多次浏览相关的 HotCholocate 和 GraphL 文档,并尝试了几乎所有我能想到的相关关键字的迭代来搜索正在发生的事情的解释,但一直未能找到解决方案。

这是目前的连接方式(为了简短起见,删除无关的 info/attributes):

//GraphQL Entity Configuration
public class GroupRoleCapacityType : ObjectType<GroupRoleCapacity>
{
    protected override void Configure(IObjectTypeDescriptor<GroupRoleCapacity> descriptor)
    {
        descriptor
            .Field(grc => grc.Group)
            .ResolveWith<Resolvers>(r => r.GetGroup(default!, default!))
            .UseDbContext<AppDbContext>()
            .Description("This is the Group for this GroupRoleCapacity.");
        descriptor
            .Field(grc => grc.Role)
            .ResolveWith<Resolvers>(r => r.GetRole(default!, default!))
            .UseDbContext<AppDbContext>()
            .Description("This is the Role for this GroupRoleCapacity.");
        descriptor
            .Field(grc => grc.CapacityUsed)
            .ResolveWith<Resolvers>(r => r.GetCapacityUsed(default!, default!))
            .UseDbContext<AppDbContext>()
            .Description("This is the number of Users with the indicated Role for this GroupRoleCapacity.");
    }
    protected class Resolvers
    {
        public Group GetGroup([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
        {
            return context.Groups.FirstOrDefault(p => p.Id == GroupRoleCapacity.GroupId);
        }
        public Role GetRole([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
        {
            return context.Roles.FirstOrDefault(p => p.Id == GroupRoleCapacity.RoleId);
        }
        public int GetCapacityUsed([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
        {
            return context.Users.Count(u => u.GroupId == GroupRoleCapacity.GroupId &&
                u.UserRoles.Any(ur => ur.RoleId == GroupRoleCapacity.RoleId));
        }
    }
}
//GraphQL Service Startup    
public class Startup
{
    public IConfiguration Configuration { get; }
    public Startup(IConfiguration configuration) { Configuration = configuration; }
    public void ConfigureServices(IServiceCollection services) {
        services.AddScoped(p => p.GetRequiredService<IDbContextFactory<AppDbContext>>().CreateDbContext());
        services
            .AddGraphQLServer()
            .AddQueryType<Query>()
            .AddMutationType<Mutation>()
            .AddType<GroupType>()
            .AddType<RoleType>()
            .AddType<UserType>()
            .AddType<UserRoleType>()
            .AddType<GroupRoleCapacityType>()
    }
}

使用下面的查询似乎按预期工作,并且在通过查询访问数据时 GroupRoleCapacity.CapacityUsed 属性被适当填充

{ groupRoleCapacity { groupId roleId capacityUsed }}

//GraphQL Query
public class Query
{
    [UseDbContext(typeof(AppDbContext))]
    [GraphQLType(typeof(NonNullType<ListType<NonNullType<GroupRoleCapacities.GroupRoleCapacityType>>>))]
    public IQueryable<GroupRoleCapacity> GetGroupRoleCapacity([ScopedService] AppDbContext context)
    {
        return context.GroupRoleCapacities;
    }
}

但是,当我使用类似于下面的突变时,GroupRoleCapacity.CapacityUsed 设置不正确,这让我怀疑 Resolver 实现或配置有问题。

mutation { addUserToGroup(input: { userId: 1, groupId: 1, roleId: 1}) { result { userId, groupId, roleId }}}

//GraphQL Mutation 
public class Mutation
{
    public Mutation(IConfiguration configuration) { }
    
    [UseDbContext(typeof(AppDbContext))]
    public async Task<AddUserToGroupPayload> AddUserToGroupAsync(AddUserToGroupInput input,
        [ScopedService] AppDbContext context
        )
    {
        int someLimit = 5;
        var roleCapacity = context.GroupRoleCapacities.First(grc =>
            grc.GroupId == input.GroupId &&
            grc.RoleId == input.RoleId);
        if(roleCapacity.CapacityUsed > someLimit) {
            throw ApplicationException("limit exceeded");
        }
        //add user to group and save etc

        return new AddUserToGroupPayload(result);
    }
}

此处的不同之处在于 Hot Chocolate 仅用于 GraphQL 查询,但您的代码显式调用 EF Core 上下文。你需要一些公共部分。

你能做什么:

  1. 最好公开 DTO 而不是数据库实体
  2. 在returns这个DTO
  3. 中编写通用查询
  4. 将此查询提供给 Hot Chocolate。在这种情况下,你根本不需要解析器(如果我正确理解这个库)

示例查询(没有 DTO,从未使用过 Hot Chocolate 并且不熟悉它的属性)

public static class MyQueries
{
    public static IQueryable<GroupRoleCapacity> GetGroupRoleCapacity(AppDbContext context)
    {
        return context.GroupRoleCapacities.Select(c => new GroupRoleCapacity
        {
            GroupId = c.GroupId,
            Group = c.Group, // I assume you have defined navigation properties
            Role  = c.Role,
            RoleId = c.RoleId,
            CapacityUsed = await context.Users.Count(u => u.GroupId == c.GroupId &&
                u.UserRoles.Any(ur => ur.RoleId == c.RoleId))
        );
    }
}

查询:

//GraphQL Query
public class Query
{
    [UseDbContext(typeof(AppDbContext))]
    [GraphQLType(typeof(NonNullType<ListType<NonNullType<GroupRoleCapacities.GroupRoleCapacityType>>>))]
    public IQueryable<GroupRoleCapacity> GetGroupRoleCapacity([ScopedService] AppDbContext context)
    {
        return MyQueries.GetGroupRoleCapacity(context);
    }
}

突变:

//GraphQL Mutation 
public class Mutation
{
    public Mutation(IConfiguration configuration) { }
    
    [UseDbContext(typeof(AppDbContext))]
    public async Task<AddUserToGroupPayload> AddUserToGroupAsync(AddUserToGroupInput input,
        [ScopedService] AppDbContext context
        )
    {
        int someLimit = 5;
        var roleCapacity = MyQueries.GetGroupRoleCapacity(context)
            .FirstAsync(grc =>
                grc.GroupId == input.GroupId &&
                grc.RoleId == input.RoleId);

        if (roleCapacity.CapacityUsed > someLimit) 
        {
            throw ApplicationException("limit exceeded");
        }
        //add user to group and save etc

        return new AddUserToGroupPayload(result);
    }
}