我应该如何在没有 `include` 的情况下使用 graphql-sequelize 急切地加载关联?

How am I supposed to eagerly load an association using graphql-sequelize without `include`?

我正在使用 graphql-sequelize 来实现 GraphQL 端点。我刚刚创建了一个新的辅助实体并将其与我的一个主要实体相关联,现在正尝试在获取主要实体时包括相关的辅助实体。当我尝试实现该提取时出现以下错误:

AssertionError ERR_ASSERTION Include support has been removed in favor of dataloader batching

我的基于解析器的旧 gql 端点如下所示:

driver: {
    type: MyGQLTypes.Driver,
    resolve: resolver(Driver)
}

我从代码库中其他地方的实验得知这通常有效:

const driver = Driver.getById(`${uuid}`, {
    include: { model: Helmet }
})
// yields:
// {
//     id: '<uuid>',
//     name: 'Tom',
//     helmetNumber: '470',
//     Helmet: {
//         number: '470',
//         type: 'cool',
//         color: 'red'
//     }
// }

因此,在检查 source code of graphql-sequelize's resolver 并确定第二个参数传递给 sqlz 访问器后,我将相同的 include 添加到我的 GQL 解析器,如下所示:

driver: {
    type: MyGQLTypes.Driver,
    resolve: resolver(Driver, {
        include: { model: Helmet }
    })
}

那是我遇到致命错误的时候。研究表明 graphql-sequelize 人员现在不允许 include 用于 performance reasons,支持 sequelize 的数据加载器,我目前没有使用也没有计划使用它。

但即使我是,也没有回答他们希望我们如何在 resolver 中获取关联行的问题。我发现 a cryptic comment 表明有一个名为 join 的替代方案,但我无法找到它的文档,并且从臀部射击失败:

driver: {
    type: MyGQLTypes.Driver,
    resolve: resolver(Driver, {

        include: { model: Helmet } // fatal error: disallowed

        join: { model: Helmet } // no effect, no error

        join: Helmet // no effect, no error

    })
}

我能想到的就是添加一个 after 例程来手动获取关联的模型,然后将该结果附加到主模型的相同位置,如果它已被获取标准方式(即使用 include),像这样:

driver: {
    type: MyGQLTypes.Driver,
    resolve: resolver(Driver, {

        // can't do this:
        include: { model: Helmet }

        // so I try to emulate it this way:
        after: async (driver) => {

            driver.Helmet = await driver.getHelmet()

            return driver
        }

    })
}

当该代码执行并找到我期望的模型时,它无法将找到的模型附加到同一个地方。

Sqlz 实例不是普通哈希:它们有一个 dataValues 属性,其中包含来自数据库的原始列值,当您引用 myModel.colName 时,一个隐藏的 getter 从 myModel.dataValues[ colName ] 中获取值。实验表明,通过 include 进行预加载会导致关联模型出现在 dataValues 结构中,可能连同所需的任何隐藏访问器一起出现,如下所示:

{
    dataValues: {
        id: '<uuid>',
        name: 'Tom',
        helmetNumber: '470',
        Helmet: {
            dataValues: {
                number: '470,
                type: 'cool',
                color: 'red'
            }
        }
    }
}

我很确定我可以将获取的模型附加到那里:

driver.dataValues.Helmet = await driver.getHelmet()

但我认为我们应该避免摆弄 dataValues,这看起来很笨拙。

我正在使用 apollo-server-express,它(正确地)根据 GQL 模式验证解析器 return 值,因此,我对 Driver 的 GQL 类型定义在技术上是 这个解析函数的下游。我已经开始构建一堆糖来组成 GQL 类型(特别是当涉及到将次级实体嵌入初级时),并且这种观点鼓励我认为基于 resolver 的端点应该具有统一形状 returns。 IE。如果某些实体的解析器发出损坏的 sqlz 模型实例,而其他实体 return 发出健康的解析器,那将很糟糕。

我们应该如何在没有 include 的情况下在 graphql-sequelize 的 resolver 中实现预加载?

包版本:

apollo-server-express@1.3.2
graphql-sequelize@5.6.1
sequelize@4.35.1
dataloader-sequelize@1.6.3

我尝试了两件事:

driver: {
    type: MyGQLTypes.Driver,
    resolve: resolver(Driver, {
        after: driver => {
            return Driver.findById(driver.id, {
                include: [ Helmet ]
            })
        }
    })
}

有效,但它击中了主要 table 两次,非常恶心。

所以,我决定直接扔掉graphql-sequelize的"helper"resolver,直接编程到apollo-server-express的API:

driver: {
    type: MyGQLTypes.Driver,
    resolve: (parent, args, context, info) => {
        return Driver.findById(args.id, {
            include: [ Helmet ]
        })
    })
}

像冠军一样工作。可能有一个我还没有看到的缺点。我想时间会证明一切。

将此问题暂时搁置,以防其他人(包括我自己)有更好的答案。