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,外键名命名为companyIdcamelCase

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_uuidsnake_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,它指向的是目标模型Companyname

 

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.

假设你有两个表PlayerTeam,先定义他们的模型:

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. 

这将会添加属性projectIdproject_idUser中。Project实例将会得到访问器getWorkerssetWorkers(因为设置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).

这将会创建一个新的同等带有外键projectIduserId的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.

关联作用域允许你在关联行中定义作用域(对于getcreate的缺省属性集)。作用域能够在关联模型(关联的目标)和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_idcommentable
ImagePost相关联- 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字符串转成getImage或getPost的调用。

你可以传递一个正常选项对象作为变量给getItem(options)去指明该条件是否满足

 

n:m

Continuing with the idea of a polymorphic model, consider a tag table - an item can have multiple tags, and a tag can be related to several items.

接着多态模型的概念,考虑一个标签表-即一个有多个标签的项目,并且每一个标签能够与多个项目关联

For brevity, the example only shows a Post model, but in reality Tag would be related to several other models.

简单来说,下面例子只展示了Post模型,但是在现实中,Tag将关联多个其他模型

const ItemTag = sequelize.define('item_tag', {
  id : {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  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
  }
});
const 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',
  constraints: false
});

Notice that the scoped column (taggable) is now on the through model (ItemTag).

注意作用域列(taggable)现在在through模型(ItemTag)

We could also define a more restrictive association, for example, to get all pending tags for a post by applying a scope of both the through model (ItemTag) and the target model (Tag):

我们也能定义更具限制性的关联,比如,通过在through模型(ItemTag)和目标模型(Tag)中供应一个作用域来得到post中所有正在运行的标签

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');

constraints: false disables references constraints on the taggable_id column. Because the column is polymorphic, we cannot say that it REFERENCES a specific table.

constraints: false阻止了taggable_id列中的引用限制。因为列是多台的,我们不能说它引用了一个具体的表

 

 

 

Naming strategy命名策略

By default sequelize will use the model name (the name passed to sequelize.define) to figure out the name of the model when used in associations. For example, a model named user will add the functions get/set/add User to instances of the associated model, and a property named .user in eager loading, while a model named User will add the same functions, but a property named .User (notice the upper case U) in eager loading.

默认情况下,sequelize将使用模型名(传递给sequelize.define的名字)去查明使用在关联中的模型的名字。比如,一个模型名为user将会添加get/set/add User函数到关联模型实例中,在预先加载中将有属性命名为.user。当模型命名为User还是会添加相同的函数,但是属性将命名为.User

As we've already seen, you can alias models in associations using as. In single associations (has one and belongs to), the alias should be singular, while for many associations (has many) it should be plural. Sequelize then uses the inflection library to convert the alias to its singular form. However, this might not always work for irregular or non-english words. In this case, you can provide both the plural and the singular form of the alias:

如我们所见,你可以在关联中使用as为模型起别名。在单关联中(has one and belongs to),别名将为单数,然而在多关联中,它将为复数。 Sequelize 将使用 inflection库去转换别名成单数形式。可是,这可能不总是对不规则会非英文的单词有用。在这种情况下,你可以为别名提供单数和复数两种形式。

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

If you know that a model will always use the same alias in associations, you can provide it when creating the model

如果你知道关联总是使用相同的别名,你可以在创建模型时提供该别名:

const Project = sequelize.define('project', attributes, {
  name: {
    singular: 'task',
    plural: 'tasks',
  }
})

User.belongsToMany(Project);

This will add the functions add/set/get Tasks to user instances.

这将会添加add/set/get Tasks函数到user实例

Remember, that using as to change the name of the association will also change the name of the foreign key. When using as, it is safest to also specify the foreign key.

记住,使用as去改变关联的名字将改变外键的名字。当使用as时,指明外键会更安全

Invoice.belongsTo(Subscription)
Subscription.hasMany(Invoice)

Without as, this adds subscriptionId as expected. However, if you were to say Invoice.belongsTo(Subscription, { as: 'TheSubscription' }), you will have both subscriptionId and theSubscriptionId, because sequelize is not smart enough to figure that the calls are two sides of the same relation. 'foreignKey' fixes this problem;

没有as,将会添加subscriptionId。可是如果你设置Invoice.belongsTo(Subscription, { as: 'TheSubscription' }),你将会拥有 subscriptionIdtheSubscriptionId,因为sequelize不够聪明去查看调用是同一个反应的两种方式。使用 'foreignKey' 将解决这个问题:

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

 

 

 

 

Associating objects关联对象

Because Sequelize is doing a lot of magic, you have to call Sequelize.sync after setting the associations! Doing so will allow you the following:

因为Sequelize做了很多魔法,所以你在设置关联之后一定要调用Sequelize.sync。这样才运行你做下面的操作:

Project.hasMany(Task)
Task.belongsTo(Project)

Project.create()...
Task.create()...
Task.create()...

// save them... and then:
project.setTasks([task1, task2]).then(() => {
  // saved!
})

// ok, now they are saved... how do I get them later on?
project.getTasks().then(associatedTasks => {
  // associatedTasks is an array of tasks
})

// You can also pass filters to the getter method.
// They are equal to the options you can pass to a usual finder method.
project.getTasks({ where: 'id > 10' }).then(tasks => {
  // tasks with an id greater than 10 :)
})

// You can also only retrieve certain fields of a associated object.
project.getTasks({attributes: ['title']}).then(tasks => {
  // retrieve tasks with the attributes "title" and "id"
})

To remove created associations you can just call the set method without a specific id:

为了移除创建的关联,你只要调用没有具体id的set方法即可:

// remove the association with task1
project.setTasks([task2]).then(associatedTasks => {
  // you will get task2 only
})

// remove 'em all
project.setTasks([]).then(associatedTasks => {
  // you will get an empty array
})

// or remove 'em more directly
project.removeTask(task1).then(() => {
  // it's gone
})

// and add 'em again
project.addTask(task1).then(function() {
  // it's back again
})

You can of course also do it vice versa:

反之亦然:

// project is associated with task1 and task2
task2.setProject(null).then(function() {
  // and it's gone
})

For hasOne/belongsTo it's basically the same:

对于hasOne/belongsTo也是相同的

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

Adding associations to a relation with a custom join table can be done in two ways (continuing with the associations defined in the previous chapter):

添加关联到自定义的连接表中有两种方法(接着之前定义关联的章节)

// Either by adding a property with the name of the join table model to the object, before creating the association
project.UserProjects = {
  status: 'active'
}
u.addProject(project)

// Or by providing a second options.through argument when adding the association, containing the data that should go in the join table
u.addProject(project, { through: { status: 'active' }})


// When associating multiple objects, you can combine the two options above. In this case the second argument
// will be treated as a defaults object, that will be used if no data is provided
project1.UserProjects = {
    status: 'inactive'
}

u.setProjects([project1, project2], { through: { status: 'active' }})
// The code above will record inactive for project one, and active for project two in the join table

When getting data on an association that has a custom join table, the data from the join table will be returned as a DAO instance:

当在有着自定义连接表的关联中得到数据时,来自连接表的数据将会以DAO实例形式返回

u.getProjects().then(projects => {
  const project = projects[0]

  if (project.UserProjects.status === 'active') {
    // .. do magic

    // since this is a real DAO instance, you can save it directly after you are done doing magic
    return project.UserProjects.save()
  }
})

If you only need some of the attributes from the join table, you can provide an array with the attributes you want:

如果你只需要连接表中的属性,你可以提供你想要的属性数组:

// This will select only name from the Projects table, and only status from the UserProjects table
user.getProjects({ attributes: ['name'], joinTableAttributes: ['status']})

 

 

 

Check associations

You can also check if an object is already associated with another one (N:M only). Here is how you'd do it:

如果对象已经与另一个对象关联,你就可以进行查看:

// check if an object is one of associated ones:
Project.create({ /* */ }).then(project => {
  return User.create({ /* */ }).then(user => {
    return project.hasUser(user).then(result => {
      // result would be false
      return project.addUser(user).then(() => {
        return project.hasUser(user).then(result => {
          // result would be true
        })
      })
    })
  })
})

// check if all associated objects are as expected:
// let's assume we have already a project and two users
project.setUsers([user1, user2]).then(() => {
  return project.hasUsers([user1]);
}).then(result => {
  // result would be true
  return project.hasUsers([user1, user2]);
}).then(result => {
  // result would be true
})

 

 

 

Foreign Keys

When you create associations between your models in sequelize, foreign key references with constraints will automatically be created. The setup below:

在sequelize中,当你在你的模型间创建关联时,带有限制的外键引用将自动创建:

const Task = this.sequelize.define('task', { title: Sequelize.STRING })
const User = this.sequelize.define('user', { username: Sequelize.STRING })

User.hasMany(Task)
Task.belongsTo(User)

Will generate the following SQL:

将会生成如下的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
);

The relation between task and user injects the user_id foreign key on tasks, and marks it as a reference to the User table. By default user_id will be set to NULL if the referenced user is deleted, and updated if the id of the user id updated. These options can be overridden by passing onUpdate and onDelete options to the association calls. The validation options are RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL.

task和user间的关系在tasks中插入了user_id外键,并标记其为User表的引用。默认当引用user被删除时user_id将被设置成NULL;当user的id更新时,它也随之更新。这个选项可以通过传递 onUpdate onDelete选项给关联调用来进行复写。验证选项为RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL

For 1:1 and 1:m associations the default option is SET NULL for deletion, and CASCADE for updates. For n:m, the default for both is CASCADE. This means, that if you delete or update a row from one side of an n:m association, all the rows in the join table referencing that row will also be deleted or updated.

对于一对一和一对多关联,默认删除选项为SET NULL,更新选项为CASCADE。对于n:m,默认其都是CASCADE。这意味着。如果你从n:m关联的某一边删除或更新一行,在连接表中引用了该行的所有行都将被删除或更改

Adding constraints between tables means that tables must be created in the database in a certain order, when using sequelize.sync. If Task has a reference to User, the User table must be created before the Task table can be created. This can sometimes lead to circular references, where sequelize cannot find an order in which to sync. Imagine a scenario of documents and versions. A document can have multiple versions, and for convenience, a document has a reference to its current version.

当使用sequelize.sync在表中添加限制意味着在数据库中的表将以某中顺序被创建。如果Task引用User,那么User表一定要在Task表之前被创建。有时可能会用循环引用,sequelize将不能找到同步的顺序。想象文件和版本的场景。一个文件有着多个版本,为了方便,一个文件会引用其当前版本:

const Document = this.sequelize.define('document', {
  author: Sequelize.STRING
})
const Version = this.sequelize.define('version', {
  timestamp: Sequelize.DATE
})

Document.hasMany(Version) // This adds document_id to version
Document.belongsTo(Version, { as: 'Current', foreignKey: 'current_version_id'}) // This adds current_version_id to document

However, the code above will result in the following error: Cyclic dependency found. 'Document' is dependent of itself. Dependency Chain: Document -> Version => Document. In order to alleviate that, we can pass constraints: false to one of the associations:

但是上面的代码将导致下面的错误:

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})

Which will allow us to sync the tables correctly:

这样将允许我们正确同步表

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
);
 

Enforcing a foreign key reference without constraints不需限制地加强外键引用

Sometimes you may want to reference another table, without adding any constraints, or associations. In that case you can manually add the reference attributes to your schema definition, and mark the relations between them.

有时,你可能想要引用别的表,并不用添加任何限制或关联。在这种情况下,你可以手动地添加引用属性到你的方案定义中,并在他们两中标记关系:

// Series has a trainer_id=Trainer.id foreign reference key after we call Trainer.hasMany(series)
const 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: "trainer",
      key: "id"
    }
  }
})

const Trainer = sequelize.define('trainer', {
  first_name: DataTypes.STRING,
  last_name:  DataTypes.STRING
});

// Video has a series_id=Series.id foreign reference key after we call Series.hasOne(Video)...
const Video = sequelize.define('video', {
  title:        DataTypes.STRING,
  sequence:     DataTypes.INTEGER,
  description:  DataTypes.TEXT,

  // set relationship (hasOne) with `Series`
  series_id: {
    type: DataTypes.INTEGER,
    references: {
      model: Series, // Can be both a string representing the table name, or a reference to the model
      key:   "id"
    }
  }
});

Series.hasOne(Video);
Trainer.hasMany(Series);

 

 

 

 

Creating with associations

An instance can be created with nested association in one step, provided all elements are new.

实例能够在一步中嵌套关联来创建,只要所有的元素是新的

Creating elements of a "BelongsTo", "Has Many" or "HasOne" association

Consider the following models:

const Product = this.sequelize.define('product', {
  title: Sequelize.STRING
});
const User = this.sequelize.define('user', {
  first_name: Sequelize.STRING,
  last_name: Sequelize.STRING
});
const Address = this.sequelize.define('address', {
  type: Sequelize.STRING,
  line_1: Sequelize.STRING,
  line_2: Sequelize.STRING,
  city: Sequelize.STRING,
  state: Sequelize.STRING,
  zip: Sequelize.STRING,
});

Product.User = Product.belongsTo(User);
User.Addresses = User.hasMany(Address);
// Also works for `hasOne`

A new Product, User, and one or more Address can be created in one step in the following way:

一个新的Product, User和一个或多个 Address可以在一步中被创建:

return Product.create({
  title: 'Chair',
  user: {
    first_name: 'Mick',
    last_name: 'Broadstone',
    addresses: [{
      type: 'home',
      line_1: '100 Main St.',
      city: 'Austin',
      state: 'TX',
      zip: '78704'
    }]
  }
}, {
  include: [{
    association: Product.User,
    include: [ User.Addresses ]
  }]
});

Here, our user model is called user, with a lowercase u - This means that the property in the object should also be user. If the name given to sequelize.define was User, the key in the object should also be User. Likewise for addresses, except it's pluralized being a hasMany association.

这里,user模型命名为user,小写u-这意味着对象中的属性是user的。如果给sequelize.define的名字是User,那么对象中的键就该是User的。对address也是同样的,除非他的多元化是hasMany一个hasMany关联

 

Creating elements of a "BelongsTo" association with an alias

The previous example can be extended to support an association alias.

以前的例子可以扩展去支持关联别名

const Creator = Product.belongsTo(User, {as: 'creator'});

return Product.create({
  title: 'Chair',
  creator: {
    first_name: 'Matt',
    last_name: 'Hansen'
  }
}, {
  include: [ Creator ]
});

 

Creating elements of a "HasMany" or "BelongsToMany" association

Let's introduce the ability to associate a product with many tags. Setting up the models could look like:

介绍关联有着许多标签的产品的方法:

const Tag = this.sequelize.define('tag', {
  name: Sequelize.STRING
});

Product.hasMany(Tag);
// Also works for `belongsToMany`.

Now we can create a product with multiple tags in the following way:

现在你可以以下面的方式创建带着多个标签的产品:

Product.create({
  id: 1,
  title: 'Chair',
  tags: [
    { name: 'Alpha'},
    { name: 'Beta'}
  ]
}, {
  include: [ Tag ]
})

And, we can modify this example to support an alias as well:

我们也可以修改它去支持别名

const 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'
  }]
})

 

 

 
posted @ 2018-12-05 16:25  慢行厚积  阅读(896)  评论(0编辑  收藏  举报