Sequelize-nodejs-7-Associations
Associations關聯性
This section describes the various association types in sequelize. When calling a method such as User.hasOne(Project)
, we say that the User
model (the model that the function is being invoked on) is the source and the Project
model (the model being passed as an argument) is the target
這個部分描述了sequelize中的多種關聯型別。當呼叫例如User.hasOne(Project)
這個方法時,我們說模型(函式正呼叫的)是來源,Project
模型(作為引數傳遞的模型)是目標
One-To-One associations
One-To-One associations are associations between exactly two models connected by a single foreign key.
一對一關聯是兩個通過一個外來鍵連線的模型之間的關聯性
BelongsTo
BelongsTo associations are associations where the foreign key for the one-to-one relation exists on the source model
BelongsTo關聯是存在於來源模型一對一關係中的外來鍵的關聯性
A simple example would be a Player being part of a Team with the foreign key on the player.
一個簡單的例子,Player是帶有player外來鍵的Team的一部分
const Player = this.sequelize.define('player', {/* attributes */}); const Team = this.sequelize.define('team', {/*attributes */}); Player.belongsTo(Team); // Will add a teamId attribute to Player to hold the primary key value for Team
Player中將會新增一個teamId屬性(這就是外來鍵),儲存Team的主鍵值
Foreign keys(寫在來源模型中)
By default the foreign key for a belongsTo relation will be generated from the target model name and the target primary key name.
預設belongsTo關係的外來鍵將被目標模型生成並以目標主鍵命名
The default casing is camelCase
however if the source model is configured with underscored: true
the foreignKey will be snake_case
.
命名格式預設為camelCase
拼寫法(即類似myForeignKeys形式)。如果來源模型配置為underscored: true
,那麼外來鍵將使用snake_case
拼寫法(即類似my_foreign_keys)
const User = this.sequelize.define('user', {/* attributes */}) const Company = this.sequelize.define('company', {/* attributes */}); User.belongsTo(Company); // Will add companyId to user,外來鍵名命名為companyId(camelCase
) const User = this.sequelize.define('user', {/* attributes */}, {underscored: true}) const Company = this.sequelize.define('company', { uuid: { type: Sequelize.UUID, primaryKey: true } }); User.belongsTo(Company); // Will add company_uuid to user,外來鍵名命名為company_uuid(snake_case
)
In cases where as
has been defined it will be used in place of the target model name.
如果使用了as
,將使用其替代目標模型的名字來命名外來鍵
const User = this.sequelize.define('user', {/* attributes */}) const UserRole = this.sequelize.define('userRole', {/* attributes */}); User.belongsTo(UserRole, {as: 'role'}); // Adds roleId to user rather than userRoleId
In all cases the default foreign key can be overwritten with the foreignKey
option. When the foreign key option is used, Sequelize will use it as-is:
在所有情況下,預設外來鍵可以被foreignKey
選項值複寫。當foreignKey
選項值被使用時,Sequelize將使用其作為外來鍵名
const User = this.sequelize.define('user', {/* attributes */}) const Company = this.sequelize.define('company', {/* attributes */}); User.belongsTo(Company, {foreignKey: 'fk_company'}); // Adds fk_company to User
Target keys
The target key is the column on the target model that the foreign key column on the source model points to. By default the target key for a belongsTo relation will be the target model's primary key. To define a custom column, use the targetKey
option.
目標鍵是目標模型中來源模型指向的外來鍵列對應的列。預設目標鍵的belongsTo關係將是目標模型的主鍵。為了定義自定義列,使用的是targetKey
選項
const User = this.sequelize.define('user', {/* attributes */}) const Company = this.sequelize.define('company', {/* attributes */}); User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // Adds fk_companyname to User
說明來源模型User的外來鍵名字是fk_companyname,它指向的是目標模型Company的name列
HasOne
HasOne associations are associations where the foreign key for the one-to-one relation exists on the target model.
HasOne關聯性是存在在目標模型中一對一關係的外來鍵的關聯性
const User = sequelize.define('user', {/* ... */}) const Project = sequelize.define('project', {/* ... */}) // One-way associations Project.hasOne(User) /* 在這個例子中,hasOne將會新增屬性projectId到User模型中 而且Project.prototype將會得到getUser和setUser方法,並根據傳遞的第一個引數去定義。 如果下劃線型別可用,新增的屬性名將使用project_id替代projectId 外來鍵將被放在users表中 你也可以定義外來鍵名,比如,如果你已經有一個存在的資料庫並想要執行它: */ Project.hasOne(User, { foreignKey: 'initiator_id' })//這就是自己定義了外來鍵名 /* for因為Sequelize將使用模型名(定義的第一個變數)給訪問器方法,還是有傳遞一個特殊的選項給hasOne的可能的: */ Project.hasOne(User, { as: 'Initiator' }) //然後你就可以呼叫Project.getInitiator和Project.setInitiator方法來獲得外來鍵和改變外來鍵 // 或者是定義一些自我引用 const Person = sequelize.define('person', { /* ... */}) Person.hasOne(Person, {as: 'Father'}) // 將會新增FatherId屬性給Person // also possible: Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'}) //將會新增DadId屬性給Person //因為設定as,所以還能夠呼叫一下的兩個方法去得到外來鍵和改變外來鍵 Person.setFather Person.getFather // 如果你需要連線一個表兩次,你可以連線兩次同一個表 Team.hasOne(Game, {as: 'HomeTeam', foreignKey : 'homeTeamId'}); Team.hasOne(Game, {as: 'AwayTeam', foreignKey : 'awayTeamId'}); Game.belongsTo(Team);
Even though it is called a HasOne association, for most 1:1 relations you usually want the BelongsTo association since BelongsTo will add the foreignKey on the source where hasOne will add on the target.
即使被稱作HasOne關聯性,對於更多的一對一關係你通常想要的是BelongsTo關聯性。兩者不同在於,BelongsTo要新增外來鍵到來源,hasOne新增外來鍵到目標。(因為兩者的來源和目標是相反的)
Difference between HasOne and BelongsTo
In Sequelize 1:1 relationship can be set using HasOne and BelongsTo. They are suitable for different scenarios. Lets study this difference using an example.
Sequelize中,一對一關係可以使用HasOne和BelongsTo來進行設定。他們對於不同的方案都是合適的。通過使用例子來學習他們之間的不同:
Suppose we have two tables to link Player and Team. Lets define their models.
假設你有兩個表Player和Team,先定義他們的模型:
const Player = this.sequelize.define('player', {/* attributes */}) const Team = this.sequelize.define('team', {/* attributes */});
When we link two models in Sequelize we can refer them as pairs of source and target models. Like this
在Sequelize中,當我們連線兩個模型時,我們可以將其參考為來源和目標模型,就像下面:
Having Player as the source and Team as the target
Player是來源模型,Team是目標模型
Player.belongsTo(Team); //Or Player.hasOne(Team);
Having Team as the source and Player as the target
或者Team是來源模型,Player是目標模型
Team.belongsTo(Player); //Or Team.hasOne(Player);
HasOne and BelongsTo insert the association key in different models from each other. HasOne inserts the association key in target model whereas BelongsTo inserts the association key in the source model.
HasOne和BelongsTo將關聯鍵插入與對方不同的模型。HasOne將關聯鍵插入目標模型,然而BelongsTo插入來源模型
Here is an example demonstrating use cases of BelongsTo and HasOne.舉例說明
const Player = this.sequelize.define('player', {/* attributes */}) const Coach = this.sequelize.define('coach', {/* attributes */}) const Team = this.sequelize.define('team', {/* attributes */});
Suppose our Player
model has information about its team as teamId
column. Information about each Team's Coach
is stored in the Team
model as coachId
column. These both scenarios requires different kind of 1:1 relation because foreign key relation is present on different models each time.
假設Player
模型有著關於他的team的訊息,即teamId
列。關於每一個Team的Coach的訊息儲存在Team模型中,即coachId
。這些場景需要不同型別的一對一關係,因為每一次外來鍵關係會出現在不同的模型中。
When information about association is present in source model we can use belongsTo
. In this case Player
is suitable for belongsTo
because it has teamId
column.
當關於關聯性的訊息出現在來源模型時,我們能使用belongsTo
。在這種情況下,Player很
適合belongsTo
,因為它有著teamId
列:
Player.belongsTo(Team) // `teamId` will be added on Player / Source model
When information about association is present in target model we can use hasOne
. In this case Coach
is suitable for hasOne
because Team
model store information about its Coach
as coachId
field.
當關於關聯性的訊息出現在目標模型時,我們能使用hasOne
。在這種情況下,Coach
很適合hasOne
,因為Team儲存關於Coach的資訊在coachId列中
Coach.hasOne(Team) // `coachId` will be added on Team / Target model
One-To-Many associations (hasMany)
One-To-Many associations are connecting one source with multiple targets. The targets however are again connected to exactly one specific source.
一對多關聯性將連線一個來源與多個目標模型。可是目標只與特定的一個來源連線
const User = sequelize.define('user', {/* ... */}) const Project = sequelize.define('project', {/* ... */}) // OK. Now things get more complicated (not really visible to the user :)). // First let's define a hasMany association Project.hasMany(User, {as: 'Workers'})
This will add the attribute projectId
or project_id
to User. Instances of Project will get the accessors getWorkers
and setWorkers
.
這將會新增屬性projectId
或project_id
到User中。Project例項將會得到訪問器getWorkers
和setWorkers
(因為設定as)
Sometimes you may need to associate records on different columns, you may use sourceKey
option:
有時你可能需要在不同的列中關聯記錄,那你需要sourceKey
選項:
const City = sequelize.define('city', { countryCode: Sequelize.STRING }); const Country = sequelize.define('country', { isoCode: Sequelize.STRING }); // Here we can connect countries and cities base on country code Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'}); City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'});
sourceKey即說明目標模型City的外來鍵對應的是來源模型Country的isoCode列
So far we dealt with a one-way association. But we want more! Let's define it the other way around by creating a many to many association in the next section.
到目前為止,我們解決了一對的關聯關係。但我們需要更多。在下面將通過建立多對多的關聯關係來以另一種方式定義它。
Belongs-To-Many associations
Belongs-To-Many associations are used to connect sources with multiple targets. Furthermore the targets can also have connections to multiple sources.
所屬對多關係用於連線來源和多個目標模型。與一對多不同在與目標能夠連線多個來源
Project.belongsToMany(User, {through: 'UserProject'}); User.belongsToMany(Project, {through: 'UserProject'});
This will create a new model called UserProject with the equivalent foreign keys projectId
and userId
. Whether the attributes are camelcase or not depends on the two models joined by the table (in this case User and Project).
這將會建立一個新的同等帶有外來鍵projectId
和userId
的UserProject模型。這個屬性是否是camelcase拼寫法將取決於通過UserProject這個表連線的兩個模型
Defining through
is required. Sequelize would previously attempt to autogenerate names but that would not always lead to the most logical setups.
通過required進行定義。Sequelize以前企圖自動生成名字,但這樣不能保證總能導致最多的邏輯設定
This will add methods getUsers
, setUsers
, addUser
,addUsers
to Project
, and getProjects
, setProjects
, addProject
, and addProjects
to User
.
這將會新增方法getUsers
, setUsers
, addUser
,addUsers
給Project,並新增getProjects
, setProjects
, addProject
, and addProjects
方法給User
Sometimes you may want to rename your models when using them in associations. Let's define users as workers and projects as tasks by using the alias (as
) option. We will also manually define the foreign keys to use:
有時當你在關聯時使用時,你可能想要重新命名你的模型。讓我們通過使用別名 (as
)選項去將users定義成workers,projects定義成tasks。我們將相互定義外來鍵來使用:
User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' }) Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' })
foreignKey
will allow you to set source model key in the through relation. otherKey
will allow you to set target model key in the through relation.
foreignKey
將允許你設定來源模型鍵到through關係中。otherKey是用於設定目標模型鍵到through關係中的
User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId', otherKey: 'projectId'})
Of course you can also define self references with belongsToMany:
當然,你也可以設定自我引用:
Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' }) // This will create the table PersonChildren which stores the ids of the objects.
If you want additional attributes in your join table, you can define a model for the join table in sequelize, before you define the association, and then tell sequelize that it should use that model for joining, instead of creating a new one:
如果你想要在你的連線表中新增額外屬性的話,你可以在sequelize中為你的連線表定義模型。在你定義關聯之前,告訴sequelize它需要使用這個模型進行連線而不是建立一個新的連線表:
const User = sequelize.define('user', {}) const Project = sequelize.define('project', {}) const UserProjects = sequelize.define('userProjects', { status: DataTypes.STRING }) User.belongsToMany(Project, { through: UserProjects }) Project.belongsToMany(User, { through: UserProjects })
有上面的例子可知,定義了連線表userProjects,並在建立連線belongsToMany時宣告其為through
To add a new project to a user and set its status, you pass extra options.through
to the setter, which contains the attributes for the join table
為了新增新的project到user並設定其狀態,你可以傳遞options.through
給設定者,裡面包含了連線表的屬性
user.addProject(project, { through: { status: 'started' }})
By default the code above will add projectId and userId to the UserProjects table, and remove any previously defined primary key attribute - the table will be uniquely identified by the combination of the keys of the two tables, and there is no reason to have other PK columns. To enforce a primary key on the UserProjects
model you can add it manually.
預設上面的程式碼將會新增projectId 和userId到UserProjects表中,並移除任何以前定義的主鍵屬性-表將通過兩個表的連線唯一標明,這裡沒有理由還有別的PK列。為了加強在UserProjects模型中的主鍵,你可以手動新增它:
const UserProjects = sequelize.define('userProjects', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, status: DataTypes.STRING })
With Belongs-To-Many you can query based on through relation and select specific attributes. For example using findAll
with through
在Belongs-To-Many中你可以基於through關係來查詢和選擇具體的屬性。比如使用帶著throug的findAll:
User.findAll({ include: [{ model: Project, through: { attributes: ['createdAt', 'startedAt', 'finishedAt'], where: {completed: true} } }] });
Belongs-To-Many creates a unique key when primary key is not present on through model. This unique key name can be overridden using uniqueKey option.
在through模型中,當主鍵不存在時,Belongs-To-Many將建立了一個唯一的鍵。使用uniqueKey選項可以複寫這個唯一鍵名
Project.belongsToMany(User, { through: UserProjects, uniqueKey: 'my_custom_unique' })
Scopes作用域
This section concerns association scopes. For a definition of association scopes vs. scopes on associated models, see Scopes.
這部分是關於關聯作用域
Association scopes allow you to place a scope (a set of default attributes for get
and create
) on the association. Scopes can be placed both on the associated model (the target of the association), and on the through table for n:m relations.
關聯作用域允許你在關聯行中定義作用域(對於get
和create的
預設屬性集)。作用域能夠在關聯模型(關聯的目標)和n:m關係的through表中定義
1:m
Assume we have tables Comment, Post, and Image. A comment can be associated to either an image or a post via commentable_id
and commentable
- we say that Post and Image are Commentable
假設我們有表Comment、Post和Image。comment能夠通過commentable_id
和commentable
與Image或Post相關聯- Post和 Image是Commentable
const Comment = this.sequelize.define('comment', { title: Sequelize.STRING, commentable: Sequelize.STRING, commentable_id: Sequelize.INTEGER }); Comment.prototype.getItem = function(options) { return this['get' + this.get('commentable').substr(0, 1).toUpperCase() + this.get('commentable').substr(1)](options); }; Post.hasMany(this.Comment, { foreignKey: 'commentable_id', constraints: false, scope: { commentable: 'post' } }); Comment.belongsTo(this.Post, { foreignKey: 'commentable_id', constraints: false, as: 'post' }); Image.hasMany(this.Comment, { foreignKey: 'commentable_id', constraints: false, scope: { commentable: 'image' } }); Comment.belongsTo(this.Image, { foreignKey: 'commentable_id', constraints: false, as: 'image' });
constraints: false,
disables references constraints - since the commentable_id
column references several tables, we cannot add a REFERENCES
constraint to it. Note that the Image -> Comment and Post -> Comment relations define a scope, commentable: 'image'
and commentable: 'post'
respectively. This scope is automatically applied when using the association functions:
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'
The getItem
utility function on Comment
completes the picture - it simply converts the commentable
string into a call to either getImage
or getPost
, providing an abstraction over whether a comment belongs to a post or an image. You can pass a normal options object as a parameter to getItem(options)
to specify any where conditions or includes.
在
函式完成了圖片-Comment
中的getItem效用只要抽象完成,無論comment屬於post還是image
,它都簡單地將commentable
字串轉成getImag
e或getPost的呼叫。
你可以傳遞一個正常選項物件作為變數給