3. 模型(表)之间的关系/关联
模型(Model
)之间存在各种各样的关系,如:一对一(One-To-One )、一对多(One-To-Many)等。模型间的关系本质上是对其代表的数据库中表之间的关系描述,通过这些关系可以实现数据库中表之间主/外键约束的创建。查询时也可以基于这些关系,生成在数据库中执行的连接查询或复合查询SQL语句。
- 关系/关联的使用
- 1.1 一对一(One-To-One)关联
- 1.2 一对多(One-To-Many)关联
- 1.3 多对多(Belongs-To-Many)关联
- 1.4 Scopes - 作用域
- 1.5 命名策略
- 1.6 关联对象
- 1.7 关系检查
- 1.8 通过关联创建实例
- 关系/关联相关的API
- 2.1 综合介绍
- 2.2
Model.hasOne()
- 拥有一个 - 2.3
Model.belongsTo()
- 属于 - 2.4
Model.hasMany()
- 拥有多个 - 2.5
Model.belongsToMany()
- 多对多
1. 关系/关联的使用
Sequelize 中的模型存在多种关系。在一个User.hasOne(Project)
形式的调用中,正在调用的模型User
是源模型
而做为参数被传入的模型是目标模型
。
1.1 一对一(One-To-One)关联
一对一关联是由一个单一的外键,实现两个模型之间的精确关联。
BelongsTo - 属于
BelongsTo关联表示一对一关系的外键存在于源模型
。
如,下例中Player
是通过外键关联的Team
的一部分:
var Player = this.sequelize.define('player', {/* attributes */}) , Team = this.sequelize.define('team', {/* attributes */}); Player.belongsTo(Team); // 会为Player添加一个teamId 属性以保持与Team 主键的关系
外键
默认情况下,一个属于关系的外键将从目标模型的名称和主键名称生成。
默认命名使用驼峰式命名
,而在源模型中添加了underscored: true
配置,将使用蛇型命名
。
var User = this.sequelize.define('user', {/* attributes */}) , Company = this.sequelize.define('company', {/* attributes */}); User.belongsTo(Company); // 会为user 添加 companyId 属性 var User = this.sequelize.define('user', {/* attributes */}, {underscored: true}) , Company = this.sequelize.define('company', { uuid: { type: Sequelize.UUID, primaryKey: true } }); User.belongsTo(Company); // 会为user 添加 company_uuid 属性
在定义中使用as
命名时,会将其做为目标模型的名称:
var User = this.sequelize.define('user', {/* attributes */}) , UserRole = this.sequelize.define('userRole', {/* attributes */}); User.belongsTo(UserRole, {as: 'role'}); // 会为 user添加 roleId 属性而不是 userRoleId
在任命情况下,使用使用了foreignKey
选项,外键名都会使用此选项值。我可以在Sequelize 中像下面这样使用外键:
var User = this.sequelize.define('user', {/* attributes */}) , Company = this.sequelize.define('company', {/* attributes */}); User.belongsTo(Company, {foreignKey: 'fk_company'}); // 为User 添加fk_company 外键
目标键
目标键是位于目标模型上通过源模型外键列指向的列。默认情况下,目标键是会belongsTo
关系中目标模型的主键。要使用自定义列,请用targetKey
选项来指定:
var User = this.sequelize.define('user', {/* attributes */}) , Company = this.sequelize.define('company', {/* attributes */}); User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // 为User 添加 fk_companyname 目标键
HasOne - 拥有一个
HasOne关联表示一对一关系的外键存在于目标模型
。
var User = sequelize.define('user', {/* ... */}) var Project = sequelize.define('project', {/* ... */}) // hasOne 关系 Project.hasOne(User) /* 在这个示例中,hasOne会添加一个projectId 属性到User模型中 另外,Project.prototype 中会增加根据传入的第一个定义参数生成的访问器方法 getUser 和 setUser 置。 如果启用了underscore 设置,添加的属性会是 project_id 而不是 projectId. 外键会存在于users 表中 你同样可以自定义外键,如:你想使用一个已存在数据库: */ Project.hasOne(User, { foreignKey: 'initiator_id' }) /* 因为 Sequelize 会访问器使用模型名(定义时的第一个参数), 如果不使用这个名称,可以在hasOne 的选项中指定: */ Project.hasOne(User, { as: 'Initiator' }) // 这时会有 Project#getInitiator 和 Project#setInitiator // 或者可以定义一些自引用 var Person = sequelize.define('person', { /* ... */}) Person.hasOne(Person, {as: 'Father'}) // 会为Person 增加一个FatherId 属性 // 同样可以自定义外键: Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'}) // 会为Person 增加一个 DadId 属性 // 这两种情况下都会有以下两个方法: Person#setFather Person#getFather // 如果想对一个表做两次连接查询: Team.hasOne(Game, {as: 'HomeTeam', foreignKey : 'homeTeamId'}); Team.hasOne(Game, {as: 'AwayTeam', foreignKey : 'awayTeamId'}); Game.belongsTo(Team);
虽然被称为HasOne 关联,但大多数 1:1关系中通常会使用BelongsTo 关联,因为BelongsTo 会在源模型中添加外键,而HasOne 则会在目标模型中添加外键。
HasOne 与BelongsTo 的不同
在1:1 的关系中,可使用HasOne 或BelongsTo来定义。但它们的使用场景有所不同,下面通过一个例子来说明。
我们有两张表,会别关联到Player
和Team
模型,定义如下:
var Player = this.sequelize.define('player', {/* attributes */}) , Team = this.sequelize.define('team', {/* attributes */});
在Sequelize 中我们可以源
和目标
模型的形式将二者建立联系。如将Player
做为源模型
,而将Team
做为目标模型
:
Player.belongsTo(Team); //Or Player.hasOne(Team);
或Team
做为源模型
,而将Player
做为目标模型
:
Team.belongsTo(Player); //Or Team.hasOne(Player);
HasOne 和 BelongsTo 插入外键的位置有所不同。HasOne 会向目标模型
中插入关联键,而BelongsTo 会向源模型
中插入关联键。
一个演示HasOne 和 BelongsTo使用的示例:
var Player = this.sequelize.define('player', {/* attributes */}) , Coach = this.sequelize.define('coach', {/* attributes */}) , Team = this.sequelize.define('team', {/* attributes */});
假设Player
模型通过teamId
列与其团队建立联系,每个团队的教练Coach
信息通过coachId
列存储。在这些 1:1 场景中需要以不同的方式建立关系,因为模型外键的存储位置不同。
当信息关联是存在于当前源模型
时,我们可以使用belongsTo
。在上面示例中,Player
适合使用belongsTo
,因为它有teamId
列。
Player.belongsTo(Team) // `teamId` 会添加到 Player / 源模型
当信息关联是存在于当前目标模型
时,我们可以使用hasOne
。在上面示例中,Coach
适合使用hasOne
,因为model
模型中存储了它的Coach
信息的coachId
字段。
Coach.hasOne(Team) // `coachId` 会添加到 Team / 目标模型
1.2 一对多(One-To-Many)关联
One-To-Many关联是指一个源模型
连接多个目标模型
。反之目标模型都会有一个明确的源。
var User = sequelize.define('user', {/* ... */}) var Project = sequelize.define('project', {/* ... */}) // 定义 hasMany 关联 Project.hasMany(User, {as: 'Workers'})
会向 User 中添加一个projectId
或project_id
属性。Project 的实例中会有访问器getWorkers
和 setWorkers
。这是一种单向关联方式,如果两个模型间还有其它关联方式请参考下面的多对多关系。
1.3 多对多(Belongs-To-Many)关联
Belongs-To-Many 关联是指一个源模型
连接多个目标模型
。而且,目标模型也可以有多个相关的源。
Project.belongsToMany(User, {through: 'UserProject'}); User.belongsToMany(Project, {through: 'UserProject'});
这会创建一个新模型UserProject
其中会projectId
和userId
两个外键。是否使用驼峰命名取决与相关联的两个表。
定义through
选项后,Sequelize会尝试自动生成名字,但并一定符合逻辑。
在本例中,会为User
添加方法 getUsers
, setUsers
, addUser
,addUsers
to Project
, and getProjects
, setProjects
, addProject
, and addProjects
有时我们会对连接的模型进行重命名,同样可以使用as
实现。如,将User 命名为Workers ,将Project 命名为 Tasks:
User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' }) Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' })
foreignKey
让你可以设置through
关系中的源模型
,而otherKey
让你可以through
关系中的目标模型
。
User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId', otherKey: 'projectId'})
belongsToMany
同样可以用来定义自连接:
Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' }) // 这会创建 PersonChildren 表,其中存储了对象的 id
如果想为连接表添加更多属性,可以建立关联前在sequelize 中为连接表定义一个模型,这会告诉sequelize 使用这个表建立关联而不是创建新表:
User = sequelize.define('user', {}) Project = sequelize.define('project', {}) UserProjects = sequelize.define('userProjects', { status: DataTypes.STRING }) User.belongsToMany(Project, { through: UserProjects }) Project.belongsToMany(User, { through: UserProjects })
要为user 添加一个新的project 并设置状态,可以在设置器中传入一个额外的对象,这个属性会包含在连接表中:
user.addProject(project, { status: 'started' })
默认情况下会向UserProjects表中添加projectId 和 userId,并移除之前已定义的主键属性表并由两个表的主键组建唯一标识,且没有额外的PK 列。要强制为UserProjects
添加主键可以手工添加:
UserProjects = sequelize.define('userProjects', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, status: DataTypes.STRING })
Belongs-To-Many 可以基于through
关系查询并可以选择查询属性:
User.findAll({ include: [{ model: Project, through: { attributes: ['createdAt', 'startedAt', 'finishedAt'], where: {completed: true} } }] });
1.4 Scopes - 作用域
关系作用域允许你设置一个作用范围(一组用于设置和创建的默认属性)。作用域同样也以放在通过 n:m 建立关系的表的关联模型(关联目标)中。
1:m
假设有Comment, Post 和 Image三个表,Comment表可以通过commentable_id
和commentable
字段分别关联image 或 post 表关联:
this.Comment = this.sequelize.define('comment', { title: Sequelize.STRING, commentable: Sequelize.STRING, commentable_id: Sequelize.INTEGER }, { instanceMethods: { getItem: function() { return this['get' + this.get('commentable').substr(0, 1).toUpperCase() + this.get('commentable').substr(1)](); } } }); this.Post.hasMany(this.Comment, { foreignKey: 'commentable_id', constraints: false, scope: { commentable: 'post' } }); this.Comment.belongsTo(this.Post, { foreignKey: 'commentable_id', constraints: false, as: 'post' }); this.Image.hasMany(this.Comment, { foreignKey: 'commentable_id', constraints: false, scope: { commentable: 'image' } }); this.Comment.belongsTo(this.Image, { foreignKey: 'commentable_id', constraints: false, as: 'image' });
constraints: false
禁止了引用限制-因为commentable_id
列会引用多个表,所以不能添加REFERENCES
选项限制它。注意,这样就为Image -> Comment 和 Post -> Comment之间关系定义了一个作用域,commentable:'image'
和commentable:'post'
分别对应了不同的表。这个作用域会自动应用到关联函数中:
image.getComments() SELECT * FROM comments WHERE commentable_id = 42 AND commentable = 'image'; image.createComment({ title: 'Awesome!' }) INSERT INTO comments (title, commentable_id, commentable) VALUES ('Awesome!', 42, 'image'); image.addComment(comment); UPDATE comments SET commentable_id = 42, commentable = 'image'
而Comment模型的工具函数getItem
会根据commentable
的不同而进行简单的转换为getImage
或getPost
。
n:m
继续使用多态模型,一个标签(tag)表-一个项目(item)可以有多个标签,而一个标签也可以属于多个项目。
下面是一个简单示例,一个Post模型和Tag模型,而现实中标签往往被关联到多个其它模型:
ItemTag = sequelize.define('item_tag', { tag_id: { type: DataTypes.INTEGER, unique: 'item_tag_taggable' }, taggable: { type: DataTypes.STRING, unique: 'item_tag_taggable' }, taggable_id: { type: DataTypes.INTEGER, unique: 'item_tag_taggable', references: null } }); Tag = sequelize.define('tag', { name: DataTypes.STRING }); Post.belongsToMany(Tag, { through: { model: ItemTag, unique: false, scope: { taggable: 'post' } }, foreignKey: 'taggable_id', constraints: false }); Tag.belongsToMany(Post, { through: { model: ItemTag, unique: false }, foreignKey: 'tag_id' });
作用范围列(taggable
)实关联到了through
关系模型(ItemTag
)。
我们可以定义一个更严格的关联关系,如为post 应用获取所有pending
状态的标签的作用范围:
Post.hasMany(Tag, { through: { model: ItemTag, unique: false, scope: { taggable: 'post' } }, scope: { status: 'pending' }, as: 'pendingTags', foreignKey: 'taggable_id', constraints: false }); Post.getPendingTags();
SELECT `tag`.* INNER JOIN `item_tags` AS `item_tag` ON `tag`.`id` = `item_tag`.`tagId` AND `item_tag`.`taggable_id` = 42 AND `item_tag`.`taggable` = 'post' WHERE (`tag`.`status` = 'pending');
1.5 命名策略
默认情况下,Sequelize会使用模型名(名称通过sequelize.define)来识别模型并与其它模型建立关联。如,一个名为user
的模型,会向其实例添加get/set/add User
等函数,且一个.user
属性会在预加载模型中,而对于一个名为User
模型来说会添加相同的函数,但预加载中会使用.User
属性。
我在前面介绍过,可以使用as
对模型起一个别名。在单一的关系中(hasOne或belongsTo),别名应该使用单数形式,而在多重关系中(hasMany)应该使用复数形式。Sequelize会使用inflection库,对别名的单复数形式进行转换。然而这一转换在非英语单词中可能不适用,这时我们以同时指定单数和复数两个别名:
User.belongsToMany(Project, { as: { singular: 'task', plural: 'tasks' }})
如果你希望总是使用相同的别名,那么可以定义模型时指定:
var Project = sequelize.define('project', attributes, { name: { singular: 'task', plural: 'tasks', } }) User.belongsToMany(Project);
会向 user 实例中添加add/set/get Tasks
等函数。
请注意使用as
指定别名后,同时也会改变关联的外键列,比较保险的做法是同时指定外键列:
Invoice.belongsTo(Subscription) Subscription.hasMany(Invoice)
不使用as
时,一般会生成一个subscriptionId
的外键,而通过Ivoice.belongsTo(Subscription, { as: 'TheSubscription' })
指定别名后,外键会变成theSubscriptionId
,这时可以通过foreignKey
选项解决这个问题:
Invoice.belongsTo(Subscription, , { as: 'TheSubscription', foreignKey: 'subscription_id' }) Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' )
1.6 关联对象
因为Sequelize 做了很多逻辑运算等,你应该在定义完关系后sequelize.sync
方法:
Project.belongsToMany(Task) Task.belongsToMany(Project) Project.create()... Task.create()... Task.create()... // 保存……然后 project.setTasks([task1, task2]).then(function() { // saved! }) // 保存后可以在then 中访问结果 project.getTasks().then(function(associatedTasks) { // associatedTasks 是一个task 数组 }) // 同可以在访问器方法中进行筛选 // 这与传入一个普通这查询方法一样 project.getTasks({ where: 'id > 10' }).then(function(tasks) { // tasks 中 id 都大于 10 :) }) // 可以检索相关对象的某些字段. project.getTasks({attributes: ['title']}).then(function(tasks) { // 仅返回 "title" 和 "id"字段 })
通过设置(set)方法还可以移除已创建的关系:
// 称除与 task1 的关联 project.setTasks([task2]).then(function(associatedTasks) { // 现在只和 task2 有关系 }) // 移除所有关联 project.setTasks([]).then(function(associatedTasks) { // 现在返回一个空数组 }) // 或者直接删除 project.removeTask(task1).then(function() { // it's gone }) // 也可以重新添加 project.addTask(task1).then(function() { // it's back again })
也可像下面这样移除:
// project 与task1 和 task2有关系 task2.setProject(null).then(function() { // and it's gone })
hasOne/belongsTo中同样适用:
Task.hasOne(User, {as: "Author"}) Task.setAuthor(anAuthor)
在自定义表连接中,可以通过两种方法添加关联关系:
// 在创建该关联之前,将属性添加到对象的连接表模型名称中 project.UserProjects = { status: 'active' } u.addProject(project) // 或者,通过在添加关联时提供一个额外的参数,其中包含在联接表中的数据 u.addProject(project, { status: 'active' }) // 当关联多个对象时,同样可以使用上面两种方法 // 在本例中,提供一个包含在联接表中的数据额外的参数 project1.UserProjects = { status: 'inactive' } u.setProjects([project1, project2], { status: 'active' })
当获取自定义连接表的关联数据时,连接表会做为一个DAO 实例返回:
u.getProjects().then(function(projects) { var project = projects[0] if (project.UserProjects.status === 'active') { // .. do magic // 因为这里是一个真正的 DAO 实例,所以可以直接保存 return project.UserProjects.save() } })
如果只想获取关联表的部分属性,可以提供一个想要获取属性和数组:
user.getProjects({ attributes: ['name'], joinTableAttributes: ['status']})
1.7 关系检查
我们可以检查一对象是否已与另一个对象建立关联,下面是一些检查示例:
// 检查对象是否是关联对象之一: Project.create({ /* */ }).then(function(project) { return User.create({ /* */ }).then(function(user) { return project.hasUser(user).then(function(result) { // false return project.addUser(user).then(function() { return project.hasUser(user).then(function(result) { // true }) }) }) }) }) // 检查所有对象是符合预期: // 假设已有一个 project 和两个 users project.setUsers([user1, user2]).then(function() { return project.hasUsers([user1]); }).then(function(result) { // false return project.hasUsers([user1, user2]); }).then(function(result) { // true })
1.8 外键
Sequelize中,如果创建了两个模型之间的关联,那么相关联的外键会被自动创建:
var Task = this.sequelize.define('task', { title: Sequelize.STRING }) , User = this.sequelize.define('user', { username: Sequelize.STRING }) User.hasMany(Task) Task.belongsTo(User)
会生成以下SQL 语句:
CREATE TABLE IF NOT EXISTS `User` ( `id` INTEGER PRIMARY KEY, `username` VARCHAR(255) ); CREATE TABLE IF NOT EXISTS `Task` ( `id` INTEGER PRIMARY KEY, `title` VARCHAR(255), `user_id` INTEGER REFERENCES `User` (`id`) ON DELETE SET NULL ON UPDATE CASCADE );
task 和 user之间的关系通过task表的外键user_id
引入,并REFERENCES
引用user表。默认情况下,引用的user 如果被删除那么user_id
会被设置为NULL
,并且会随user id的更新而更新。这些选项可以在建立关系时通过MonUpdate
和onDelete
选项修改。可选项有:
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 之前创建。有时这会导致循环引用,想象一个场景:一个文档和版本,一个文档可以有多个版本,并且为了方便,文档对它的当前版本有一个引用。
var Document = this.sequelize.define('document', { author: Sequelize.STRING }) , Version = this.sequelize.define('version', { timestamp: Sequelize.DATE }) Document.hasMany(Version) // 添中 document_id 到 version中 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
选项:
Document.hasMany(Version) Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id', constraints: false})
这时会按以下顺序将表同步到数据库中:
CREATE TABLE IF NOT EXISTS `Document` ( `id` INTEGER PRIMARY KEY, `author` VARCHAR(255), `current_version_id` INTEGER ); CREATE TABLE IF NOT EXISTS `Version` ( `id` INTEGER PRIMARY KEY, `timestamp` DATETIME, `document_id` INTEGER REFERENCES `Document` (`id`) ON DELETE SET NULL ON UPDATE CASCADE );
没有约束的外键引用
有时我们想添加一个外键引用,但不想添加任何约束或关系。这种情形下,你可以在schema定义时手动添加reference
属性:
var Series, Trainer, Video // 在调用Trainer.hasMany(series)方法的,Series 有一个 trainer_id=Trainer.id 的外键引用 Series = sequelize.define('series', { title: DataTypes.STRING, sub_title: DataTypes.STRING, description: DataTypes.TEXT, // Set FK relationship (hasMany) with `Trainer` trainer_id: { type: DataTypes.INTEGER, references: { model: "trainers", key: "id" } } }) Trainer = sequelize.define('trainer', { first_name: DataTypes.STRING, last_name: DataTypes.STRING }); // 在调用Series.hasOne(Video)后,Video has a series_id=Series.id 的外键引用…… Video = sequelize.define('video', { title: DataTypes.STRING, sequence: DataTypes.INTEGER, description: DataTypes.TEXT, // 为`Series`设置 hasOne关系 series_id: { type: DataTypes.INTEGER, references: { model: Series, // 可以是一个表示表名的字符串或模型引用 key: "id" } } }); Series.hasOne(Video); Trainer.hasMany(Series);
1.8 通过关联创建实例
一个实例可以在一个步骤中创建嵌套关联,且提供所有元素都是新的。
通过BelongsTo 或HasOne 关系创建实例
对于如相模型:
var Product = this.sequelize.define('product', { title: Sequelize.STRING }); var User = this.sequelize.define('user', { first_name: Sequelize.STRING, last_name: Sequelize.STRING }); Product.belongsTo(User); // `hasOne`同样适用
一个Product
和User
在同一步中被创建:
return Product.create({ title: 'Chair', User: { first_name: 'Mick', last_name: 'Broadstone' } }, { include: [ User ] });
通过BelongsTo 关系的别名创建
var Creator = Product.belongsTo(User, {as: 'creator'}); return Product.create({ title: 'Chair', creator: { first_name: 'Matt', last_name: 'Hansen' } }, { include: [ Creator ] });
通过HasMany 或BelongsToMany 关系创建实例
对于以下关系的模型:
var Tag = this.sequelize.define('tag', { name: Sequelize.STRING }); Product.hasMany(Tag); // `belongsToMany` 同样适用
现在我们可以创建一个project 同时创建多个相关联的tag:
Product.create({ id: 1, title: 'Chair', Tags: [ { name: 'Alpha'}, { name: 'Beta'} ] }, { include: [ Tag ] })
在使用别名时也可以使用:
var Categories = Product.hasMany(Tag, {as: 'categories'}); Product.create({ id: 1, title: 'Chair', categories: [ {id: 1, name: 'Alpha'}, {id: 2, name: 'Beta'} ] }, { include: [{ model: Categories, as: 'categories' }] })
2. 关系/关联相关的API
2.1 综合介绍
在Sequelize中创建关联通过调用模型(源
)的 belongsTo / hasOne / hasMany / belongsToMany方法完成,并且为这个方法第一个参数提供另一个模型(目标
)。各种方法以下规则创建关联:
hasOne
- 添加外键到目标模型,并以单数关系混入到源模型belongsTo
- 为当前模型添加外键,并以单数关系混入到源模型hasMany
- 添加外键到目标模型,并以复数关系混入到源模型belongsToMany
- 为连接的表创建N:M
关系并以复数关系混入到源模型。会通过sourceId
和targetId
创建交叉表。
在创建关系时,可以通过as
选项指定别名。这在对一模型引用两次,或者对关联模型使用定义之外的名称时非常有用。
User.hasMany(Picture) User.belongsTo(Picture, { as: 'ProfilePicture', constraints: false }) user.getPictures() // 获取所有图片 user.getProfilePicture() // 仅获取主图 User.findAll({ where: ..., include: [ { model: Picture }, // 加载所有图片 { model: Picture, as: 'ProfilePicture' }, // 加载主图,名称拼写必须与关联关系中命名相同 ] })
要完全控制通过Sequlize 添加的外键列,可以使用foreignKey
选项。选项值可以是表示名称的字符串或类似使用sequelize.define
进行模型定义时对象。
User.hasMany(Picture, { foreignKey: 'uid' })
这样外键列会使用uid
代替默认的userId
。
User.hasMany(Picture, { foreignKey: { name: 'uid', allowNull: false } })
指定uid
列不能为NULL
。在大多数情况下,这将覆盖的外键约束,这sequelize自动创建的,这在外键禁用时非常有用。
当匹配关联模型时,可限制只匹配部分模型。这些查询条件与在find/findAll
中的使用方式相同。如,只查找'jpg'格式的图片:
user.getPictures({ where: { format: 'jpg' } })
2.2 Model.hasOne()
- 拥有一个
Model.hasOne(target, [options])
创建当前模型(源)到目标模型之间的关系,外键会被添加到目标模型中。
名称 | 类型 | 说明 |
---|---|---|
target | Model | |
[options] | object | |
[options.hooks=false] | boolean | 设置为 true 时,会在关联模型删除时执行 before-/afterDestroy 钩子方法 |
[options.as] | string | 当前模型(源)的别名,单数形式。如果你为一个表创建多次关联,或者不想使用定义模型时使用的名称,那么就应该为模型指定一个别名。 |
[options.foreignKey] | string | object | 目标表中的外键名或相当于定义外键列的对象 (语法参考 Sequelize.define )。使用对象时,应该添加一个name 来设置列名。默认的外键命名规为源模型名+源模型主键名 |
[options.onDelete='SET NULL | CASCADE'] | string | 如果外允许空则 SET NULL,其它则 CASCADE |
[options.onUpdate='CASCADE'] | string | |
[options.constraints=true] | boolean | 是否在删除或更新时启用外键约束 |
2.3 Model.belongsTo()
- 属于
Model.belongsTo(target, [options])
创建当前模型(源)到目标模型之间的关系,外键会被添加到源模型中。
名称 | 类型 | 说明 |
---|---|---|
target | Model | |
[options] | object | |
[options.hooks=false] | boolean | 设置为 true 时,会在关联模型删除时执行 before-/afterDestroy 钩子方法 |
[options.as] | string | 当前模型(源)的别名,单数形式。如果你为一个表创建多次关联,或者不想使用定义模型时使用的名称,那么就应该为模型指定一个别名。 |
[options.foreignKey] | string | object | 目标表中的外键名或相当于定义外键列的对象 (语法参考 Sequelize.define )。使用对象时,应该添加一个name 来设置列名。默认的外键命名规为源模型名+源模型主键名 |
[options.scope] | object | 键/值 集合,用于目标的创建和查找操作(sqlite 不支持 N:M) |
[options.onDelete='SET NULL | NO ACTION'] | string | 如果外允许空则 SET NULL,其它则 CASCADE |
[options.onUpdate='CASCADE'] | string | |
[options.constraints=true] | boolean | 是否在删除或更新时启用外键约束 |
2.4 Model.hasMany()
- 拥有多个
Model.hasMany(target, [options])
创建当前模型(源)到目标模型之间的 1:m 的关系,外键会被添加到目标模型中。
名称 | 类型 | 说明 |
---|---|---|
target | Model | |
[options] | object | |
[options.hooks=false] | boolean | 设置为 true 时,会在关联模型删除时执行 before-/afterDestroy 钩子方法 |
[options.as] | string | 当前模型(源)的别名,单数形式。如果你为一个表创建多次关联,或者不想使用定义模型时使用的名称,那么就应该为模型指定一个别名。 |
[options.foreignKey] | string | object | 目标表中的外键名或相当于定义外键列的对象 (语法参考 Sequelize.define )。使用对象时,应该添加一个name 来设置列名。默认的外键命名规为源模型名+源模型主键名 |
[options.targetKey] | string | 用于关联目标表的字段名。默认为目标表的主键。 |
[options.onDelete='SET NULL | NO ACTION'] | string | 如果外允许空则 SET NULL,其它则 CASCADE |
[options.onUpdate='CASCADE'] | string | |
[options.constraints=true] | boolean | 是否在删除或更新时启用外键约束 |
2.5 Model.belongsToMany()
- 多对多
Model.belongsToMany(target, [options])
创建连接表的 N:M 的关系
User.belongsToMany(Project, { through: 'UserProjects' }) Project.belongsToMany(User, { through: 'UserProjects' })
定义中指定需要through
时,sequelize会尝试自动生成名字,但生成的名字并不一定符合逻辑。
你通过自定义属性定义一个模型,它的属性可以用两种方式添加/设置关联。
var UserProjects = sequelize.define('UserProjects', { started: Sequelize.BOOLEAN }) User.belongsToMany(Project, { through: UserProjects }) Project.belongsToMany(User, { through: UserProjects })
jan.addProject(homework, { started: false }) // homework 工程还未开始 jan.setProjects([makedinner, doshopping], { started: true}) // shopping和dinner 两种方式都会启动
如果你想设置多个目标实例,但是有不同的属性,这时必须在实例上设置属性:
p1.UserProjects = { started: true } user.setProjects([p1, p2], {started: false})
类似的,使用自定义属性连接表时,这些属性将做为一个对象的名称:
user.getProjects().then(function (projects) { var p1 = projects[0] p1.UserProjects.started // Is this project started yet? })
名称 | 类型 | 说明 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
target | Model | ||||||||||
[options] | object | ||||||||||
[options.hooks=false] | boolean | 设置为 true 时,会在关联模型删除时执行 before-/afterDestroy 钩子方法 | |||||||||
[options.through] | Model | string | object | 在N:M 的关联中,用于连接源 和 目标 表的名称 | |||||||||
[options.through.model] | Model | 用于连接 N:M 关系的模型 | [options.through.scope] | object | 用于建立关联的键/值集合,并通过模型查找默认值。 | [options.through .unique=true] | boolean | 设置为 true时,唯一键会从使用的外键中生成 | [options.as] | string | 当前模型(源)的别名,单数形式。如果你为一个表创建多次关联,或者不想使用定义模型时使用的名称,那么就应该为模型指定一个别名。 |
[options.foreignKey] | string | object | 目标表中的外键名或相当于定义外键列的对象 (语法参考 Sequelize.define )。使用对象时,应该添加一个name 来设置列名。默认的外键命名规为源模型名+源模型主键名 | |||||||||
[options.otherKey] | string | object | 连接表的外键名称(表示目标模型)或表示其它列的类型定义(见sequelize.define 语法)。使用对象时,可以添加一个name 属性以设置目标列,默认为 目标模型名称 + 目标主键的名称 | |||||||||
[options.onDelete='SET NULL | NO ACTION'] | string | 如果外允许空则 SET NULL,其它则 CASCADE | |||||||||
[options.onUpdate='CASCADE'] | string | ||||||||||
[options.constraints=true] | boolean | 是否在删除或更新时启用外键约束 |