We have a decent sized dataset that we are using graphql dotnet to query. We have custom logic in place to parse the GQL query string and generate a function that will select the appropriate fields from the database and not the full object.
As such, the 'Query' object in our schema has a load of almost identical calls to Field
in it which just look an object up by it's Id, issue the parsed select command, and return the resulting object.
For example:
// expression based department query
Field<DepartmentType>(
"department",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "organisationId" },
new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "employerId" },
new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "Id" }),
resolve: context =>
{
var query = context.Document.OriginalQuery.Substring(context.Document.OriginalQuery.IndexOf("{"));
var organisationId = new Guid(context.GetArgument<string>("organisationId"));
var employerId = new Guid(context.GetArgument<string>("employerId"));
var Id = new Guid(context.GetArgument<string>("Id"));
appContext.OrganisationId = organisationId;
appContext.EmployerId = employerId;
var retVal = database.Departments()
.Where(x => x.Id == Id && x.Deleted != true);
var selectStatement = GraphDocumentParser.GenerateSelect(query, "department");
var builder = new ExpressionBuilder();
var statement = builder.BuildSelector<Department, Department>(selectStatement);
var result = retVal.Select(statement);
return result.FirstOrDefault();
}).AuthorizeWith(Constants.GraphQL.Policies.API_ACCESS);
So I thought that I could just use reflection to loop our graphql types like this:
var queryableObjectTypes = Assembly.GetExecutingAssembly().GetTypes()
.Where(x => x.IsClass && x.Namespace == "my.namespace.query.api" && !x.FullName.Contains("+<>c"))
.ToList();
//Loop through our GQL types
foreach (var queryableObjectType in queryableObjectTypes)
{
Console.WriteLine(queryableObjectType.FullName);
//Grab the fields property from the type
var fieldsProperty = queryableObjectType.GetProperties().FirstOrDefault(x => x.Name == "Fields");
//If this has our 'fields' property which we require
if (fieldsProperty != null)
{
//Get the instance of this
var instanceOfType = database.GetServiceProvider().GetService(queryableObjectType);
//If this fields is the type we definitely want - i.e.actually get the value from the
// instance and check it
if (fieldsProperty.GetValue(instanceOfType) is GraphQL.Types.TypeFields typeFields)
{
//If this type contains an employerid, then we need to generate a query for this
if (typeFields.Any(x => x.Name.ToLower() == "employerid"))
{ ... } //These are the objects that I want to generate fields for
Which does give me the correct collection of GQL Type objects that I can loop over and use to add a field (any one of our objects with an employerId... which I will blacklist a few of later on, but that would be a simple list to skip over)
I can then parse various bits like the 'base' entity that this type points to, and get most of the required data for the Field to add to our GQL query.
I am able to get almost everything I need to create a field type to add to our query... as shown here:
var f = new FieldType();
f.Name = genericTypeNameFormatted;
f.Description = string.Empty;
f.Type = queryableObjectType;
f.Resolver = ????????
f.Arguments = new QueryArguments(
new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "organisationId" },
new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "employerId" },
new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "Id" });
f.AuthorizeWith(Constants.GraphQL.Policies.API_ACCESS);
However, I am totally stuck with the mix of generics etc.. required to create the resolver. It's definition is as follows:
public class AsyncFieldResolver<TSourceType, TReturnType> : IFieldResolver<Task<TReturnType>>
{
private readonly Func<IResolveFieldContext<TSourceType>, Task<TReturnType>> _resolver;
/// <inheritdoc cref="AsyncFieldResolver{TReturnType}.AsyncFieldResolver(Func{IResolveFieldContext, Task{TReturnType}})"/>
public AsyncFieldResolver(Func<IResolveFieldContext<TSourceType>, Task<TReturnType>> resolver)
{
_resolver = resolver ?? throw new ArgumentNullException(nameof(resolver), "A resolver function must be specified");
}
/// <inheritdoc cref="AsyncFieldResolver{TReturnType}.Resolve(IResolveFieldContext)"/>
public Task<TReturnType> Resolve(IResolveFieldContext context) => _resolver(context.As<TSourceType>());
object IFieldResolver.Resolve(IResolveFieldContext context) => Resolve(context);
}
Is it possible to create a Func<IResolveFieldContext<TSource>, Task<TReturn>>
using data from reflection etc? or is it actually not possible?
Aucun commentaire:
Enregistrer un commentaire