【发布时间】:2020-07-18 17:59:59
【问题描述】:
我正在尝试使用 Entity Framework Core 和 Hot Chocolate 创建一个 ASP.NET Core 3.1 应用程序。 应用程序需要支持通过 GraphQL 创建、查询、更新和删除对象。 有些字段需要有值。
创建、查询和删除对象不是问题,但是更新对象就比较麻烦了。 我要解决的问题是部分更新。
Entity Framework 使用下面的模型对象先通过代码创建数据库表。
public class Warehouse
{
[Key]
public int Id { get; set; }
[Required]
public string Code { get; set; }
public string CompanyName { get; set; }
[Required]
public string WarehouseName { get; set; }
public string Telephone { get; set; }
public string VATNumber { get; set; }
}
我可以在数据库中创建一条记录,其中定义如下:
public class WarehouseMutation : ObjectType
{
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.Field("create")
.Argument("input", a => a.Type<InputObjectType<Warehouse>>())
.Type<ObjectType<Warehouse>>()
.Resolver(async context =>
{
var input = context.Argument<Warehouse>("input");
var provider = context.Service<IWarehouseStore>();
return await provider.CreateWarehouse(input);
});
}
}
目前,对象很小,但在项目完成之前它们将拥有更多的领域。我需要放弃 GraphQL 的强大功能,只为那些已更改的字段发送数据,但是如果我使用相同的 InputObjectType 进行更新,我会遇到 2 个问题。
- 更新必须包含所有“必填”字段。
- 更新尝试将所有未提供的值设置为默认值。
避免这个问题我看过 HotChocolate 提供的Optional<> 泛型类型。
这需要定义一个新的“更新”类型,如下所示
public class WarehouseUpdate
{
public int Id { get; set; } // Must always be specified
public Optional<string> Code { get; set; }
public Optional<string> CompanyName { get; set; }
public Optional<string> WarehouseName { get; set; }
public Optional<string> Telephone { get; set; }
public Optional<string> VATNumber { get; set; }
}
将其添加到突变中
descriptor.Field("update")
.Argument("input", a => a.Type<InputObjectType<WarehouseUpdate>>())
.Type<ObjectType<Warehouse>>()
.Resolver(async context =>
{
var input = context.Argument<WarehouseUpdate>("input");
var provider = context.Service<IWarehouseStore>();
return await provider.UpdateWarehouse(input);
});
UpdateWarehouse 方法只需要更新那些已被提供值的字段。
public async Task<Warehouse> UpdateWarehouse(WarehouseUpdate input)
{
var item = await _context.Warehouses.FindAsync(input.Id);
if (item == null)
throw new KeyNotFoundException("No item exists with specified key");
if (input.Code.HasValue)
item.Code = input.Code;
if (input.WarehouseName.HasValue)
item.WarehouseName = input.WarehouseName;
if (input.CompanyName.HasValue)
item.CompanyName = input.CompanyName;
if (input.Telephone.HasValue)
item.Telephone = input.Telephone;
if (input.VATNumber.HasValue)
item.VATNumber = input.VATNumber;
await _context.SaveChangesAsync();
return item;
}
虽然这可行,但它确实有几个主要缺点。
- 由于 Enity Framework 不理解
Optional<>泛型类型,每个模型都需要 2 个类 - Update 方法需要为要更新的每个字段提供条件代码 这显然不理想。
实体框架可以与JsonPatchDocument<> 泛型类一起使用。这允许将部分更新应用于实体而无需自定义代码。
但是,我正在努力寻找一种将其与 Hot Chocolate GraphQL 实现相结合的方法。
为了完成这项工作,我正在尝试创建一个自定义 InputObjectType,它的行为就像使用 Optional<> 定义属性并映射到 JsonPatchDocument<> 的 CLR 类型一样。这将通过在反射的帮助下为模型类中的每个属性创建自定义映射来工作。但是,我发现定义框架处理请求方式的某些属性 (IsOptional) 是 Hot Chocolate 框架的内部属性,无法从自定义类中的可覆盖方法访问。
我也考虑过
- 将 UpdateClass 的
Optional<>属性映射到JsonPatchDocument<>对象中 - 使用代码编织生成具有每个属性的
Optional<>版本的类 - 首先覆盖 EF 代码以处理
Optional<>属性
我正在寻找有关如何使用通用方法实现此功能的任何想法,并避免需要为每种类型编写 3 个单独的代码块 - 这些代码块需要彼此保持同步。
【问题讨论】:
-
我们在 graphql-dotnet 中使用 AutoMapper 解决了这个问题,您可以在其中将参数作为字典获取,然后将其映射到实体。这样,未传入的属性将不在字典中,因此不会映射,并且传入值为 null 的属性设置为 null。但是,我无法弄清楚如何从 hotchoclate 中的 IResolverContext 作为字典获取参数。
标签: c# graphql entity-framework-core json-patch hotchocolate