GraphQL .NET 上部分更新突变的空字段

Null fields on a partial update mutation on GraphQL .NET

在工作中,我们在数据层上使用 EFCore 并使用 graphql-dotnet 来管理 API 请求,我在使用 GraphQL 突变更新一些大对象时遇到了问题。当用户发送对模型的部分更新时,我们希望仅更新数据库中实际被突变更改的字段。我们遇到的问题是,当我们直接将输入映射到实体时,如果某些字段被故意传递为 null,或者该字段根本没有在突变中指定,我们得到的 属性 值为 null .这样我们就不能将更改发送到数据库,否则我们会错误地将一堆字段更新为空。

因此,我们需要一种方法来识别在变更中发送了哪些字段并仅更新这些字段。在 JS 中,这是通过检查 属性 值是否未定义来实现的,如果值为 null,我们知道它是故意作为 null 传递的。

我们一直在考虑的一些解决方法是在字典上使用反射来仅更新指定的字段。但是我们需要对每一个突变进行反思。另一个解决方案是对我们模型上的每个可为空的 属性 设置一个 isChanged 属性,并在引用的 属性 setter 上更改 ir,但是... cmon...

我在下面提供了一些代码作为这种情况的示例:

人类class:

public class Human
{
    public Id { get; set; }
    public string Name { get; set; }
    public string HomePlanet { get; set; }
}

GraphQL 类型:

public class HumanType : ObjectGraphType<Human>
{
    public HumanType()
    {
        Name = "Human";
        Field(h => h.Id).Description("The id of the human.");
        Field(h => h.Name, nullable: true).Description("The name of the human.");
        Field(h => h.HomePlanet, nullable: true).Description("The home planet of the human.");
    }
}

输入类型:

public class HumanInputType : InputObjectGraphType
    {
        public HumanInputType()
        {
            Name = "HumanInput";
            Field<NonNullGraphType<StringGraphType>>("name");
            //The problematic field
            Field<StringGraphType>("homePlanet");
        }
    }

人类突变:

/// Example JSON request for an update mutation without HomePlanet 
/// {
///   "query": "mutation ($human:HumanInput!){ createHuman(human: $human) { id name } }",
///   "variables": {
///     "human": {
///       "name": "Boba Fett"
///     }
///   }
/// }
///
public class StarWarsMutation : ObjectGraphType<object>
{
    public StarWarsMutation(StarWarsRepository data)
    {
        Name = "Mutation";

        Field<HumanType>(
            "createOrUpdateHuman",
            arguments: new QueryArguments(
                new QueryArgument<NonNullGraphType<HumanInputType>> {Name = "human"}
            ),
            resolve: context =>
            {
                //After conversion human.HomePlanet is null. But it was not informed, we should keep what is on the database at the moment
                var human = context.GetArgument<Human>("human");
                //On EFCore the Update method is equivalent to an InsertOrUpdate method
                return data.Update(human);
            });
    }
}

您可以使用 Newtonsoft Json 库中的 JsonConvert.PopulateObject。在突变解析器上,我没有使用 GetArgument 我的类型,而是使用 GetArgument<dynamic> 并使用 JsonConvert.SerializeObject 序列化它,然后通过调用 JsonConvert.PopulateObject 我只能更新被告知的字段。

public StarWarsMutation(StarWarsRepository data)
{
    Name = "Mutation";

    Field<HumanType>(
        "createOrUpdateHuman",
        arguments: new QueryArguments(
            new QueryArgument<NonNullGraphType<HumanInputType>> {Name = "human"}
        ),
        resolve: context =>
        {
            //After conversion human.HomePlanet is null. But it was not informed, we should keep what is on the database at the moment
            var human = context.GetArgument<dynamic>("human");
            var humanDb = data.GetHuman(human["id"]);
            var json = JsonConvert.SerializeObject(human);
            JsonConvert.PopulateObject(json, humanDb);
            //On EFCore the Update method is equivalent to an InsertOrUpdate method
            return data.Update(humanDb);
        });
}