We are building a GraphQL
server using Hot Chocolate 13.2
. Our application has a very common scenario with a Code-First
approach and a SQL
Server database. Operations are 100% destined for CRUD, and, for each model
that we have on the application there is a DbSet in a single context.
public DbSet<Currency> Currencies { get; set; }
public DbSet<Language> Languages { get; set; }
// Too many another contexts.
Since there is an expressive quantity of models, the number of mutations and query types on the code is growing too much. Furthermore, the process for creating new mutations are basically a copy/paste, so we started having too many classes doing exactly the same thing (except by the Type that changes for each class).
The problem:
From what we know, Hot Chocolate does not allow different Query and Mutations types being exposed with the same method name across these classes, that's why we cannot use Generic Methods. Example:
public class BaseMutation<T> : Validator where T : BaseEntity
{
public static T Add([Service]MyDbContext context, ClaimsPrincipal claims, [Argument]T model)
{
// Do some stuff
context.Set<T>().Add(model);
context.SaveChanges();
}
}
Our question:
Does anyone know a way to represent different query and mutation types in a single class? Or even, to create a unique class for Query Type objects and another class for the Mutation Types, with the possibility of having a on the calls?
By the way, I found an class attribute called "GraphQLName", that permits us for changing the operation name. Is there any way for dynamically change this value to represent all these queries and mutations in a single class?
[GraphQLName("dynamic-name")]
public static T <DYNAME_METHOD_NAME>([Service]MyContext context, ClaimsPrincipal claims, [Argument] T model)
{
// Do some stuff
context.Set<T>().Add(model);
context.SaveChanges();
}
You could use the fluent API and write extension methods on top of it.
public class QueryType : ObjectType
{
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.AddEntity<Foo>("foo");
descriptor.AddEntity<Bar>("bar");
}
}
public static class QueryExtensionMethods
{
public static IObjectTypeDescriptor AddEntity<T>(
this IObjectTypeDescriptor descriptor,
string name) where T: IHasId
{
descriptor.Field($"{name}ById")
.Argument("id", x => x.ID(typeof(T).Name))
.Resolve(x =>
{
var dbContext = x.Service<MyDbContext>();
var id = x.ArgumentValue<Guid>("id")
return dbContext.Set<T>().FirstOrDefault(x => x.Id == id);
})
.Type<ObjectType<T>>();
}
}
But, to also mention this here. This is not really GraphQL best practice. In GraphQL you should think beyond CURD an implement mutations and query in a way that you actually want them to be consumed, rather than exposing a generic solution.
you rather have mutations changeAddress
or changeUserName
rather than a combinded mutation updateUser