首页 文章

使用表连接的GraphQL查询

提问于
浏览
18

我正在学习 GraphQL 所以我建了一个小项目 . 假设我有2个型号, UserComment .

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,

});

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

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()
    }
  })
});

和查询:

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

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

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

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

2 回答

  • 10

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

    • Dataloader:由Facebook维护的通用实用程序,提供"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."

  • 2

    对于使用.NET和GraphQL for .NET包的任何人,我已经制作了一个将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;
                }
            }
        }
    }
    

    可以说我们有以下查询:

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

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

    var include = context.GetIncludeString();
    

    生成以下字符串:

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

    并将其传递给Entity Framwork:

    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));
    }
    

相关问题