Я пытаюсь создать приложение ASP.NET Core 3.1 с использованием Entity Framework Core и Hot Chocolate. Приложение должно поддерживать создание, запрос, обновление и удаление объектов через 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 для обновлений, я сталкиваюсь с двумя проблемами.
- Обновление должно включать все обязательные поля.
- Обновление пытается установить для всех непредоставленных значений значения по умолчанию.
Чтобы избежать этой проблемы, я рассмотрел общий тип Optional<>
, предоставляемый HotChocolate. Для этого необходимо определить новый тип «Обновления», как показано ниже.
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 должен иметь условный код для обновления каждого поля. Это, очевидно, не идеально.
Entity Framework можно использовать вместе с универсальным классом JsonPatchDocument<>
. Это позволяет применять частичные обновления к сущности без необходимости настраивать код. Однако я изо всех сил пытаюсь найти способ объединить это с реализацией Hot Chocolate GraphQL.
Чтобы выполнить эту работу, я пытаюсь создать собственный InputObjectType, который ведет себя так, как будто свойства определены с помощью Optional<>
и сопоставляются с типом CLR JsonPatchDocument<>
. Это будет работать путем создания настраиваемых сопоставлений для каждого свойства в классе модели с помощью отражения. Однако я обнаружил, что некоторые свойства (IsOptional
), которые определяют способ обработки запроса платформой, являются внутренними по отношению к платформе Hot Chocolate, и к ним нельзя получить доступ из переопределяемых методов в пользовательском классе.
Я также рассмотрел способы
- Сопоставление
Optional<>
свойств UpdateClass с объектомJsonPatchDocument<>
- Использование переплетения кода для создания класса с
Optional<>
версиями каждого свойства - Переопределение кода EF в первую очередь для обработки
Optional<>
свойств
Я ищу любые идеи относительно того, как я могу реализовать это, используя общий подход и избежать необходимости писать 3 отдельных блока кода для каждого типа, которые необходимо синхронизировать друг с другом.