如何将模型与 GraphQL 界面上的“具有一个”关系相关联

Posted

技术标签:

【中文标题】如何将模型与 GraphQL 界面上的“具有一个”关系相关联【英文标题】:How do I associate a model with a "has one" relationship on an GraphQL interface 【发布时间】:2019-09-20 11:33:56 【问题描述】:

我有一个使用 Sequelize/mysql 的 GraphQL/Apollo 服务器。我的 GraphQL 类型(Employee、Contractor)每个都实现了一个 Person 接口。我的数据库模型包含一个员工、承包商和事件表。我希望我的 Person Type 与 Events 有“多重”关系。而我的事件类型“属于”员工或承包商的人员类型。

我猜它与事件类型中的person_id 字段有关。我可以让它在没有单个表“员工”上的界面并将person_id更改为employee_id的情况下工作。所以我猜它只是不知道如何区分 Employee 和 Contractor 来引用该表?

//typeDefs.js

const typeDefs = gql`
  type Event 
    id: ID!
    person_id: ID!
    location: String!
    escort: String!
  

  interface Person 
    id: ID!
    first_name: String!
    last_name: String!
    department_company: String!
    events: [Event]
  

  type Employee implements Person 
    id: ID!
    first_name: String!
    last_name: String!
    department_company: String!
    events: [Event]
    employee_sh_id: String
  

  type Contractor implements Person 
    id: ID!
    first_name: String!
    last_name: String!
    department_company: String!
    events: [Event]
    escort_required: Boolean!
  
//Employee model

module.exports = (sequelize, DataTypes) => 
  const Employee = sequelize.define('Employee', 
    id: 
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true
    ,
    first_name: DataTypes.STRING,
    last_name: DataTypes.STRING,
    department_company: DataTypes.STRING,
    emplyee_sh_id: DataTypes.STRING
  , );
  Employee.associate = function(models) 
      Employee.hasMany(models.Event);
  ;
  return Employee;
;
// Contractor model

module.exports = (sequelize, DataTypes) => 
  const Contractor = sequelize.define('Contractor', 
    id: 
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true
    ,
    first_name: DataTypes.STRING,
    last_name: DataTypes.STRING,
    department_company: DataTypes.STRING,
    escort_required: DataTypes.BOOLEAN,
  , );
  Contractor.associate = function(models) 
      Contractor.hasMany(models.Event);
  ;
  return Contractor;
;
// Event model

module.exports = (sequelize, DataTypes) => 
  const Event = sequelize.define(
    "Event",
    
      id: 
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
      ,
      person_id: DataTypes.INTEGER,
      location: DataTypes.STRING,
      escort: DataTypes.STRING
    ,
    
  );
  Event.associate = function(models) 
    Event.belongsTo(models.Employee),
    Event.belongsTo(models.Contractor)
  ;
  return Event;
;
// resolvers.js

const resolvers = 
  Query: 
    async employee(root,  id ,  models ) 
      return models.Employee.findByPk(id);
    ,
    async contractor(root,  id ,  models ) 
      return models.Contractor.findByPk(id);
    ,
    async employees(root, args,  models ) 
      return models.Employee.findAll();
    ,
    async contractors(root, args,  models ) 
      return models.Contractor.findAll();
    ,
    async event(root,  id ,  models ) 
      return models.Event.findByPk(id);
    ,
    async events(root, args,  models ) 
      return models.Event.findAll();
    
  ,

  Mutation: 
    async addEmployee(
      root,
      
        first_name,
        last_name,
        department_company,
        employee_sh_id
      ,
       models 
    ) 
      return models.Employee.create(
        first_name,
        last_name,
        department_company,
        employee_sh_id
      );
    ,

    async addContractor(
      root,
      
        first_name,
        last_name,
        department_company,
        escort_required,
      ,
       models 
    ) 
      return models.Contractor.create(
        first_name,
        last_name,
        department_company,
        escort_required,
      );
    ,

    async addEvent(
      root,
       person_id, location, escort ,
       models 
    ) 
      return models.Event.create(
        person_id,
        location,
        escort
      );
    ,

  Person: 
    __resolveType: person => 
      if (person.employee) 
        return "Employee";
      
      return "Contractor";
    
  ,

  Employee: 
    events: (parent, args, context, info) => parent.getEvents(),
  ,

  Contractor: 
    events: (parent, args, context, info) => parent.getEvents(),
  
;

【问题讨论】:

你好。感谢您在问题中包含相关代码。不幸的是,说“没有界面也能工作”并不能真正描述具体的问题。如果您在运行服务器或执行查询时看到特定的错误消息,请将其包含在您的问题中。否则,请详细描述您看到的预期行为和实际的意外行为。 另外,如果界面的工作方式有问题,最好看看您是如何为界面实现resolveType 函数的。 @DanielRearden 感谢丹尼尔,抱歉信息不足。我已经添加了 resolver.js 文件中的信息。我只是说我在实现接口之前让它工作了。所以只是一个员工到事件的关系。我只是不确定如何与可以来自两个不同表的人员类型建立“有一个”关系。 我应该将员工和承包商的数据存储在同一个表中吗?当我想获得员工时,我只查询员工字段而不是承包商独有的字段?这对我来说似乎是错误的,但可行。 对不起,我现在明白了。感谢您澄清您的问题。 【参考方案1】:

你还需要接口吗?

抽象类型(如接口和联合)背后的主要目的是它们允许特定字段解析为一组类型中的一个。如果您有 ContractorEmployee 类型并希望特定字段返回任一类型,则添加接口或联合来处理这种情况是有意义的:

type Event 
  contractors: [Contractor!]!
  employees: [Employee!]!
  people: [Person!]! # could be either

如果您不需要此功能,则实际上不需要任何抽象类型。 (旁注:您可以使用接口只是为了强制共享字段的类型之间的一致性,但此时您只是将它们用作验证工具并且它们的存在可能不应影响您设计底层数据层的方式)。

没有灵丹妙药

处理关系数据库中的继承can be tricky 并没有万能的答案。使用 Sequelize 或其他 ORM 时,事情变得更加奇怪,因为您的解决方案也必须在该特定库的限制内工作。这里有几种不同的方法可以解决这个问题,尽管到目前为止还不是一个详尽的列表:

如果您只有几个返回 Person 类型的字段,您可以使用单独的表和单独的模型并自己合并结果。比如:
people: async (event) => 
  const [employees, contractors] = await Promise.all([
    event.getEmployees(),
    event.getContractors(),
  ])
  const people = employees.concat(contractors)
  // Sort here if needed
  return people

这确实意味着您现在要查询数据库两次,并且可能会花费额外的时间进行数据库原本会为您完成的排序。但是,这意味着您可以为 Contractors 和 Employees 维护单独的表和模型,这意味着查询这些实体很简单。

将Contractors 和Employees 集中在一个表下,使用某种type 字段来区分两者。然后,您可以use scopes 帮助您在 Sequelize 中适当地建模关系:
Event.hasMany(models.Person,  as: 'employees', scope: type: 'EMPLOYEE' ... )
Event.hasMany(models.Person,  as: 'contractors', scope: type: 'CONTRACTOR' ... )
Event.hasMany(models.Person,  as: 'people', /** no scope **/ ... )

这很有效,即使将所有东西都放在一张桌子上“感觉”不正确。您必须记住正确确定关联和查询的范围。

如果您将 Sequelize 严格用作 ORM 并且不从 Sequelize 模型生成数据库(即不调用 sync),也可以将 view 建模为 Sequelize 模型。视图的编写和维护有点麻烦,但这将允许您为员工和承包商保留单独的表,同时从其他两个表创建一个可用于查询所有人员的虚拟表。

【讨论】:

以上是关于如何将模型与 GraphQL 界面上的“具有一个”关系相关联的主要内容,如果未能解决你的问题,请参考以下文章

GraphQL 模型上的多个查询

如何将单个枚举用于 django 模型和 graphql 突变参数?

GraphQL 同构模型

如何将 Mongoose 与 GraphQL 和 DataLoader 一起使用?

如何在不创建 Mongoose 模型的情况下将 GraphQL 与 Mongoose 和 MongoDB 一起使用

将 json 模型字段与 django 石墨烯一起使用