You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express+PostgreSQL+Sequelize+TS模型关联时报错:Cannot read properties of undefined (reading 'Sequelize')

问题描述

使用Express/PostgreSQL/Sequelize/TypeScript技术栈开发,创建了包含Company和Stakeholder的模型文件。单独调用Company.findAll()、Stakeholder.findAll()查询数据列表正常,但通过Position中间表建立多对多关联时,触发错误:TypeError: Cannot read properties of undefined (reading 'Sequelize')。

需要实现两者的多对多关系,理想结构为每个模型单独一个文件,通过index文件创建Sequelize连接实例,但缺少该index文件的参考示例。

当前依赖版本:

  • "express": "^4.17.1"
  • "sequelize": "^6.20.0"
  • "pg": "^8.7.3"
  • "pg-hstore": "^2.3.4"
  • "ts-node-dev": "^1.1.8"
  • "typescript": "^4.6.4"
  • "@types/node": "^17.0.35"
  • "@types/express": "^4.17.13"
错误原因

触发错误的核心原因是在模型初始化完成前调用了关联方法。原代码中Company.belongsToMany()写在Company.init()和Stakeholder.init()之前,此时模型未完成Sequelize实例绑定,导致读取Sequelize属性时出现undefined。同时,将两个模型和Sequelize实例放在同一文件中,不符合"单模型单文件"的结构需求,也降低了代码可维护性。

解决方案

1. 重构项目结构

按照"单模型单文件"的需求调整结构:

src/
├── db/
│   ├── index.ts          # Sequelize实例创建、模型导入与关联配置
│   ├── models/
│   │   ├── Company.ts    # Company模型文件
│   │   └── Stakeholder.ts# Stakeholder模型文件
├── ...其他业务目录

2. 编写db/index.ts

负责创建Sequelize实例、导入所有模型并配置关联:

import { Sequelize } from 'sequelize';
import logger from '../utils/logger/logger';
import { Company } from './models/Company';
import { Stakeholder } from './models/Stakeholder';

// 创建Sequelize数据库连接实例
export const sequelize = new Sequelize('postgres://test:test@localhost:5432/board4');

// 测试数据库连接
const testConnection = async () => {
  try {
    await sequelize.authenticate();
    console.log('数据库连接成功');
  } catch (error) {
    logger.error('数据库连接失败:', error);
  }
};
testConnection();

// 配置多对多关联(需在所有模型初始化后执行)
Company.belongsToMany(Stakeholder, { through: 'Position', foreignKey: 'positionCompanyId' });
Stakeholder.belongsToMany(Company, { through: 'Position', foreignKey: 'positionStakeholderId' });

// 导出所有模型和连接实例
export { Company, Stakeholder };

3. 编写Company.ts模型文件

import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model, Association } from 'sequelize';
import { sequelize } from '../index';
import { Stakeholder } from './Stakeholder';

export class Company extends Model<InferAttributes<Company>, InferCreationAttributes<Company>> {
  declare companyId: CreationOptional<string>;
  declare companyName: string;
  declare companyEntityType: string | null;
  declare companyVatNumber: string | null;
  declare companyRegistrationNumber: string;
  declare companyPubliclyListed: boolean | null;
  declare companyDateOfIncorporation: Date | null;
  declare companyAddress1: string | null;
  declare companyAddress2: string | null;
  declare companyCity: string | null;
  declare companyPostCode: string | null;
  declare companyCountry: string | null;
  declare companyPhone: string | null;
  declare companyEmail: string | null;
  declare companyUrl: string | null;
  declare companyLogoUrl: string | null;
  declare createdAt: CreationOptional<Date>;
  declare updatedAt: CreationOptional<Date>;

  declare static associations: {
    stakeholders: Association<Company, Stakeholder>;
  };
}

Company.init(
  {
    companyId: {
      type: DataTypes.UUID,
      defaultValue: DataTypes.UUIDV4,
      primaryKey: true,
    },
    companyName: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    companyEntityType: DataTypes.STRING,
    companyVatNumber: DataTypes.STRING,
    companyRegistrationNumber: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: {
        name: 'uniqueRegistrationNumber',
        msg: '公司注册号已被使用',
      },
      validate: {
        notNull: { msg: '必须填写有效的注册号' },
      },
    },
    companyDateOfIncorporation: DataTypes.DATE,
    companyPubliclyListed: DataTypes.BOOLEAN,
    companyAddress1: DataTypes.STRING,
    companyAddress2: DataTypes.STRING,
    companyCity: DataTypes.STRING,
    companyPostCode: DataTypes.STRING,
    companyCountry: DataTypes.STRING,
    companyPhone: DataTypes.STRING,
    companyEmail: DataTypes.STRING,
    companyUrl: DataTypes.STRING,
    companyLogoUrl: DataTypes.STRING,
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE,
  },
  {
    sequelize,
    underscored: true,
    timestamps: true,
    tableName: 'companies',
  }
);

4. 编写Stakeholder.ts模型文件

import { CreationOptional, DataTypes, ForeignKey, InferAttributes, InferCreationAttributes, Model, Association } from 'sequelize';
import { sequelize } from '../index';
import { Company } from './Company';
import logger from '../../utils/logger/logger';

export class Stakeholder extends Model<InferAttributes<Stakeholder>, InferCreationAttributes<Stakeholder>> {
  declare stakeholderId: string;
  declare stakeholderName: string;
  declare username: string;
  declare password: string;
  declare stakeholderLastname: string;
  declare stakeholderEmail: string;
  declare stakeholderJobTitle?: string | null;
  declare stakeholderSharesPercentage?: number | null;
  declare stakeholderDOB?: string | null;
  declare stakeholderNationality?: string | null;
  declare stakeholderAddress1?: string | null;
  declare stakeholderAddress2?: string | null;
  declare stakeholderPostCode?: string | null;
  declare stakeholderCity?: string | null;
  declare stakeholderCountry?: string | null;
  declare stakeholderPhone?: string | null;
  declare stakeholderPassport?: string | null;
  declare stakeholderProofOfAddress?: string | null;
  declare stakeholderRole: string;
  declare positionCompanyId: ForeignKey<string>;

  declare createdAt: CreationOptional<Date>;
  declare updatedAt: CreationOptional<Date>;

  declare static associations: {
    companies: Association<Stakeholder, Company>;
  };
}

Stakeholder.init(
  {
    stakeholderId: {
      type: DataTypes.UUID,
      defaultValue: DataTypes.UUIDV4,
      primaryKey: true,
    },
    username: {
      allowNull: true,
      type: DataTypes.STRING,
    },
    password: {
      allowNull: true,
      type: DataTypes.STRING,
    },
    stakeholderRole: {
      type: DataTypes.STRING,
    },
    stakeholderName: DataTypes.STRING,
    stakeholderLastname: DataTypes.STRING,
    stakeholderJobTitle: DataTypes.STRING,
    stakeholderEmail: DataTypes.STRING,
    stakeholderSharesPercentage: DataTypes.INTEGER,
    stakeholderDOB: DataTypes.STRING,
    stakeholderNationality: DataTypes.STRING,
    stakeholderAddress1: DataTypes.STRING,
    stakeholderAddress2: DataTypes.STRING,
    stakeholderPostCode: DataTypes.STRING,
    stakeholderCity: DataTypes.STRING,
    stakeholderCountry: DataTypes.STRING,
    stakeholderPhone: DataTypes.STRING,
    stakeholderPassport: {
      type: DataTypes.STRING,
      set: function (val: any) {
        logger.debug(`${__filename}:${val} 是否有效?: ${val.fileName && val.fileName.split('.')[1] !== 'undefined'}`);
        if (val.fileName && val.fileName.split('.')[1] !== 'undefined') this.setDataValue('stakeholderPassport', val.fileName);
      },
    },
    stakeholderProofOfAddress: {
      type: DataTypes.STRING,
      set: function (val: any) {
        logger.debug(`${__filename}:${val} 是否有效?: ${val.fileName && val.fileName.split('.')[1] !== 'undefined'}`);
        if (val.fileName && val.fileName.split('.')[1] !== 'undefined') this.setDataValue('stakeholderProofOfAddress', val.fileName);
      },
    },
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE,
  },
  {
    sequelize,
    timestamps: true,
    tableName: 'stakeholders',
  }
);

关键修正点

  • 将belongsToMany关联配置放在所有模型初始化完成之后,确保模型已绑定Sequelize实例
  • 拆分模型到单独文件,通过db/index.ts统一管理连接实例和关联关系
  • 修正关联外键:Company.belongsToMany的外键指向中间表中关联Company的字段(positionCompanyId),Stakeholder.belongsToMany的外键对应positionStakeholderId,避免外键混淆

内容的提问来源于stack exchange,提问作者geuxor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 19:10:32