本文介绍了GraphQL 中的多对多关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有两个对象数组:

Let's say I have two Arrays of Objects:

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 模式.

I want to create many-to-many relationship betweet them. For the begining in Vanilla JavaScript , eventually in GraphQL schema.

在 JavaScript 中我做了这样的事情:

In JavaScript I did something like this:

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 中运行前面的代码时,终端会返回

When I run preceding code in Node, Terminal will return

A Good Year
A Beautiful Mind
Gladiator

这正是我想要的.

不幸的是,我迷失在 GraphQL 模式中.到目前为止,我所拥有的——当然是在 fields 函数中——是这样的:

Unfortunately I've get lost in GraphQL schema. What I have so far—inside of fields function of course—is this:

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 中运行以下查询时...

When I run the following query in GraphiQL...

{
    actor(id: "c") {
        name
        in_which_movies {
            title
        }
    }
}

...我有这样的回应:

...I have this response:

{
  "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
    }
  }
}

...这对我来说很奇怪,因为终端按照我的预期响应

...which is strange for me, cuz Terminal is responding as I expected

A Good Year
A Beautiful Mind
Gladiator

我想到目前为止我写的所有代码都是无用的,我需要一些新的指导方针,如何在 GraphQL 中正确编写多对多关系.

I guess all code I've wrote so far is useless and I need some fresh guidelines how to write many-to-many relationship in GraphQL properly.

推荐答案

TL;DR 我认为你想得太多了.您的解析器做了太多工作,导致代码难以推理和调试.

TL;DR I think you're way overthinking things. Your resolvers are doing way too much work and that's resulting in code that's hard to reason about and hard to debug.

我认为您的问题与 GraphQL 没有太大关系,而只是对基础数据进行正确的操作.我将尝试从您的示例和 GraphQL 开始逐步完成它,以便我们最终得到您正在寻找的类型和解析器.

I don't think your question really has much to do with GraphQL but just getting the right operations on your underlying data. I'll try to step through it starting with your example and in terms of GraphQL so we end up with the types and resolvers you're looking for.

从您的原始代码开始:

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 的数据库或键值存储建模.

I'd like to suggest that we translate this into something indexed by id so that it's easier to query. That will also better model a database or key-value store that you'll usually have behind a production GraphQL API.

将其转换为索引的内容,但仍然是普通的 JS:

Translating this to something indexed, but still vanilla 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 模式.这就是多对多"部分发挥作用的地方.我认为我们可以非常干净地从您的示例数据中导出类型:

Next, we should think about the GraphQL schema that will represent these types. Here's where the "many to many" part comes into play. I think we can pretty cleanly derive the types from your example data:

type Movie {
  id: ID!
  title: String
  actors: [Actor]
}

type Actor {
  id: ID!
  name: String
  movies: [Movie]
}

请注意,[Movie] 是一个 Movie 对象列表.即使底层数据包含 id(也就是规范化",这是我们所期望的),我们还是根据实际类型化关系对 API 进行建模.

Note that [Movie] is a list of Movie objects. Even though the underlying data contains ids (aka "normalized", which is what we would expect) we model the API in terms of the actual typed relationships.

接下来我们需要设置解析器.让我们看看 Actor 类型的解析器,因为这就是您的示例中的内容.

Next we need to set up the resolvers. Let's look at the Actor type's resolvers since that's what is in your example.

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,它可能是一个字符串,并且与电影类型列表"所期望的不匹配,并且在我上面的例子中,返回了一个电影数据对象数组.

Notice that in your original resolver, you returned movies[j].title which is probably a string, and doesn't match up with what would be expected by "List of FilmType", and in my example above an array of movie data objects is returned.

此外,上面的代码是一种非常冗长的方法,但我认为对每个步骤进行评论会有所帮助.要真正实现多对多,那么 Movie 类型的 actors 字段应该具有几乎相同的代码.然而,只是为了展示如何通过使用 .map() 运算符来大大简化此代码的示例,我将用另一种方式编写:

Also, the code above is a quite verbose way to do this, but I thought it would be helpful to comment on each step. To be truly many-to-many, then the Movie type should have nearly identical code for it's actors field. However just to show an example of how this code can be greatly simplified by using the .map() operator, I'll write it another way:

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 中的多对多关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-06 13:28