SQL 带有表连接的 GraphQL 查询

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/40945940/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-01 05:01:18  来源:igfitidea点击:

GraphQL queries with tables join

sqlnode.jssequelize.jsgraphql

提问by itaied

I am learning GraphQLso I built a little project. Let's say I have 2 models, Userand Comment.

我正在学习,GraphQL所以我建立了一个小项目。假设我有 2 个模型,User并且Comment.

const Comment = Model.define('Comment', {

  content: {
    type: DataType.TEXT,
    allowNull: false,
    validate: {
      notEmpty: true,
    },
  },

});

const User = Model.define('User', {

  name: {
    type: DataType.STRING,
    allowNull: false,
    validate: {
      notEmpty: true,
    },
  },

  phone: DataType.STRING,

  picture: DataType.STRING,

});

The relations are 1:many, where a user can have many comments.
I have built the schema like this:

关系是 1:many,用户可以有很多评论。
我已经构建了这样的架构:

const UserType = new GraphQLObjectType({
  name: 'User',
  fields: () => ({
    id: {
      type: GraphQLString
    },
    name: {
      type: GraphQLString
    },
    phone: {
      type: GraphQLString
    },
    comments: {
      type: new GraphQLList(CommentType),
      resolve: user => user.getComments()
    }
  })
});

And the query:

和查询:

const user = {
  type: UserType,
  args: {
    id: {
      type: new GraphQLNonNull(GraphQLString)
    }
  },
  resolve(_, {id}) => User.findById(id)
};

Executing the query for a user and his comments is done with 1 request, like so:

对用户及其评论执行查询是通过 1 个请求完成的,如下所示:

{
  User(id:"1"){
    Comments{
      content
    }
  }
}

As I understand, the client will get the results using 1 query, this is the benefit using GraphQL. But the server will execute 2 queries, one for the user and another one for his comments.
My question is, what are the best practices for building the GraphQLschema and types and combining join between tables, so that the server could also execute the query with 1 request?

据我了解,客户端将使用 1 个查询获得结果,这是使用GraphQL. 但是服务器会执行 2 个查询,一个是针对用户的,另一个是针对他的评论。
我的问题是,构建GraphQL模式和类型以及组合表之间的连接的最佳实践是什么,以便服务器也可以使用 1 个请求执行查询?

回答by marktani

The concept you are refering to is called batching. There are several libraries out there that offer this. For example:

您所指的概念称为批处理。有几个图书馆提供这个。例如:

  • Dataloader: generic utility maintained by Facebook that provides "a consistent API over various backends and reduce requests to those backends via batching and caching"

  • join-monster: "A GraphQL-to-SQL query execution layer for batch data fetching."

  • Dataloader:由 Facebook 维护的通用实用程序,它提供“在各种后端上的一致 API,并通过批处理和缓存减少对这些后端的请求”

  • join-monster:“用于批量数据获取的 GraphQL-to-SQL 查询执行层。”

回答by ceferrari

To anyone using .NET and the GraphQL for .NETpackage, I have made an extension method that converts the GraphQL Query into Entity Framework Includes.

对于使用 .NET 和.NET包的GraphQL 的任何人,我制作了一个扩展方法,将 GraphQL 查询转换为实体框架包含。

public static class ResolveFieldContextExtensions
{
    public static string GetIncludeString(this ResolveFieldContext<object> source)
    {
        return string.Join(',', GetIncludePaths(source.FieldAst));
    }

    private static IEnumerable<Field> GetChildren(IHaveSelectionSet root)
    {
        return root.SelectionSet.Selections.Cast<Field>()
                                           .Where(x => x.SelectionSet.Selections.Any());
    }

    private static IEnumerable<string> GetIncludePaths(IHaveSelectionSet root)
    {
        var q = new Queue<Tuple<string, Field>>();
        foreach (var child in GetChildren(root))
            q.Enqueue(new Tuple<string, Field>(child.Name.ToPascalCase(), child));

        while (q.Any())
        {
            var node = q.Dequeue();
            var children = GetChildren(node.Item2).ToList();
            if (children.Any())
            {
                foreach (var child in children)
                    q.Enqueue(new Tuple<string, Field>
                                  (node.Item1 + "." + child.Name.ToPascalCase(), child));

            }
            else
            {
                yield return node.Item1;
            }
        }}}

Lets say we have the following query:

假设我们有以下查询:

query {
  getHistory {
    id
    product {
      id
      category {
        id
        subCategory {
          id
        }
        subAnything {
          id
        }
      }
    }
  }
}

We can create a variable in "resolve" method of the field:

我们可以在字段的“resolve”方法中创建一个变量:

var include = context.GetIncludeString();

which generates the following string:

它生成以下字符串:

"Product.Category.SubCategory,Product.Category.SubAnything"

and pass it to Entity Framework:

并将其传递给实体框架:

public Task<TEntity> Get(TKey id, string include)
{
    var query = Context.Set<TEntity>();
    if (!string.IsNullOrEmpty(include))
    {
        query = include.Split(',', StringSplitOptions.RemoveEmptyEntries)
                       .Aggregate(query, (q, p) => q.Include(p));
    }
    return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
}