Sequelize关联报错求助:include查询提示关联不存在
Sequelize关联查询报错:Association with alias "EmployeePosition" does not exist on "Employee"
尝试用Sequelize的关联功能查询数据,使用include选项时抛出错误:Association with alias "EmployeePosition" does not exist on "Employee"。即使修改include为{model: EmployeePosition,as: 'position'},也会出现新错误:EmployeePosition is not associated to Employee!。已尝试别名、指定外键、调整复数形式等方法,确认关联静态方法已执行、迁移顺序正确,但问题仍未解决。
模型代码
Employee 模型
'use strict'; const {Model} = require('sequelize'); module.exports = (sequelize, DataTypes) => { class Employee extends Model { static associate(models) { Employee.hasMany(models.EmployeePosition) } } Employee.init({ id: { allowNull: false, autoIncrement: true, primaryKey: true, type: DataTypes.INTEGER, }, name: DataTypes.STRING, bornDate:{ type: DataTypes.DATEONLY, allowNull: false }, address:{ type: DataTypes.STRING, allowNull:false }, hireDate:{ type: DataTypes.DATEONLY, }, curp:{ type: DataTypes.STRING(18), allowNull: false }, email: DataTypes.STRING, salary: { type: DataTypes.DECIMAL(10,2), allowNull: false }, rfc:{ type: DataTypes.STRING(13), }, nss:{ type: DataTypes.STRING(11) }, telephone:{ type: DataTypes.STRING(13), allowNull: false }, driverLicense:{ type: DataTypes.STRING() }, ineFrontImageType:{ type: DataTypes.STRING, }, ineFrontImageName: { type: DataTypes.STRING, }, ineFrontImageData: { type: DataTypes.BLOB('long') }, ineBackImageType:{ type: DataTypes.STRING, }, ineBackImageName: { type: DataTypes.STRING, }, ineBackImageData: { type: DataTypes.BLOB('long') }, }, { underscored: true, paranoid: true, sequelize, modelName: 'Employee', tableName: 'hrm_employees', }); return Employee; };
EmployeePosition 模型
'use strict'; const { Model } = require('sequelize'); module.exports = (sequelize, DataTypes) => { class EmployeePosition extends Model { static associate(models) { EmployeePosition.belongsTo(models.Employee) EmployeePosition.belongsTo(models.Position) } } EmployeePosition.init({ id: { allowNull: false, autoIncrement: true, primaryKey: true, type: DataTypes.INTEGER, }, employeeId:{ allowNull: false, type: DataTypes.INTEGER, references:{ model: { tableName: 'hrm_employees' }, key: 'id' }, onUpdate: 'CASCADE', }, positionId:{ allowNull: false, type: DataTypes.INTEGER, references:{ model:{ tableName: 'hrm_positions' }, key: 'id' }, onUpdate: 'CASCADE', }, }, { modelName: 'EmployeePosition', underscored: true, paranoid: true, sequelize, tableName: 'hrm_employees_positions', }); return EmployeePosition; };
迁移文件代码
Employee 迁移
'use strict'; /** @type {import('sequelize-cli').Migration} */ module.exports = { async up(queryInterface, Sequelize) { await queryInterface.createTable('hrm_employees', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER, }, name: { type: Sequelize.STRING, allowNull: false }, born_date:{ type: Sequelize.DATEONLY, allowNull: false }, address:{ type: Sequelize.STRING, allowNull:false }, hire_date:{ type: Sequelize.DATEONLY, }, curp:{ type: Sequelize.STRING(18), allowNull: false }, email: Sequelize.STRING, salary: { type: Sequelize.DECIMAL(10,2), allowNull: false }, rfc:{ type: Sequelize.STRING(13), }, nss:{ type: Sequelize.STRING(11) }, telephone:{ type: Sequelize.STRING(13), allowNull: false }, driver_license:{ type: Sequelize.STRING() }, ine_front_image_type:{ type: Sequelize.STRING, }, ine_front_image_name: { type: Sequelize.STRING, }, ine_front_image_data: { type: Sequelize.BLOB('long') }, ine_back_image_type:{ type: Sequelize.STRING, }, ine_back_image_name: { type: Sequelize.STRING, }, ine_back_image_data: { type: Sequelize.BLOB('long') }, created_at: { allowNull: false, type: Sequelize.DATE }, updated_at: { allowNull: false, type: Sequelize.DATE }, deleted_at: { type: Sequelize.DATE } }); }, async down(queryInterface, Sequelize) { await queryInterface.dropTable('hrm_employees'); } };
EmployeePosition 迁移
'use strict'; /** @type {import('sequelize-cli').Migration} */ module.exports = { async up(queryInterface, Sequelize) { await queryInterface.createTable('hrm_employees_positions', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER }, employee_id: { type: Sequelize.DataTypes.INTEGER, references: { model: { tableName:'hrm_employees' }, key: 'id' }, allowNull: false }, position_id: { type: Sequelize.DataTypes.INTEGER, references: { model: { tableName: 'hrm_positions', }, key: 'id' }, allowNull: false }, created_at: { allowNull: false, type: Sequelize.DATE }, updated_at: { allowNull: false, type: Sequelize.DATE }, deleted_at: { type: Sequelize.DATE } }); }, async down(queryInterface, Sequelize) { await queryInterface.dropTable('hrm_employees_positions'); } };
请求处理代码
const {Sequelize, Op} = require('sequelize'); const sequelize = require('../../database/database').sequelize; const Employee = require('../../models/Employee')(sequelize, Sequelize.DataTypes,Sequelize.Model); const boom = require('@hapi/boom'); const create = async (req, res, next) => { try{ const data = req.body const {name,born_date, address, hire_date, curp, email, salary, rfc, nss, telephone} = data; let nameLowerCase = name.toLowerCase(); const dateOfBirth = new Date(born_date); const hireDate = new Date(hire_date); const employee = await Employee.create({ name: nameLowerCase, bornDate: dateOfBirth, address, hireDate, curp, email, salary, rfc, nss, telephone, }); res.json({message: "created", data: employee}); }catch(err){ next(err) } } const update = async (req, res, next) =>{ try{ let employee = await Employee.findByPk(req.params.employee_id); if(!employee) throw boom.notFound('Empleado no encontrado'); employee= await employee.update(req.body) res.json({message: "updated", data: employee}); }catch(err){ next(err); } } const get = (async (req,res, next) =>{ try{ const employee = await Employee.findByPk(req.params.employee_id, {include: ['EmployeePosition']}); if(!employee) throw boom.notFound('Empleado no encontrado'); res.json({message: "found", data: employee}); }catch(err){ next(err); } }) const index = (async (req, res, next) =>{ try{ const{name, limit, page, telephone} = req.query; const options = { where:{} } options.include = ['EmployeePosition']; if(limit && page){ options.limit = limit options.page = page } if(name) options.where.name = { [Op.like]:`%${name}%` }; if(telephone) options.where.telephone = { [Op.like]:`%${telephone}%` }; const employees = await Employee.findAll(options); if(employees.length == 0) throw boom.notFound('Empleados no encontrados'); res.json({message: "found", data: employees}); }catch(err){ next(err); } }) const destroy = (async (req, res, next) =>{ try{ const employee = await Employee.findByPk(req.params.employee_id); if(!employee) throw boom.notFound('Empleado no encontrado'); await employee.destroy(); res.json({message: "deleted", data: employee}); }catch(err){ next(err); } }) module.exports = {create, update,get,index, destroy};
问题排查与解决方案
核心问题分析
问题出在模型加载方式和关联别名匹配上:
- 你的请求处理代码中直接调用模型函数创建实例,没有通过Sequelize统一的
models对象加载,导致关联未被正确初始化。 hasMany关联默认会生成复数形式的别名,而你用的是单数EmployeePosition,二者不匹配。
解决方案步骤
统一模型加载方式
不要直接调用模型文件导出的函数,改用Sequelize自动生成的models对象。修改你的数据库配置文件(比如database.js),确保正确加载所有模型并初始化关联:// database.js 示例 const Sequelize = require('sequelize'); const sequelize = new Sequelize(/* 你的数据库连接配置 */); // 加载所有模型 const models = { Employee: require('../models/Employee')(sequelize, Sequelize.DataTypes), EmployeePosition: require('../models/EmployeePosition')(sequelize, Sequelize.DataTypes), Position: require('../models/Position')(sequelize, Sequelize.DataTypes) }; // 执行所有模型的关联定义 Object.values(models).forEach(model => { if (model.associate) { model.associate(models); } }); module.exports = { sequelize, models };修改请求处理代码中的模型引用
从database.js导入统一的models对象,替换原有的直接创建模型实例的方式:const {Sequelize, Op} = require('sequelize'); const { sequelize, models } = require('../../database/database'); const { Employee, EmployeePosition } = models; const boom = require('@hapi/boom');匹配关联别名
两种可选方式:- 方式一:使用默认复数别名。
hasMany默认会将模型名转为驼峰复数形式,所以include时用['employeePositions']:const employee = await Employee.findByPk(req.params.employee_id, {include: ['employeePositions']}); - 方式二:显式指定关联别名。修改Employee模型的关联定义,固定别名:
同时修改EmployeePosition模型的关联,确保外键和别名对应:static associate(models) { Employee.hasMany(models.EmployeePosition, { as: 'EmployeePosition' }); }
这样就可以继续使用static associate(models) { EmployeePosition.belongsTo(models.Employee, { as: 'Employee', foreignKey: 'employeeId' }); EmployeePosition.belongsTo(models.Position, { as: 'Position', foreignKey: 'positionId' }); }include: ['EmployeePosition']进行查询。
- 方式一:使用默认复数别名。
修复分页参数逻辑
Sequelize中分页需要用offset而非page,修改index函数中的分页代码:if(limit && page){ options.limit = parseInt(limit); options.offset = (parseInt(page) - 1) * parseInt(limit); }
验证修改
完成上述调整后,重启服务执行查询请求,关联数据应该能正常加载,不会再抛出别名不存在的错误。
内容的提问来源于stack exchange,提问作者Kevin Jimenez
相关产品推荐
相关产品推荐

