关系

如果没有关系,您的类型定义会更像是一组断开的节点,没有太大价值。在您的数据模型中添加关系可以为您的数据提供必要的上下文,以便在图表的各个部分运行复杂查询。

本页介绍如何为简单的连接模型编写类型定义,通过模式插入数据,然后查询数据。

类型定义

以以下图表为例,其中 Person 类型有两种不同的关系类型,可以将其连接到 Movie 类型。

relationships
图 1. 示例图表

要使用 Neo4j GraphQL 库创建该图表,首先需要定义节点并定义此模型中的两种不同类型。

type Person {
    name: String!
    born: Int!
}

type Movie {
    title: String!
    released: Int!
}

然后可以使用 @relationship 指令将这两种类型连接在一起。

type Person {
    name: String!
    born: Int!
    actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
    directedMovies: [Movie!]! @relationship(type: "DIRECTED", direction: OUT)
}

type Movie {
    title: String!
    released: Int!
    actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN)
    director: Person! @relationship(type: "DIRECTED", direction: IN)
}

请注意,在此查询中

  • 一个 Person 可以出演执导多部电影,而一部 Movie 可以有多个演员。但是,一部 Movie 很少会有不止一位导演,因此您可以在类型定义中对这种基数进行建模,以确保数据的准确性。

  • 一部 Movie 实际上不是一部 Movie,除非它有导演,并且这已通过将 director 字段标记为不可为空来表示。这意味着 Movie 必须有一个指向它的 DIRECTED 关系才能有效。

  • 要确定 @relationship 指令的 direction 参数应该是 IN 还是 OUT,请像上面的图示一样可视化您的关系,然后建模箭头方向。

  • @relationship 指令是对 Neo4j 关系的引用,而在模式中,使用短语 edge(s) 来与 Relay 使用的通用 API 语言保持一致。

关系属性

您可以通过两个步骤将关系属性添加到示例中。

  1. 添加一个用 @relationshipProperties 指令装饰的类型定义,其中包含所需的属性。

  2. @relationship 指令的两个“侧”(或您喜欢的任何一侧)都添加一个 properties 参数,该参数指向新定义的接口。

例如,假设您想区分演员在电影中扮演的角色

type Person {
    name: String!
    born: Int!
    actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
    directedMovies: [Movie!]! @relationship(type: "DIRECTED", direction: OUT)
}

type Movie {
    title: String!
    released: Int!
    actors: [Person!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
    director: Person! @relationship(type: "DIRECTED", direction: IN)
}

type ActedIn @relationshipProperties {
    roles: [String!]
}

@declareRelationship

如果您需要在接口上使用关系,则需要使用新的 @declareRelationship 指令,并定义具体类型中的关系

interface Production {
    title: String!
    actors: [Actor!]! @declareRelationship
}

type Actor {
    name: String!
    born: Int!
    actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
}

type Movie implements Production {
    title: String!
    released: Int!
    actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
}

type Series implements Production {
    title: String!
    released: Int!
    episodes: Int!
    actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
}

type ActedIn @relationshipProperties {
    roles: [String!]
}

queryDirection

所有关系都有方向。但是,在查询它们时,可以执行 无向查询。要设置关系在查询时的默认行为,可以使用参数 queryDirection

type Person {
    name: String!
    born: Int!
    actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, queryDirection: DEFAULT_DIRECTED)
}

queryDirection 可以具有以下值

  • DEFAULT_DIRECTED (默认):默认情况下,所有查询都是有向的,但用户可以执行无向查询。

  • DEFAULT_UNDIRECTED:默认情况下,所有查询都是无向的,但用户可以执行有向查询。

  • DIRECTED_ONLY:只能对该关系执行有向查询。

  • UNDIRECTED_ONLY:只能对该关系执行无向查询。

插入数据

嵌套变异意味着您可以通过 GraphQL 模式以多种方式将数据插入数据库中。请考虑前面提到的规则,即不能在不添加导演的情况下创建 Movie 节点。但是,您可以先创建一个导演节点,然后创建并将其连接到 Movie。另一种选择是在同一变异中创建 MovieDirector,例如

mutation CreateMovieAndDirector {
    createMovies(input: [
        {
            title: "Forrest Gump"
            released: 1994
            director: {
                create: {
                    node: {
                        name: "Robert Zemeckis"
                        born: 1951
                    }
                }
            }
        }
    ]) {
        movies {
            title
            released
            director {
                name
                born
            }
        }
    }
}

然后,您需要在本例中创建演员,并将它们连接到新的 Movie 节点,并指定他们扮演的角色

mutation CreateActor {
    createPeople(input: [
        {
            name: "Tom Hanks"
            born: 1956
            actedInMovies: {
                connect: {
                    where: {
                        node: { title: "Forrest Gump" }
                    }
                    edge: {
                        roles: ["Forrest"]
                    }
                }
            }
        }
    ]) {
        movies {
            title
            released
            director {
                name
                born
            }
            actorsConnection {
                edges {
                    roles
                    node {
                        name
                        born
                    }
                }
            }
        }
    }
}

请注意 actorsConnection 字段的选择,以便查询 roles 关系属性。

还要注意,在第二次变异中,返回了整个图表。这不是必需的,因为您可以将这些变异压缩为一个插入所有必要数据的单一操作

mutation CreateMovieDirectorAndActor {
    createMovies(input: [
        {
            title: "Forrest Gump"
            released: 1994
            director: {
                create: {
                    node: {
                        name: "Robert Zemeckis"
                        born: 1951
                    }
                }
            }
            actors: {
                create: [
                    {
                        node: {
                            name: "Tom Hanks"
                            born: 1956
                        }
                        edge: {
                            roles: ["Forrest"]
                        }
                    }
                ]
            }
        }
    ]) {
        movies {
            title
            released
            director {
                name
                born
            }
            actorsConnection {
                edges {
                    roles
                    node {
                        name
                        born
                    }
                }
            }
        }
    }
}

认识到这一点有助于您在一个变异中一次创建更大的子图表,从而更有效地进行操作。

获取您的数据

现在您已将 Movie 信息存储在数据库中,您可以按如下方式查询所有信息

query {
    movies(where: { title: "Forrest Gump" }) {
        title
        released
        director {
            name
            born
        }
        actorsConnection {
            edges {
                roles
                node {
                    name
                    born
                }
            }
        }
    }
}

基数

Neo4j GraphQL 库对“多”关系有类型定义要求。例如

type User {
    name: String!
    posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT)
}

type Post {
    name: String!
}

User.posts 处的关系被视为“多”关系,这意味着它始终应为 NonNullListTypeNonNullNamedType 类型。换句话说,数组和数组内的类型都应该有 !