【问题标题】:many-to-many relationship in GraphQLGraphQL 中的多对多关系
【发布时间】: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 中编写多对多关系。

【问题讨论】:

    标签: many-to-many 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]);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2019-11-10
      • 2019-01-31
      • 2020-04-25
      • 2021-01-13
      • 2019-06-06
      • 2018-10-14
      • 2017-10-09
      • 2022-11-13
      • 2019-12-18
      相关资源
      最近更新 更多