bigtv
bigtv

Reputation: 2711

Advice on building graphQL queries in c#

I am working on a data migration project - API to API.

The destination API is graphQL, we have a number of objects to push into the destination and the shapes vary so I am looking for some advice on how best to dynamically build mutations/queries specifically in c#.

Currently we are just using templates and using find/replace routines to inject values. While this approach does work as the shapes of the data vary this becomes evermore complex and inelegant.

I am looking for any advice/pointers from anyone who have may have had a similar scenario or knows of any libraries I should look at.

Upvotes: 5

Views: 7772

Answers (2)

Davydenko Roman
Davydenko Roman

Reputation: 126

I developed simple library for translating linq-expressions to graphql-queries. It may be useful for back-to-back grapqhl-queries.

https://github.com/RDavydenko/SmartGraphQLClient

  1. Install Nuget-package:
Install-Package SmartGraphQLClient
  1. Add services to DI:
services.AddSmartGraphQLClient();
  1. Use linq-syntax:
using SmartGraphQLClient;

GraphQLHttpClient client = ... // from DI

var users = await client.Query<UserModel>("users")
    .Include(x => x.Roles)
    .ThenInclude(x => x.Users)
    .Where(x => x.UserName.StartsWith("A") || x.Roles.Any(r => r.Code == RoleCode.ADMINISTRATOR))
    .Select(x => new 
    {
        x.Id,
        Name = x.UserName,
        x.Roles,
        IsAdministrator = x.Roles.Any(r => r.Code == RoleCode.ADMINISTRATOR)
    })
    .Skip(5)
    .Take(10)
    .Argument("secretKey", "1234")
    .ToListAsync();

Your request will be translated to graphql-query:

{ 
    users (
      where: {
        or: [ 
          { userName: { startsWith: "A" } }
          { roles: { some: { code: { eq: ADMINISTRATOR } } } }
        ]
      }
      skip: 5
      take: 10
      secretKey: "1234"
  ) {
        id
        userName
        roles {
            code
            name
            description
            id
            users {
                userName
                age
                id
            }
        }
    }
}

Upvotes: 3

no1spirite
no1spirite

Reputation: 638

Update - 13/02/2018

I have since updated this monstrosity to cater for nested sub selections and GraphQl enums so if anyone is interested here it is in GitHub

Orignal answer

I've got the same requirement. Couldn't find anything out there so I've come up with this very inelegant solution. It works for my scenarios so I'll post it here for anyone else looking for a solution.

 public static class GraphQlObjectParser
{
    public static string Parse(string queryType, string queryName, string[] subSelection, object @object = null, string objectTypeName = null)
    {
        var query = queryType + "{" + queryName;

        if (@object != null)
        {
            query += "(";

            if (objectTypeName != null)
            {
                query += objectTypeName + ":" + "{";
            }

            var queryData = string.Empty;
            foreach (var propertyInfo in @object.GetType().GetProperties())
            {
                var value = propertyInfo.GetValue(@object);
                if (value != null)
                {
                    var type = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
                    var valueQuotes = type == typeof(string) ? "\"" : string.Empty;

                    var queryPart = char.ToLowerInvariant(propertyInfo.Name[0]) + propertyInfo.Name.Substring(1) + ":" + valueQuotes + value + valueQuotes;
                    queryData += queryData.Length > 0 ? "," + queryPart : queryPart;
                }
            }
            query += (objectTypeName != null ? queryData + "}" : queryData) + ")";
        }

        if (subSelection.Length > 0)
        {
            query += subSelection.Aggregate("{", (current, s) => current + (current.Length > 1 ? "," + s : s)) + "}";
        }

        query += "}";

        return query;
    }
}

This works for both queries and mutations. Examples of usage are:

var query = GraphQlObjectParser.Parse("query", "users", new[] { "id", "name" });

will give you

query{users{id,name}}

or

var query = GraphQlObjectParser.Parse("query", "user", new[] { "id", "name" }, new User { Id = 1 });

will give you

query{user(id:1){id,name}}

or

var query = GraphQlObjectParser.Parse("mutation", "user", new[] { "id", "name" }, new User { Id = 1, Name = "John" }, "data");

will give you

mutation{user(data:{id:1,name:"John"}){id,name}}

It'll work with enums which is why I needed this solution in the first place. You can pass in a sub selection without the object or the object without an object type name. I've tried to cover as many bases as possible although I've not yet catered for sub selection of a sub selection. I'll update here if/when I code this one.

Hope it helps someone.

Upvotes: 6

Related Questions