Associations - 关联

本部分描述 sequelize 中的各种关联类型。 当调用 User.hasOne(Project) 这样的方法时,我们说 User 模型(该函数被调用的模型)是 sourceProject 模型(模型被传递为参数)是 target

一对一关联

一对一关联是通过单个外键连接的两个模型之间的关联。

BelongsTo

BelongsTo 关联是在 source model 上存在一对一关系的外键的关联。

一个简单的例子是 Player 通过 player 的外键作为 Team 的一部分。

  1. const Player = this.sequelize.define('player', {/* attributes */});
  2. const Team = this.sequelize.define('team', {/* attributes */});
  3. Player.belongsTo(Team); // 将向 Player 添加一个 teamId 属性以保存 Team 的主键值

外键

默认情况下,将从目标模型名称和目标主键名称生成 belongsTo 关系的外键。

默认的样式是 camelCase,但是如果源模型配置为 underscored: true ,那么 foreignKey 将是snake_case

  1. const User = this.sequelize.define('user', {/* attributes */})
  2. const Company = this.sequelize.define('company', {/* attributes */});
  3. User.belongsTo(Company); // 将 companyId 添加到 user
  4. const User = this.sequelize.define('user', {/* attributes */}, {underscored: true})
  5. const Company = this.sequelize.define('company', {
  6. uuid: {
  7. type: Sequelize.UUID,
  8. primaryKey: true
  9. }
  10. });
  11. User.belongsTo(Company); // 将 company_uuid 添加到 user

在已定义 as 的情况下,将使用它代替目标模型名称。

  1. const User = this.sequelize.define('user', {/* attributes */})
  2. const UserRole = this.sequelize.define('userRole', {/* attributes */});
  3. User.belongsTo(UserRole, {as: 'role'}); // 将 role 添加到 user 而不是 userRole

在所有情况下,默认外键可以用 foreignKey 选项覆盖。
当使用外键选项时,Sequelize 将按原样使用:

  1. const User = this.sequelize.define('user', {/* attributes */})
  2. const Company = this.sequelize.define('company', {/* attributes */});
  3. User.belongsTo(Company, {foreignKey: 'fk_company'}); // 将 fk_company 添加到 User

目标键

目标键是源模型上的外键列指向的目标模型上的列。 默认情况下,belongsTo 关系的目标键将是目标模型的主键。 要定义自定义列,请使用 targetKey 选项。

  1. const User = this.sequelize.define('user', {/* attributes */})
  2. const Company = this.sequelize.define('company', {/* attributes */});
  3. User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // 添加 fk_companyname 到 User

HasOne

HasOne 关联是在 target model 上存在一对一关系的外键的关联。

  1. const User = sequelize.define('user', {/* ... */})
  2. const Project = sequelize.define('project', {/* ... */})
  3. // 单向关联
  4. Project.hasOne(User)
  5. /*
  6. 在此示例中,hasOne 将向 User 模型添加一个 projectId 属性 !
  7. 此外,Project.prototype 将根据传递给定义的第一个参数获取 getUser 和 setUser 的方法。
  8. 如果启用了 underscore 样式,则添加的属性将是 project_id 而不是 projectId。
  9. 外键将放在 users 表上。
  10. 你也可以定义外键,例如 如果您已经有一个现有的数据库并且想要处理它:
  11. */
  12. Project.hasOne(User, { foreignKey: 'initiator_id' })
  13. /*
  14. 因为Sequelize将使用模型的名称(define的第一个参数)作为访问器方法,
  15. 还可以将特殊选项传递给hasOne:
  16. */
  17. Project.hasOne(User, { as: 'Initiator' })
  18. // 现在你可以获得 Project.getInitiator 和 Project.setInitiator
  19. // 或者让我们来定义一些自己的参考
  20. const Person = sequelize.define('person', { /* ... */})
  21. Person.hasOne(Person, {as: 'Father'})
  22. // 这会将属性 FatherId 添加到 Person
  23. // also possible:
  24. Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'})
  25. // 这将把属性 DadId 添加到 Person
  26. // 在这两种情况下,你都可以:
  27. Person.setFather
  28. Person.getFather
  29. // 如果你需要联结表两次,你可以联结同一张表
  30. Team.hasOne(Game, {as: 'HomeTeam', foreignKey : 'homeTeamId'});
  31. Team.hasOne(Game, {as: 'AwayTeam', foreignKey : 'awayTeamId'});
  32. Game.belongsTo(Team);

即使它被称为 HasOne 关联,对于大多数1:1关系,您通常需要BelongsTo关联,因为 BelongsTo 将会在 hasOne 将添加到目标的源上添加 foreignKey。

HasOne 和 BelongsTo 之间的区别

在Sequelize 1:1关系中可以使用HasOne和BelongsTo进行设置。 它们适用于不同的场景。 让我们用一个例子来研究这个差异。

假设我们有两个表可以链接 PlayerTeam 。 让我们定义他们的模型。

  1. const Player = this.sequelize.define('player', {/* attributes */})
  2. const Team = this.sequelize.define('team', {/* attributes */});

当我们连接 Sequelize 中的两个模型时,我们可以将它们称为一对 sourcetarget 模型。像这样

Player 作为 sourceTeam 作为 target

  1. Player.belongsTo(Team);
  2. //或
  3. Player.hasOne(Team);

Team 作为 sourcePlayer 作为 target

  1. Team.belongsTo(Player);
  2. //Or
  3. Team.hasOne(Player);

HasOne 和 BelongsTo 将关联键插入到不同的模型中。 HasOne 在 target 模型中插入关联键,而 BelongsTo 将关联键插入到 source 模型中。

下是一个示例,说明了 BelongsTo 和 HasOne 的用法。

  1. const Player = this.sequelize.define('player', {/* attributes */})
  2. const Coach = this.sequelize.define('coach', {/* attributes */})
  3. const Team = this.sequelize.define('team', {/* attributes */});

假设我们的 Player 模型有关于其团队的信息为 teamId 列。 关于每个团队的 Coach 的信息作为 coachId 列存储在 Team 模型中。 这两种情况都需要不同种类的1:1关系,因为外键关系每次出现在不同的模型上。

当关于关联的信息存在于 source 模型中时,我们可以使用 belongsTo。 在这种情况下,Player 适用于 belongsTo,因为它具有 teamId 列。

  1. Player.belongsTo(Team) // `teamId` 将被添加到 Player / Source 模型中

当关于关联的信息存在于 target 模型中时,我们可以使用 hasOne。 在这种情况下, Coach 适用于 hasOne ,因为 Team 模型将其 Coach 的信息存储为 coachId 字段。

  1. Coach.hasOne(Team) // `coachId` 将被添加到 Team / Target 模型中

一对多关联 (hasMany)

一对多关联将一个来源与多个目标连接起来。 而多个目标接到同一个特定的源。

  1. const User = sequelize.define('user', {/* ... */})
  2. const Project = sequelize.define('project', {/* ... */})
  3. // 好。 现在,事情变得更加复杂(对用户来说并不真实可见)。
  4. // 首先我们来定义一个 hasMany 关联
  5. Project.hasMany(User, {as: 'Workers'})

这将添加属性 projectIdproject_id 到 User。 Project 的实例将获得访问器 getWorkerssetWorkers

有时您可能需要在不同的列上关联记录,您可以使用 sourceKey 选项:

  1. const City = sequelize.define('city', { countryCode: Sequelize.STRING });
  2. const Country = sequelize.define('country', { isoCode: Sequelize.STRING });
  3. // 在这里,我们可以根据国家代码连接国家和城市
  4. Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'});
  5. City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'});

到目前为止,我们解决了单向关联。 但我们想要更多! 让我们通过在下一节中创建一个多对多的关联来定义它。

多对多关联

多对多关联用于将源与多个目标相连接。 此外,目标也可以连接到多个源。

  1. Project.belongsToMany(User, {through: 'UserProject'});
  2. User.belongsToMany(Project, {through: 'UserProject'});

这将创建一个名为 UserProject 的新模型,具有等效的外键projectIduserId。 属性是否为camelcase取决于由表(在这种情况下为UserProject)连接的两个模型。

定义 throughrequired。 Sequelize 以前会尝试自动生成名称,但并不总是导致最合乎逻辑的设置。

这将添加方法 getUsers, setUsers, addUser,addUsersProject, 还有 getProjects, setProjects, addProject, 和 addProjectsUser.

有时,您可能需要在关联中使用它们时重命名模型。 让我们通过使用别名(as)选项将 users 定义为 workers 而 projects 定义为t asks。 我们还将手动定义要使用的外键:

  1. User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' })
  2. Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' })

foreignKey 将允许你在 through 关系中设置 source model 键。

otherKey 将允许你在 through 关系中设置 target model 键。

  1. User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId', otherKey: 'projectId'})

当然你也可以使用 belongsToMany 定义自我引用:

  1. Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' })
  2. // 这将创建存储对象的 ID 的表 PersonChildren。

如果您想要连接表中的其他属性,则可以在定义关联之前为连接表定义一个模型,然后再说明它应该使用该模型进行连接,而不是创建一个新的关联:

  1. const User = sequelize.define('user', {})
  2. const Project = sequelize.define('project', {})
  3. const UserProjects = sequelize.define('userProjects', {
  4. status: DataTypes.STRING
  5. })
  6. User.belongsToMany(Project, { through: UserProjects })
  7. Project.belongsToMany(User, { through: UserProjects })

要向 user 添加一个新 project 并设置其状态,您可以将额外的 options.through 传递给 setter,其中包含连接表的属性

  1. user.addProject(project, { through: { status: 'started' }})

默认情况下,上面的代码会将 projectId 和 userId 添加到 UserProjects 表中, 删除任何先前定义的主键属性 - 表将由两个表的键的组合唯一标识,并且没有其他主键列。 要在 UserProjects 模型上强添加一个主键,您可以手动添加它。

  1. const UserProjects = sequelize.define('userProjects', {
  2. id: {
  3. type: Sequelize.INTEGER,
  4. primaryKey: true,
  5. autoIncrement: true
  6. },
  7. status: DataTypes.STRING
  8. })

使用多对多你可以基于 through 关系查询并选择特定属性。 例如通过 through 使用findAll

  1. User.findAll({
  2. include: [{
  3. model: Project,
  4. through: {
  5. attributes: ['createdAt', 'startedAt', 'finishedAt'],
  6. where: {completed: true}
  7. }
  8. }]
  9. });

作用域

本节涉及关联作用域。 有关关联作用域与相关模型上的作用域的定义,请参阅 作用域

关联作用域允许您在关联上放置一个作用域(一套 getcreate 的默认属性)。作用域可以放在相关联的模型(关联的target)上,也可以通过表上的 n:m 关系。

1:m

假设我们有表评论,帖子和图像。 一个评论可以通过 commentable_idcommentable 关联到一个图像或一个帖子 - 我们说 Post 和 Image 是 Commentable

  1. const Comment = this.sequelize.define('comment', {
  2. title: Sequelize.STRING,
  3. commentable: Sequelize.STRING,
  4. commentable_id: Sequelize.INTEGER
  5. });
  6. Comment.prototype.getItem = function(options) {
  7. return this['get' + this.get('commentable').substr(0, 1).toUpperCase() + this.get('commentable').substr(1)](options);
  8. };
  9. Post.hasMany(this.Comment, {
  10. foreignKey: 'commentable_id',
  11. constraints: false,
  12. scope: {
  13. commentable: 'post'
  14. }
  15. });
  16. Comment.belongsTo(this.Post, {
  17. foreignKey: 'commentable_id',
  18. constraints: false,
  19. as: 'post'
  20. });
  21. Image.hasMany(this.Comment, {
  22. foreignKey: 'commentable_id',
  23. constraints: false,
  24. scope: {
  25. commentable: 'image'
  26. }
  27. });
  28. Comment.belongsTo(this.Image, {
  29. foreignKey: 'commentable_id',
  30. constraints: false,
  31. as: 'image'
  32. });

constraints: false, 禁用引用约束 - 由于 commentable_id 列引用了几个表,我们不能添加一个 REFERENCES 约束。 请注意,Image - > Comment 和 Post - > Comment 关系分别定义了一个作用域:commentable: 'image'commentable: 'post'。 使用关联功能时自动应用此作用域:

  1. image.getComments()
  2. SELECT * FROM comments WHERE commentable_id = 42 AND commentable = 'image';
  3. image.createComment({
  4. title: 'Awesome!'
  5. })
  6. INSERT INTO comments (title, commentable_id, commentable) VALUES ('Awesome!', 42, 'image');
  7. image.addComment(comment);
  8. UPDATE comments SET commentable_id = 42, commentable = 'image'

Comment 上的 getItem 作用函数完成了图片 - 它只是将commentable字符串转换为getImagegetPost的一个调用,提供一个注释是属于一个帖子还是一个图像的抽象概念。您可以将普通选项对象作为参数传递给 getItem(options),以指定任何条件或包含的位置。

n:m

继续多态模型的思路,考虑一个 tag 表 - 一个 item 可以有多个 tag,一个 tag 可以与多个 item 相关。

为了简洁起见,该示例仅显示了 Post 模型,但实际上 Tag 与其他几个模型相关。

  1. const ItemTag = sequelize.define('item_tag', {
  2. id : {
  3. type: DataTypes.INTEGER,
  4. primaryKey: true,
  5. autoIncrement: true
  6. },
  7. tag_id: {
  8. type: DataTypes.INTEGER,
  9. unique: 'item_tag_taggable'
  10. },
  11. taggable: {
  12. type: DataTypes.STRING,
  13. unique: 'item_tag_taggable'
  14. },
  15. taggable_id: {
  16. type: DataTypes.INTEGER,
  17. unique: 'item_tag_taggable',
  18. references: null
  19. }
  20. });
  21. const Tag = sequelize.define('tag', {
  22. name: DataTypes.STRING
  23. });
  24. Post.belongsToMany(Tag, {
  25. through: {
  26. model: ItemTag,
  27. unique: false,
  28. scope: {
  29. taggable: 'post'
  30. }
  31. },
  32. foreignKey: 'taggable_id',
  33. constraints: false
  34. });
  35. Tag.belongsToMany(Post, {
  36. through: {
  37. model: ItemTag,
  38. unique: false
  39. },
  40. foreignKey: 'tag_id',
  41. constraints: false
  42. });

请注意,作用域列(taggable)现在在 through 模型(ItemTag)上。

我们还可以定义一个更具限制性的关联,例如,通过应用through 模型(ItemTag)和目标模型(Tag)的作用域来获取所有挂起的 tag。

  1. Post.hasMany(Tag, {
  2. through: {
  3. model: ItemTag,
  4. unique: false,
  5. scope: {
  6. taggable: 'post'
  7. }
  8. },
  9. scope: {
  10. status: 'pending'
  11. },
  12. as: 'pendingTags',
  13. foreignKey: 'taggable_id',
  14. constraints: false
  15. });
  16. Post.getPendingTags();
  1. SELECT `tag`.* INNER JOIN `item_tags` AS `item_tag`
  2. ON `tag`.`id` = `item_tag`.`tagId`
  3. AND `item_tag`.`taggable_id` = 42
  4. AND `item_tag`.`taggable` = 'post'
  5. WHERE (`tag`.`status` = 'pending');

constraints: false 禁用 taggable_id 列上的引用约束。 因为列是多态的,我们不能说它是 REFERENCES 一个特定的表。

命名策略

默认情况下,Sequelize将使用模型名称(传递给sequelize.define的名称),以便在关联时使用模型名称。 例如,一个名为user的模型会将关联模型的实例中的get / set / add User函数和加入一个名为.user的属性,而一个名为User的模型会添加相同的功能,和一个名为.User的属性(注意大写U)。

正如我们已经看到的,你可以使用as来关联模型。 在单个关联(has one 和 belongs to),别名应该是单数,而对于许多关联(has many)它应该是复数。 Sequelize然后使用inflection库将别名转换为其单数形式。 但是,这可能并不总是适用于不规则或非英语单词。 在这种情况下,您可以提供复数和单数形式的别名:

  1. User.belongsToMany(Project, { as: { singular: 'task', plural: 'tasks' }})
  2. // Notice that inflection has no problem singularizing tasks, this is just for illustrative purposes.

如果你知道模型将始终在关联中使用相同的别名,则可以在创建模型时提供它

  1. const Project = sequelize.define('project', attributes, {
  2. name: {
  3. singular: 'task',
  4. plural: 'tasks',
  5. }
  6. })
  7. User.belongsToMany(Project);

这将为用户实例添加 add/set/get Tasks 方法。

记住,使用as来更改关联的名称也会改变外键的名称。 当使用as时,也可以指定外键是最安全的。

  1. Invoice.belongsTo(Subscription)
  2. Subscription.hasMany(Invoice)

不使用 as,这会按预期添加 subscriptionId。 但是,如果您要发送Invoice.belongsTo(Subscription, { as: 'TheSubscription' }),那么您将同时拥有 subscriptionIdtheSubscriptionId,因为 sequelize 不够聪明,无法确定调用是相同关系的两面。 foreignKey 修正了这个问题;

  1. Invoice.belongsTo(Subscription, , { as: 'TheSubscription', foreignKey: 'subscription_id' })
  2. Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' )

关联对象

因为 Sequelize 做了很多神奇的事,所以你必须在设置关联后调用 Sequelize.sync。 这样做将允许您进行以下操作:

  1. Project.hasMany(Task)
  2. Task.belongsTo(Project)
  3. Project.create()...
  4. Task.create()...
  5. Task.create()...
  6. // 保存它们.. 然后:
  7. project.setTasks([task1, task2]).then(() => {
  8. // 已保存!
  9. })
  10. // 好的,现在它们已经保存了...我怎么才能得到他们?
  11. project.getTasks().then(associatedTasks => {
  12. // associatedTasks 是一个 tasks 的数组
  13. })
  14. // 您还可以将过滤器传递给getter方法。
  15. // 它们与你能传递给常规查找器方法的选项相同。
  16. project.getTasks({ where: 'id > 10' }).then(tasks => {
  17. // id大于10的任务
  18. })
  19. // 你也可以仅检索关联对象的某些字段。
  20. project.getTasks({attributes: ['title']}).then(tasks => {
  21. // 使用属性“title”和“id”检索任务
  22. })

要删除创建的关联,您可以调用set方法而不使用特定的ID:

  1. // 删除与 task1 的关联
  2. project.setTasks([task2]).then(associatedTasks => {
  3. // 你将只得到 task2
  4. })
  5. // 删除全部
  6. project.setTasks([]).then(associatedTasks => {
  7. // 你将得到空数组
  8. })
  9. // 或更直接地删除
  10. project.removeTask(task1).then(() => {
  11. // 什么都没有
  12. })
  13. // 然后再次添加它们
  14. project.addTask(task1).then(function() {
  15. // 它们又回来了
  16. })

反之亦然你当然也可以这样做:

  1. // project与task1和task2相关联
  2. task2.setProject(null).then(function() {
  3. // 什么都没有
  4. })

对于 hasOne/belongsTo 与其基本相同:

  1. Task.hasOne(User, {as: "Author"})
  2. Task.setAuthor(anAuthor)

可以通过两种方式添加与自定义连接表的关系的关联(继续前一章中定义的关联):

  1. // 在创建关联之前,通过向对象添加具有连接表模型名称的属性
  2. project.UserProjects = {
  3. status: 'active'
  4. }
  5. u.addProject(project)
  6. // 或者在添加关联时提供第二个options.through参数,其中包含应该在连接表中的数据
  7. u.addProject(project, { through: { status: 'active' }})
  8. // 关联多个对象时,可以组合上述两个选项。 在这种情况下第二个参数
  9. // 如果没有提供使用的数据将被视为默认对象
  10. project1.UserProjects = {
  11. status: 'inactive'
  12. }
  13. u.setProjects([project1, project2], { through: { status: 'active' }})
  14. // 上述代码将对项目1记录无效,并且在连接表中对项目2进行active

当获取具有自定义连接表的关联的数据时,连接表中的数据将作为DAO实例返回:

  1. u.getProjects().then(projects => {
  2. const project = projects[0]
  3. if (project.UserProjects.status === 'active') {
  4. // .. 做点什么
  5. // 由于这是一个真正的DAO实例,您可以在完成操作之后直接保存它
  6. return project.UserProjects.save()
  7. }
  8. })

如果您仅需要连接表中的某些属性,则可以提供具有所需属性的数组:

  1. // 这将仅从 Projects 表中选择 name,仅从 UserProjects 表中选择status
  2. user.getProjects({ attributes: ['name'], joinTableAttributes: ['status']})

检查关联

您还可以检查对象是否已经与另一个对象相关联(仅 n:m)。 这是你怎么做的

  1. // 检查对象是否是关联对象之一:
  2. Project.create({ /* */ }).then(project => {
  3. return User.create({ /* */ }).then(user => {
  4. return project.hasUser(user).then(result => {
  5. // 结果是 false
  6. return project.addUser(user).then(() => {
  7. return project.hasUser(user).then(result => {
  8. // 结果是 true
  9. })
  10. })
  11. })
  12. })
  13. })
  14. // 检查所有关联的对象是否如预期的那样:
  15. // 我们假设我们已经有一个项目和两个用户
  16. project.setUsers([user1, user2]).then(() => {
  17. return project.hasUsers([user1]);
  18. }).then(result => {
  19. // 结果是 true
  20. return project.hasUsers([user1, user2]);
  21. }).then(result => {
  22. // 结果是 true
  23. })

外键

当您在sequelize模型中创建关联时,将自动创建具有约束的外键引用。 设置如下:

  1. const Task = this.sequelize.define('task', { title: Sequelize.STRING })
  2. const User = this.sequelize.define('user', { username: Sequelize.STRING })
  3. User.hasMany(Task)
  4. Task.belongsTo(User)

将生成以下SQL:

  1. CREATE TABLE IF NOT EXISTS `User` (
  2. `id` INTEGER PRIMARY KEY,
  3. `username` VARCHAR(255)
  4. );
  5. CREATE TABLE IF NOT EXISTS `Task` (
  6. `id` INTEGER PRIMARY KEY,
  7. `title` VARCHAR(255),
  8. `user_id` INTEGER REFERENCES `User` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
  9. );

在task和user的关系之 中在task上注入user_id外键,并将其标记为User表的引用。默认情况下,如果引用的用户被删除,user_id将被设置为NULL,如果更新了用户标识的id,则会被更新。通过将onUpdateonDelete选项传递给关联调用,可以覆盖这些选项。验证选项为RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL

对于1:1和1:m关联,默认选项为SET NULL用于删除, CASCADE用于更新。对于n:m,两者的默认值为CASCADE。这意味着,如果从n:m关联的一侧删除或更新行,引用该行的连接表中的所有行也将被删除或更新。

在表之间添加约束意味着在使用sequelize.sync时,必须以特定顺序在数据库中创建表。如果Task引用了User,则必须先创建User表,然后才能创建Task表。这有时可能导致循环引用,其中后遗症找不到要同步的顺序。想象一下文件和版本的场景。一个文档可以有多个版本,为方便起见,一个文档可以引用它的当前版本。

  1. const Document = this.sequelize.define('document', {
  2. author: Sequelize.STRING
  3. })
  4. const Version = this.sequelize.define('version', {
  5. timestamp: Sequelize.DATE
  6. })
  7. Document.hasMany(Version) // 这将 document_id 添加到版本
  8. Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id'}) // 这将current_version_id添加到文档

但是,上面的代码将导致以下错误: Cyclic dependency found. 'Document' is dependent of itself. Dependency Chain: Document -> Version => Document.
为了减轻这一点,我们可以将 constraints: false 传递给其中一个关联:

  1. Document.hasMany(Version)
  2. Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id', constraints: false})

这将允许我们正确地同步表:

  1. CREATE TABLE IF NOT EXISTS `Document` (
  2. `id` INTEGER PRIMARY KEY,
  3. `author` VARCHAR(255),
  4. `current_version_id` INTEGER
  5. );
  6. CREATE TABLE IF NOT EXISTS `Version` (
  7. `id` INTEGER PRIMARY KEY,
  8. `timestamp` DATETIME,
  9. `document_id` INTEGER REFERENCES `Document` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
  10. );

强制执行外键引用而不受约束

有时,您可能需要引用另一个表,而不添加任何约束或关联。 在这种情况下,您可以手动将引用属性添加到模式定义,并标记它们之间的关系。

  1. // 在我们调用 Trainer.hasMany(series) 之后 Series 有一个 外参考键 trainer_id=Trainer.id
  2. const Series = sequelize.define('series', {
  3. title: DataTypes.STRING,
  4. sub_title: DataTypes.STRING,
  5. description: DataTypes.TEXT,
  6. // 用 `Trainer` 设置外键关系(hasMany)
  7. trainer_id: {
  8. type: DataTypes.INTEGER,
  9. references: {
  10. model: "trainer",
  11. key: "id"
  12. }
  13. }
  14. })
  15. const Trainer = sequelize.define('trainer', {
  16. first_name: DataTypes.STRING,
  17. last_name: DataTypes.STRING
  18. });
  19. // 在我们调用 Series.hasOne(Video) 之后 Video 有一个 外参考键 series_id=Series.id
  20. const Video = sequelize.define('video', {
  21. title: DataTypes.STRING,
  22. sequence: DataTypes.INTEGER,
  23. description: DataTypes.TEXT,
  24. // 用 `Series` 设置关系(hasOne)
  25. series_id: {
  26. type: DataTypes.INTEGER,
  27. references: {
  28. model: Series, // 可以是表示表名称的字符串,也可以是对模型的引用
  29. key: "id"
  30. }
  31. }
  32. });
  33. Series.hasOne(Video);
  34. Trainer.hasMany(Series);

用关联创建

如果所有元素都是新的,则可以在一个步骤中创建具有嵌套关联的实例。

创建一个 “BelongsTo”, “Has Many” 或 “HasOne” 关联的元素

考虑以下模型:

  1. const Product = this.sequelize.define('product', {
  2. title: Sequelize.STRING
  3. });
  4. const User = this.sequelize.define('user', {
  5. first_name: Sequelize.STRING,
  6. last_name: Sequelize.STRING
  7. });
  8. const Address = this.sequelize.define('address', {
  9. type: Sequelize.STRING,
  10. line_1: Sequelize.STRING,
  11. line_2: Sequelize.STRING,
  12. city: Sequelize.STRING,
  13. state: Sequelize.STRING,
  14. zip: Sequelize.STRING,
  15. });
  16. Product.User = Product.belongsTo(User);
  17. User.Addresses = User.hasMany(Address);
  18. // 也能用于 `hasOne`

可以通过以下方式在一个步骤中创建一个新的Product, User和一个或多个Address:

  1. return Product.create({
  2. title: 'Chair',
  3. user: {
  4. first_name: 'Mick',
  5. last_name: 'Broadstone',
  6. addresses: [{
  7. type: 'home',
  8. line_1: '100 Main St.',
  9. city: 'Austin',
  10. state: 'TX',
  11. zip: '78704'
  12. }]
  13. }
  14. }, {
  15. include: [{
  16. association: Product.User,
  17. include: [ User.Addresses ]
  18. }]
  19. });

这里,我们的用户模型称为user,带小写u - 这意味着对象中的属性也应该是user。 如果给sequelize.define指定的名称为User,对象中的键也应为User。 对于addresses也是同样的,除了它是一个 hasMany 关联的复数。

用别名创建一个 “BelongsTo” 关联的元素

可以将前面的示例扩展为支持关联别名。

  1. const Creator = Product.belongsTo(User, {as: 'creator'});
  2. return Product.create({
  3. title: 'Chair',
  4. creator: {
  5. first_name: 'Matt',
  6. last_name: 'Hansen'
  7. }
  8. }, {
  9. include: [ Creator ]
  10. });

创建 “HasMany” 或 “BelongsToMany” 关联的元素

我们来介绍将产品与许多标签相关联的功能。 设置模型可能如下所示:

  1. const Tag = this.sequelize.define('tag', {
  2. name: Sequelize.STRING
  3. });
  4. Product.hasMany(Tag);
  5. // Also works for `belongsToMany`.

现在,我们可以通过以下方式创建具有多个标签的产品:

  1. Product.create({
  2. id: 1,
  3. title: 'Chair',
  4. tags: [
  5. { name: 'Alpha'},
  6. { name: 'Beta'}
  7. ]
  8. }, {
  9. include: [ Tag ]
  10. })

然后,我们可以修改此示例以支持别名:

  1. const Categories = Product.hasMany(Tag, {as: 'categories'});
  2. Product.create({
  3. id: 1,
  4. title: 'Chair',
  5. categories: [
  6. {id: 1, name: 'Alpha'},
  7. {id: 2, name: 'Beta'}
  8. ]
  9. }, {
  10. include: [{
  11. model: Categories,
  12. as: 'categories'
  13. }]
  14. })