graphql-dotnet

Best pattern to manually parse arguments for query optimisation in GraphQL dotnet


We have a GraphQL dotnet implementation sitting on top of our existing app database.

<PackageReference Include="GraphQL" Version="3.3.2" />
<PackageReference Include="GraphQL.SystemTextJson" Version="3.3.2" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="4.4.1" />
<PackageReference Include="GraphQL.Server.Transports.WebSockets" Version="4.4.1" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore.SystemTextJson" Version="4.4.1" />
<PackageReference Include="GraphQL.Server.Ui.Playground" Version="4.4.1" />
<PackageReference Include="GraphQL.Server.Authorization.AspNetCore" Version="4.4.1" />

We have a fairly intertwined data structure, so when querying from some of our top level fields, and including some of their sub fields, we could be joining on a large number of tables. However, not every query would need all those joins.

I was hoping to be able to manually parse the context.Arguments in our Query ObjectGraphType when I am resolving it to eliminate the joins if a particular sub-field is not queried.

A very simple high level version of this would be as follows:

Field<ListGraphType<OrganisationType>>(
"organisations",
resolve: context =>
{
    var retVal = database.Organisations();

    //Are we joining on employers?
    if(context.SubFields.ContainsKey("employers"))
    {
        retVal.Include(x => x.Employers.Where(x => x.Deleted != true))
                .ThenInclude(x => x.Departments.Where(x => x.Deleted != true));
    }

    return retVal;          
}

Where we only join on employers, if the user has queried that. However, the issue is that employers can have departments, employees, managers etc etc... and those themselves can have a large number of sub-properties.

Currently our query joins on pretty much every permutation of the query, producing a really hefty SQL query. Which is a lot of heavy lifting if the user only wants, the organisation name, and each employers name.

Filtering on the very top level is easy enough (as shown above) but I can't figure out how to query from that point onwards, I just seem to end up in an infinite loop of Children... for example:

var employerSubField = context.SubFields["employers"];
var otherJoins = employerSubField.SelectionSet.Children.Where(x => x.Children)

I can't seem to get a where name == "Employees" or anything like that. Do I need to cast the GraphQL.Language.AST.INode to something at some point? When I inspect the values at debug time it looks like I should be able to see the values I am after. But this won't compile.

var deptsField = employerSubField.SelectionSet.Selections.Where(x => x.Name == "departments");

Debug information


Solution

  • I have found the following way to get the basic information out that I can then easily parse myself and add my own custom logic.

    var query = context.Document.OriginalQuery.Substring(context.Document.OriginalQuery.IndexOf("{")).RemoveWhitespace();
    

    returns:

    {organisations{id,name,employers{id,name,clientNotes,departments{id,name}}}}