为啥 Sequelize 认为我的模型没有关联?

Posted

技术标签:

【中文标题】为啥 Sequelize 认为我的模型没有关联?【英文标题】:Why does Sequelize think my models are not associated?为什么 Sequelize 认为我的模型没有关联? 【发布时间】:2018-04-14 08:15:30 【问题描述】:

我正在尝试进行 Sequelize 查询,但它一直告诉我我的模型没有关联。然而我确实声明了一个关联......我想我根本没有像我想象的那样理解关联......

我做了一个 repo,你可以克隆它来自己查看问题:https://github.com/WaltzApp/sequelize-troubleshooting

我认为我遇到这个问题的原因是 Sequelize 不支持复合外键,所以我必须在我的联接中使用自定义的 on 子句,但它没有按预期工作,因为我无法指定关联本身的加入方法相同。这可以解释为什么我有时会通过 Sequelize 打印出功能完善的 SQL,但查询仍然失败(可能是因为虽然数据库查询成功,但将结果转换为 Sequelize entiteis 失败)。

此复制工作的先前版本也复制如下:

相关模型

用户角色

module.exports = (sequelize, DataTypes) => 
  const usersRoles = sequelize.define('usersRoles',
    
      key: 
        type: DataTypes.UUID,
        primary: true,
      ,
      userUid: 
        type: DataTypes.UUID,
        allowNull: false,
      ,
      roleUid: 
        type: DataTypes.UUID,
        allowNull: false,
      ,
      oemUid: 
        type: DataTypes.UUID,
        allowNull: true,
      ,
      propertyManagerUid: 
        type: DataTypes.UUID,
        allowNull: true,
      ,
      tenantUid: 
        type: DataTypes.UUID,
        allowNull: true,
      ,
      [MORE FIELDS IRRELEVANT TO MY PROBLEM]
    ,
    
      tableName: 'usersRoles',
      indexes: [
        
          name: 'userRoleOem',
          fields: ['userUid', 'roleUid', 'oemUid'],
          unique: true,
          where: 
            oemUid:  $ne: null ,
            propertyManagerUid: null,
            tenantUid: null,
          ,
        ,
        
          name: 'userRolePropertyManager',
          fields: ['userUid', 'roleUid', 'propertyManagerUid'],
          unique: true,
          where: 
            oemUid: null,
            propertyManagerUid:  $ne: null ,
            tenantUid: null,
          ,
        ,
        
          name: 'userRoleTenant',
          fields: ['userUid', 'roleUid', 'tenantUid'],
          unique: true,
          where: 
            oemUid: null,
            propertyManagerUid: null,
            tenantUid:  $ne: null ,
          ,
        ,
        
          name: 'compositePK',
          fields: ['userUid', 'roleUid', 'oemUid', 'propertyManagerUid', 'tenantUid'],
          unique: true,
        ,
      ],
      freezeTableName: true,
      timestamps: false,
    );

  usersRoles.removeAttribute('id');

  usersRoles.associate = (models) => 
    models.usersRoles.belongsTo(models.oems,  foreignKey: 'oemUid' );
    models.usersRoles.belongsTo(models.propertyManagers,  foreignKey: 'propertyManagerUid' );
    models.usersRoles.belongsTo(models.tenants,  foreignKey: 'tenantUid' );
    models.usersRoles.belongsTo(models.users,  foreignKey: 'userUid' );
    models.usersRoles.belongsTo(models.roles,  foreignKey: 'roleUid' );
    models.usersRoles.belongsTo(models.invitations,  as: 'invitation', foreignKey: 'compositePK' );
  ;

  return usersRoles;
;

邀请

module.exports = (sequelize, DataTypes) => 
  const invitations = sequelize.define('invitations',
    
      uid: 
        type: DataTypes.UUID,
        primaryKey: true,
      ,
      guestUid: 
        type: DataTypes.UUID,
        allowNull: true,
      ,
      mappingGroupUid: 
        type: DataTypes.UUID,
        allowNull: true,
      ,
      invitedByUid: 
        type: DataTypes.UUID,
        allowNull: false,
      ,
      adminUid: 
        type: DataTypes.UUID,
        allowNull: true,
      ,
      propertyManagerUid: 
        type: DataTypes.UUID,
        allowNull: true,
      ,
      tenantUid: 
        type: DataTypes.UUID,
        allowNull: true,
      ,
      [MORE FIELDS IRRELEVANT TO MY PROBLEM]
    , 
      tableName: 'invitations',
      freezeTableName: true,
      timestamps: false,
    );

  invitations.associate = (models) => 
    models.invitations.belongsTo(models.propertyManagers,  foreignKey: 'propertyManagerUid' );
    models.invitations.belongsTo(models.tenants,  foreignKey: 'tenantUid' );
    models.invitations.belongsTo(models.mappingGroups,  foreignKey: 'mappingGroupUid' );
    models.invitations.belongsTo(models.users,  as: 'guest', foreignKey: 'guestUid' );
    models.invitations.belongsTo(models.users,  as: 'invitedBy', foreignKey: 'invitedByUid' );
    models.invitations.belongsTo(models.users,  as: 'admin', foreignKey: 'adminUid' );
    models.invitations.hasMany(models.usersRoles,  as: 'invitation', foreignKey: 'compositePK' );
  ;

  return invitations;
;

请注意,我将invitationsusersRoles 模型与models.invitations.hasMany(models.usersRoles, foreignKey: 'compositePK' ) 行相关联。

查询

const path = require('path');
const glob = require('glob');
const Promise = require('bluebird');

const Sequelize = require('sequelize');
const configs = require('./test/_helpers/getConfigs');

const models = ;

const performQuery = (db) => 
  const  usersRoles, invitations  = db; // eslint-disable-line max-len

  const sql = 
    include: 
      model: invitations,
      as: 'invitation',
      on: 
        user: Sequelize.where(
          Sequelize.col('usersRoles.userUid'),
          '=',
          Sequelize.col('invitation.guestUid')),
        propertyManager: Sequelize.where(
          Sequelize.col('usersRoles.propertyManagerUid'),
          '=',
          Sequelize.col('invitation.propertyManagerUid')),
        tenant: Sequelize.where(
          Sequelize.col('usersRoles.tenantUid'),
          '=',
          Sequelize.col('invitation.tenantUid')),
      ,
    ,
  ;
  return usersRoles.findAll(sql);
;

const sequelize = new Sequelize(
  configs.get('DB_NAME'),
  configs.get('DB_USER'),
  configs.get('DB_PSSWD'),
  
    dialect: configs.get('DB_TYPE'),
    host: configs.get('DB_HOST'),
    port: configs.get('DB_PORT'),
  );

const modelsPath = path.join(__dirname, 'sequelize/models');
const globOpts = 
  cwd: modelsPath,
  ignore: '**/index.js',
  realPath: false,
  nosort: true,
  nodir: true,
;

glob('**/*.js', globOpts, (err, files) => 
  Promise.map(files, (file) => 
    const model = sequelize.import(path.join(modelsPath, file));
    models[model.name] = model;
  )
    .then(() => 
      Object.keys(models).forEach((modelName) => 
        if (models[modelName].associate) 
          models[modelName].associate(models);
        
      );
      return performQuery(models);
    )
    .then((res) => 
      console.log('SUCCESS', res);
    , (reason) => 
      console.error('FAILED', reason);
    );
);

输出

FAILED  SequelizeEagerLoadingError: invitations is not associated to usersRoles!
    at Function._getIncludedAssociation (.../node_modules/sequelize/lib/model.js:573:13)
    at Function._validateIncludedElement (.../node_modules/sequelize/lib/model.js:489:53)
    at options.include.options.include.map.include (.../node_modules/sequelize/lib/model.js:385:37)
    at Array.map (native)
    at Function._validateIncludedElements (.../node_modules/sequelize/lib/model.js:381:39)
    at Promise.try.then.then (.../node_modules/sequelize/lib/model.js:1525:14)
    at tryCatcher (.../node_modules/bluebird/js/release/util.js:16:23)
    at Promise._settlePromiseFromHandler (.../node_modules/bluebird/js/release/promise.js:512:31)
    at Promise._settlePromise (.../node_modules/bluebird/js/release/promise.js:569:18)
    at Promise._settlePromise0 (.../node_modules/bluebird/js/release/promise.js:614:10)
    at Promise._settlePromises (.../node_modules/bluebird/js/release/promise.js:693:18)
    at Async._drainQueue (.../node_modules/bluebird/js/release/async.js:133:16)
    at Async._drainQueues (.../node_modules/bluebird/js/release/async.js:143:10)
    at Immediate.Async.drainQueues (.../node_modules/bluebird/js/release/async.js:17:14)
    at runCallback (timers.js:637:20)
    at tryOnImmediate (timers.js:610:5)
    at processImmediate [as _immediateCallback] (timers.js:582:5) name: 'SequelizeEagerLoadingError' 

进一步尝试 我尝试在邀请模型中添加以下行:

models.usersRoles.belongsTo(models.invitations);

在这种情况下,Sequelize 会打印一些 SQL,这些 SQL 在执行时完全按预期工作,但显然它没有正确执行它,因为查询仍然不起作用,而是说 SequelizeDatabaseError: relation "usersRoles" does not exist

执行中(默认):SELECT "usersRoles"."key", "usersRoles"."userUid", "usersRoles"."roleUid", "usersRoles"."oemUid", "usersRoles"."propertyManagerUid", "usersRoles"."doorSchedule", "usersRoles"."cardId", "usersRoles"."notifyBy", "usersRoles"."contactEmail", "usersRoles"."tenantUid", "usersRoles"."createdAt", "usersRoles"."compositePK", "usersRoles"."invitationUid", "invitation"."uid" AS "invitation.uid", "invitation"."status" AS "invitation.status", "invitation"."guestUid" AS "invitation.guestUid", "invitation"."firstName" AS "invitation.firstName", "invitation"."lastName" AS "invitation.lastName", "invitation"."email" AS "invitation.email", "邀请"."mobile" AS "invitation.mobile", "邀请"."mappingGroupUid" AS "invitation.mappingGroupUid", "邀请"."doorSchedule" AS "invitation.doorSchedule", "邀请"."invitedByUid" AS "invitation.invitedByUid", "邀请"."adminUid" AS "invitation.adminUid", "邀请"."acceptedAt" AS "invitation.acceptedAt", "邀请"."rejectedAt" AS "invitation.rejectedAt", "邀请"."propertyManagerUid" AS "invitation.propertyManagerUid", "邀请"."tenantUid" AS "invitation.tenantUid", "邀请"."contactEmail" AS "invitation.contactEmail", "邀请"."notifyBy" AS "invitation.notifyBy", "邀请"."createdAt" AS "invitation.createdAt", “邀请”。“updatedAt”作为“invitation.updatedAt”来自“usersRoles” AS "usersRoles" LEFT OUTER JOIN "invitations" AS "invitation" ON "usersRoles"."userUid"="invitation"."guestUid" AND "usersRoles"."propertyManagerUid"="invitation"."propertyManagerUid" AND "usersRoles"."tenantUid"="invitation"."tenantUid";查询失败 SequelizeDatabaseError:关系“usersRoles”不存在

【问题讨论】:

【参考方案1】:

您永远不会调用Model.associate() 方法,因此模型不会相互关联。诀窍是关联可能需要尚未定义的模型,因此关联必须排在最后。加载模型后,循环遍历它们并调用 model.associate(models) 来创建关联。

userRole 定义也不包括与invitations 的关联。在 Sequelize 中,如果您计划从两个对象查询关系,则必须从要查询的一侧定义关系,或者两者都定义。由于invitations 有很多userRoles,我假设userRoles 属于invitations。您在查询中将表引用为invitation,因此您可能希望使用as 属性为关联设置别名。

用户角色

models.usersRoles.belongsTo(models.invitations, 
  as: 'invitation', 
  foreignKey: 'compositePK',
);

【讨论】:

我确实打电话给associate。我不认为这部分代码是相关的,所以我没有把它放进去。我只是编辑了我的问题以包含我创建的完整测试文件,以最小化重现错误。看看吧。 也许是显而易见的,但userRoles.associate() 不包括与models.invitations 的关系。您必须从双方定义关联。 models.usersRoles.belongsTo(models.invitations, foreignKey: 'compositePK' );foreignKey 也很重要,我不确定 compositePK 是如何定义的。 试过了(见最新的编辑)。结果相同 (relation "usersRoles" does not exist) 您没有在关联中指定 foreignKeyas 属性。 查看 SQL 输出,它试图将 invitations 别名为 invitation - LEFT OUTER JOIN "invitations" AS "invitation"

以上是关于为啥 Sequelize 认为我的模型没有关联?的主要内容,如果未能解决你的问题,请参考以下文章

Sequelize:“模型 A 与模型 B 没有关联”

Sequelize 模型关联不会创建新列

按关联模型排序时,Sequelize 抛出错误“无法找到模型 x 的有效关联”

如何嵌套 Sequelize 关联?

使用 sequelize 关联不同的模型?

Sequelize - 在验证模型的关联时获取正确的路径