使用Sequelize的多对多关系的简单示例

时间:2017-03-17 06:27:10

标签: javascript mysql node.js many-to-many sequelize.js

我试图使用Sequelize构建表之间多对多关系的简单示例。然而,这似乎比我预期的要复杂。

这是我目前的代码(./db.js文件导出Sequelize连接实例)。

const Sequelize = require("sequelize");
const sequelize = require("./db");

var Mentee = sequelize.define('mentee', {
    id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
    },
    name: {
        type: Sequelize.STRING
    }
});

var Question = sequelize.define('question', {
    id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
    },
    text: {
        type: Sequelize.STRING
    }
});

var MenteeQuestion = sequelize.define('menteequestion', {
//    answer: {
//        type: Sequelize.STRING
//    }
});

// A mentee can answer several questions
Mentee.belongsToMany(Question, { as: "Questions", through: MenteeQuestion });

// And a question can be answered by several mentees
Question.belongsToMany(Mentee, { as: "Mentees", through: MenteeQuestion });

let currentQuestion = null;
Promise.all([
    Mentee.sync({ force: true })
  , Question.sync({ force: true })
  , MenteeQuestion.sync({ force: true })
]).then(() => {
    return Mentee.destroy({where: {}})
}).then(() => {
    return Question.destroy({ where: {} })
}).then(() => {
    return Question.create({
        text: "What is 42?"
    });
}).then(question => {
    currentQuestion = question;
    return Mentee.create({
        name: "Johnny"
    })
}).then(mentee => {
    console.log("Adding question");
    return mentee.addQuestion(currentQuestion);
}).then(() => {
    return MenteeQuestion.findAll({
        where: {}
      , include: [Mentee]
    })
}).then(menteeQuestions => {
    return MenteeQuestion.findAll({
        where: {
            menteeId: 1
        }
      , include: [Mentee]
    })
}).then(menteeQuestion => {
    console.log(menteeQuestion.toJSON());
}).catch(e => {
    console.error(e);
});

运行时,我得到:

  

无法添加外键约束

我认为这是因为id类型 - 但我不知道为什么它出现以及我们如何解决它。

上一个出现的另一个错误是:

  

执行(默认): INSERT INTO menteequestionsmenteeIdquestionIdcreatedAtupdatedAt)VALUES(2,1,& #39; 2017-03-17 06:18:01',2017-03-17 06:18:01');

     

错误:受指导者与menteequestion无关!

另外,我得到的另一个错误 - 我认为因force:true中的sync而导致的错误 - 是:

  

DROP TABLE IF EXISTS mentees;

     

ER_ROW_IS_REFERENCED:无法删除或更新父行:外键约束失败

如何解决这些问题?

同样,我只需要一个多对多crud操作的最小例子(在这种情况下只是插入和读取),但这似乎超出了我的理解。用这两天挣扎了。

1 个答案:

答案 0 :(得分:15)

迁移

我建议您使用sequelize migrations而不是在每个模型上执行sync()。有一个模块 - sequelize.cli,可让您轻松管理迁移和种子。它以某种方式通过在项目的index.js目录中创建初始化文件/models来强制项目结构。它假设您的所有模型定义都在此目录中。此脚本遍历所有模型文件(每个模型定义位于单独的文件中,例如mentee.jsquestion.js)并执行sequelize.import()以将这些模型分配给续集实例 - 这可以让您稍后通过sequelize[modelName]访问它们,例如sequelize.question

注意:在创建迁移文件时,请记住时间戳字段 - createdAtupdatedAt,最后是deletedAt

同步

我个人只在运行测试时使用sync() - 这可能会分三个步骤显示

  1. 执行sequelize.sync({ force: true })以同步所有模型
  2. 运行一些数据库seeds(也可以通过sequelize-cli),
  3. 运行
  4. 运行测试。
  5. 这非常舒服,因为允许您在运行测试之前清理数据库,并且为了区分开发和测试,测试可以使用不同的数据库,例如project_test,以便开发数据库保持不变。

    许多对多

    现在让我们继续讨论你的问题 - 两个模型之间的m:n关系。首先,由于您执行Promise.all()sync的运行顺序可能与您在其中添加功能的顺序不同。为了避免这种情况,我建议您使用mapSeries承诺的Bluebird功能,Sequelize在sequelize.Promise下使用和公开(这也是您上次删除父行的错误的原因 - 您尝试删除从mentees引用的menteequestion

    sequelize.Promise.mapSeries([
        Mentee.sync({ force: true })
      , Question.sync({ force: true })
      , MenteeQuestion.sync({ force: true })
    ], (model) => { return model.destroy({ where: {} }); }).then(() => {
    
    });
    

    mapSeries的第一个参数是promises数组,但是第二个参数是一个函数,它使用每个先前定义的promise的结果运行。由于Model.sync()导致模型本身,我们可以在每次迭代时执行model.destroy()

    之后,您可以通过create()将一些数据插入数据库,就像示例中一样。现在是时候修复错误:被指导者与menteequestion没有关联!错误。之所以出现这种情况,是因为您已将MenteeQuestion相关联,但MenteeQuestionMentee(或Question)之间没有关联。为了解决这个问题,在belongsToMany之后,您可以添加

    MenteeQuestion.belongsTo(Mentee, { foreignKey: 'menteeId' });
    MenteeQuestion.belongsTo(Question, { foreignKey: 'questionId' });
    

    现在,您可以在查询include: [Mentee, Question]时添加MenteeQuestion。在执行toJSON()时,您还会遇到另一个错误,因为您执行了返回实例数组的findAll。你可以做forEach()

    menteeQuestions.forEach(menteeQuestion => {
        console.log(menteeQuestion.toJSON());
    });