如何将模型与 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】:
你还需要接口吗?
抽象类型(如接口和联合)背后的主要目的是它们允许特定字段解析为一组类型中的一个。如果您有 Contractor
和 Employee
类型并希望特定字段返回任一类型,则添加接口或联合来处理这种情况是有意义的:
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 界面上的“具有一个”关系相关联的主要内容,如果未能解决你的问题,请参考以下文章
如何将单个枚举用于 django 模型和 graphql 突变参数?
如何将 Mongoose 与 GraphQL 和 DataLoader 一起使用?