GraphQL 中的多对多关系
Posted
技术标签:
【中文标题】GraphQL 中的多对多关系【英文标题】:many-to-many relationship in GraphQL 【发布时间】:2018-09-28 21:41:16 【问题描述】:假设我有两个对象数组:
let movies = [
id: '1', title: 'Erin Brockovich',
id: '2', title: 'A Good Year',
id: '3', title: 'A Beautiful Mind',
id: '4', title: 'Gladiator'
];
let actors = [
id: 'a', name: 'Julia Roberts',
id: 'b', name: 'Albert Finney',
id: 'c', name: 'Russell Crowe'
];
我想在他们之间建立多对多的关系。从 Vanilla javascript 开始,最终在 GraphQL 模式中。
在 JavaScript 中我做了这样的事情:
let movies = [
id: '1', title: 'Erin Brockovich', actorId: ['a', 'b'] ,
id: '2', title: 'A Good Year', actorId: ['b', 'c'] ,
id: '3', title: 'A Beautiful Mind', actorId: ['c'] ,
id: '4', title: 'Gladiator', actorId: ['c']
];
let actors = [
id: 'a', name: 'Julia Roberts', movieId: ['1'] ,
id: 'b', name: 'Albert Finney', movieId: ['1', '2'] ,
id: 'c', name: 'Russell Crowe', movieId: ['2', '3', '4']
];
let actorIds = [];
let movieIds = [];
for (let m = 0; m < movies.length; m ++)
for (let i = 0; i < movies[m].actorId.length; i ++)
actorIds.push(movies[m].actorId[i]);
for (let a = 0; a < actors.length; a ++)
for (let i = 0; i < actors[a].movieId.length; i ++)
movieIds.push(actors[a].movieId[i]);
for (let a = 0; a < actors.length; a ++)
for (let i = 0; i < actorIds.length; i ++)
if ((actors[a].id == 'c') && (actors[a].id == actorIds[i]))
for (let j = 0; j < movies.length; j ++)
if (movies[j].id == movieIds[i])
console.log(movies[j].title);
当我在 Node 中运行前面的代码时,终端会返回
A Good Year
A Beautiful Mind
Gladiator
这正是我想要的。
不幸的是,我迷失在 GraphQL 架构中。到目前为止,我所拥有的——当然是在 fields
函数内部——是这样的:
in_which_movies:
type: new GraphQLList(FilmType),
resolve(parent, args)
let actorIds = [];
let movieIds = [];
for (let m = 0; m < movies.length; m ++)
for (let i = 0; i < movies[m].actorId.length; i ++)
actorIds.push(movies[m].actorId[i]);
for (let a = 0; a < actors.length; a ++)
for (let i = 0; i < actors[a].movieId.length; i ++)
movieIds.push(actors[a].movieId[i]);
for (var a = 0; a < actors.length; a ++)
for (var i = 0; i < actorIds.length; i ++)
if ((actors[a].id == parent.id) && (actors[a].id == actorIds[i]))
for (var j = 0; j < movies.length; j ++)
if (movies[j].id == movieIds[i])
console.log(movies[j].title);
return movies[j].title;
当我在 GraphiQL 中运行以下查询时...
actor(id: "c")
name
in_which_movies
title
...我有这样的回应:
"errors": [
"message": "Cannot read property 'title' of undefined",
"locations": [
"line": 4,
"column": 3
],
"path": [
"actor",
"in_which_movies"
]
],
"data":
"actor":
"name": "Russell Crowe",
"in_which_movies": null
...这对我来说很奇怪,因为终端响应我的预期
A Good Year
A Beautiful Mind
Gladiator
我想到目前为止我写的所有代码都是无用的,我需要一些新的指导来正确地在 GraphQL 中编写多对多关系。
【问题讨论】:
【参考方案1】:TL;DR 我认为你想太多了。您的解析器正在做太多的工作,这导致代码难以推理和调试。
我认为您的问题与 GraphQL 没有太大关系,只是对您的基础数据进行正确的操作。我将尝试从您的示例和 GraphQL 开始逐步完成它,因此我们最终会得到您正在寻找的类型和解析器。
从您的原版代码开始:
let movies = [
id: '1', title: 'Erin Brockovich', actorId: ['a', 'b'] ,
id: '2', title: 'A Good Year', actorId: ['b', 'c'] ,
id: '3', title: 'A Beautiful Mind', actorId: ['c'] ,
id: '4', title: 'Gladiator', actorId: ['c']
];
let actors = [
id: 'a', name: 'Julia Roberts', movieId: ['1'] ,
id: 'b', name: 'Albert Finney', movieId: ['1', '2'] ,
id: 'c', name: 'Russell Crowe', movieId: ['2', '3', '4']
];
我想建议我们将其翻译成由id
索引的内容,以便更容易查询。这也将更好地建模您通常在生产 GraphQL API 后面拥有的数据库或键值存储。
把它翻译成索引的东西,但仍然是普通的 JS:
let movies =
'1': id: '1', title: 'Erin Brockovich', actorId: ['a', 'b'] ,
'2': id: '2', title: 'A Good Year', actorId: ['b', 'c'] ,
'3': id: '3', title: 'A Beautiful Mind', actorId: ['c'] ,
'4': id: '4', title: 'Gladiator', actorId: ['c']
;
let actors =
'a': id: 'a', name: 'Julia Roberts', movieId: ['1'] ,
'b': id: 'b', name: 'Albert Finney', movieId: ['1', '2'] ,
'c': id: 'c', name: 'Russell Crowe', movieId: ['2', '3', '4']
;
接下来,我们应该考虑代表这些类型的 GraphQL 模式。这就是“多对多”部分发挥作用的地方。我认为我们可以非常干净地从您的示例数据中派生出类型:
type Movie
id: ID!
title: String
actors: [Actor]
type Actor
id: ID!
name: String
movies: [Movie]
请注意,[Movie]
是 Movie
对象的列表。即使底层数据包含 id(也就是我们所期望的“标准化”),我们还是根据实际的类型化关系对 API 进行建模。
接下来我们需要设置解析器。让我们看看 Actor
类型的解析器,因为这就是您的示例中的内容。
movies:
type: new GraphQLList(FilmType),
resolve(parent)
// The ids of all the movies this actor is in. "parent" will be the
// actor data currently being queried
let movieIds = parent.movieId;
// We'll build up a list of the actual movie datas to return.
let actorInMovies = [];
for (let m = 0; m < movieIds.length; m++)
// The m'th movie id.
let movieId = movieIds[m];
// The movie data from our indexed "movies" top level object.
// In production, this might access a database service
let movie = movies[movieID];
// Add that movie to the list of movies
actorInMovies.push(movie)
// Then we'll return that list of movie objects.
return actorInMovies;
请注意,在您的原始解析器中,您返回的 movies[j].title
可能是一个字符串,并且与“电影类型列表”所期望的不匹配,并且在我上面的示例中是一个电影数据对象数组被退回。
此外,上面的代码是一种非常冗长的方法,但我认为对每个步骤进行评论会很有帮助。要真正实现多对多,Movie
类型的actors
字段应该具有几乎相同的代码。但是,为了展示如何通过使用 .map()
运算符大大简化此代码的示例,我将用另一种方式编写它:
actors:
type: new GraphQLList(ActorType),
resolve(parent)
// In this case, "parent" will be the movie data currently being queried.
// Use "map" to convert a list of actor ids into a list of actor data
// objects using the indexed "actors" top level object.
return parent.actorId.map(id => actors[id]);
【讨论】:
以上是关于GraphQL 中的多对多关系的主要内容,如果未能解决你的问题,请参考以下文章
在 TypeORM 与 GraphQL 的多对多关系上使用数据加载器,查询多对多
SQL Server Analysis Services 中的多对多关系;第二个多对多关系不起作用