Sequelize.js:包含意外。元素必须是模型、关联或对象之一。

4
我正在使用Sequelize.js在我的Node.js应用程序中,并且一直遇到一个非常奇怪的问题。
背景:我有两个模型,Account和AccountCategory如下所示。我的API端点调用路由/accounts,该路由调用账户控制器执行Account.findAll()查询。
Accounts模型具有defaultScope,可以默认包含相关类别,而无需每次在findAll({})块内指定它。
问题:当Accounts模型尝试访问并从数据库返回数据时,默认范围(defaultScope)尝试包含AccountCategory,Sequelize抛出错误:
Include unexpected. Element has to be either a Model, an Association or an object.
我怀疑这与我的models文件夹中的AccountCategory放在Account之后有关,因此没有被处理(关联)。我基于这样一个事实:其他关联,如UserRole(即用户拥有角色),使用相同的方法都很好(即没有路径深度问题,就像this answer所建议的那样)。
我已经花了最近两天的时间尝试使defaultScope正常工作并停止产生此错误,但没有成功。类似的问题没有提供答案,我将非常感激任何帮助解决这个问题。谢谢。 账户:
module.exports = (sequelize, DataTypes) => {
    const Account = sequelize.define(
        "Account",
        {
            id: {
                type: DataTypes.INTEGER(11),
                allowNull: false,
                primaryKey: true,
                autoIncrement: true
            },
            name: {
                type: DataTypes.STRING(100)
            },
            category_id: {
                type: DataTypes.INTEGER(11),
                allowNull: false
            }
        },
        {
            timestamps: false,
            tableName: "Account",
            defaultScope: {
                include: [{
                    model: sequelize.models.AccountCategory,
                    as: "category"
                }]
            }
        }
    );

    Account.associate = models => {
        // Association: Account -> AccountCategory
        Account.belongsTo(models.AccountCategory, {
            onDelete: "CASCADE",
            foreignKey: {
                fieldName: "category_id",
                allowNull: false,
                require: true
            },
            targetKey: "id",
            as: "category"
        });
    };

    return Account;
};

账户类别:

module.exports = (sequelize, DataTypes) => {
    var AccountCategory = sequelize.define(
        "AccountCategory",
        {
            id: {
                type: DataTypes.INTEGER(11),
                allowNull: false,
                primaryKey: true,
                autoIncrement: true
            },
            name: {
                type: DataTypes.STRING(30),
                allowNull: false,
                unique: true
            }
        },
        {
            timestamps: false,
            tableName: "Account_Category"
        }
    );

    return AccountCategory;
};

模型索引:

const fs = require("fs");
const path = require("path");
const Sequelize = require("sequelize");
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || "development";
const db = {};

const sequelize = new Sequelize(
    process.env.DB_NAME,
    process.env.DB_USER,
    process.env.DB_PASS,
    {
        host: process.env.DB_HOST,
        dialect: "mysql",
        operatorAliases: false,

        pool: {
            max: 5,
            min: 0,
            acquire: 30000,
            idle: 10000
        }
    }
);

fs.readdirSync(__dirname)
    .filter(function(file) {
        return (
            file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
        );
    })
    .forEach(function(file) {
        var model = sequelize["import"](path.join(__dirname, file));
        db[model.name] = model;
    });

Object.keys(db).forEach(function(modelName) {
    if (db[modelName].associate) {
        db[modelName].associate(db);
    }
    db[modelName].associate(db);
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

你确定你的代码顺序没问题吗?也就是说应该先有两个define()语句,然后是associate(),最后是findAll()?类似的代码在我的电脑上可以运行。 - KenOn10
我可以帮你试着将你尝试包含的模型重命名,使其在引用它的模型之前出现吗?只是想确认一下我的猜想。 - ProGrammer
对我来说尝试这个变得很痛苦。你不能自己做吗?例如,用var model = sequelize["import"](path.join(__dirname, 'account.js')); db[model.name] = model;model = sequelize["import"](path.join(__dirname, 'account_categoryjs')); db[model.name] = model;替换readDir(或者按照你想要的任何顺序)? - KenOn10
我遇到了同样的问题,我可以确认这是由于在模型的 index.js 文件中执行的导入/排序顺序引起的。 - Dillon
1个回答

0

你说得对:

我怀疑这是因为在设置模型时,AccountCategory放在Account之后,因此未被处理(关联)。

简而言之: 在模型类定义中添加一个类似于associate函数的新函数,并使用addScope函数来定义任何引用其他模型的范围,这些模型可能由于文件树顺序而未被初始化。最后,在models.index.js文件中调用该新函数,就像调用db[modelName].associate一样。

我曾经遇到过类似的问题,并通过在models/index.js文件中运行以下命令来定义引用任何模型的范围,例如在include中,以确保在所有模型初始化后进行:

以下是一个示例:

models/agent.js

'use strict';
const { Model } = require('sequelize');
const camelCase = require('lodash/camelCase');
const { permissionNames } = require('../../api/constants/permissions');

module.exports = (sequelize, DataTypes) => {
  /**
   * @summary Agent model
   */
  class Agent extends Model {}

  Agent.init(
    {
      id: {
        type: DataTypes.INTEGER,
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
      },
      firstName: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      lastName: {
        type: DataTypes.STRING,
        allowNull: false,
      },
    },
    {
      sequelize,
      scopes: {
        // Works because the agent-role.js file / model comes before agent.js in the file tree
        [camelCase(permissionNames.readAgentRoles)]: {
          include: [
            {
              model: sequelize.models.AgentRole,
            },
          ],
        },
        // Doesn't work due to import order in models/index.js, i.e., agent.js is before role.js in the file tree
        // [camelCase(permissionNames.readRoles)]: {
        //   include: [
        //     {
        //       model: sequelize.models.Role,
        //     },
        //   ],
        // },
      },
    }
  );

  Agent.associate = function (models) {
    Agent.belongsToMany(models.Role, {
      through: 'AgentRole',
      onDelete: 'CASCADE', // default for belongsToMany
      onUpdate: 'CASCADE', // default for belongsToMany
      foreignKey: {
        name: 'agentId',
        type: DataTypes.INTEGER,
        allowNull: false,
      },
    });
    Agent.hasMany(models.AgentRole, {
      onDelete: 'CASCADE',
      onUpdate: 'CASCADE',
      foreignKey: {
        name: 'agentId',
        type: DataTypes.INTEGER,
        allowNull: false,
      },
    });
  };

  // Add a custom `addScopes` function to call after initializing all models in `index.js`
  Agent.addScopes = function (models) {
    Agent.addScope(camelCase(permissionNames.readRoles), {
      include: [
        {
          model: models.Role,
        },
      ],
    });
  };

  return Agent;
};

models/index.js

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const config = require('../database-config.js');
const db = {};

const sequelize = new Sequelize(config.database, config.username, config.password, config);

/**
 * Import and attach all of the model definitions within this 'models' directory to the sequelize instance.
 */
fs.readdirSync(__dirname)
  .filter((file) => {
    return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
  })
  .forEach((file) => {
    // Here is where file tree order matters... the sequelize const may not have the required model added to it yet
    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
    db[model.name] = model;
  });

Object.keys(db).forEach((modelName) => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
  // We need to add scopes that reference other tables once they have all been initialized
  if (db[modelName].addScopes) {
    db[modelName].addScopes(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接