如何在GraphQL接口上将模型与“具有一个”关系相关联

时间:2019-05-01 23:56:14

标签: javascript sql graphql sequelize.js apollo-server

我有一个使用Sequelize / mysql的GraphQL / Apollo服务器。我的GraphQL类型(雇员,承包商)均实现一个Person接口。我的数据库模型包含一个雇员,承包商和事件表。我希望我的“人员类型”与“事件”具有“许多”的关系。当我的事件类型“属于”人员类型时,员工或承包商。

我猜想它与事件类型中的person_id字段有关。我可以在没有单个表“ Employee”上的接口并将person_id更改为employee_id的情况下使其工作。因此,我猜测它只是不知道如何区分员工和承包商来引用该表?

//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(),
  }
};

1 个答案:

答案 0 :(得分: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维护单独的表和模型,这意味着查询这些实体非常简单。

  • 使用某种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模型。视图的编写和维护有些麻烦,但是,这使您可以为雇员和承包商保留单独的表,同时从其他两个表创建可用于查询所有人员的虚拟表。