作用域
作用域用于帮助您重用代码。您可以定义常用的查询,指定选项,例如 where
、include
、limit
等。
本指南涉及模型作用域。您可能也对关联作用域指南感兴趣,它们类似但并非同一事物。
定义
作用域在模型定义中定义,可以是查找器对象,也可以是返回查找器对象的函数 - 除了默认作用域,它只能是对象。
class Project extends Model {}
Project.init(
{
// Attributes
},
{
defaultScope: {
where: {
active: true,
},
},
scopes: {
deleted: {
where: {
deleted: true,
},
},
activeUsers: {
include: [{ model: User, where: { active: true } }],
},
random() {
return {
where: {
someNumber: Math.random(),
},
};
},
accessLevel(value) {
return {
where: {
accessLevel: {
[Op.gte]: value,
},
},
};
},
sequelize,
modelName: 'project',
},
},
);
您还可以通过调用YourModel.addScope
在模型定义后添加作用域。这对于包含 includes 的作用域特别有用,因为 include 中的模型可能在定义另一个模型时尚未定义。
默认作用域始终应用。这意味着,使用上面的模型定义,Project.findAll()
将创建以下查询
SELECT * FROM projects WHERE active = true
可以通过调用 .unscoped()
、.scope(null)
或调用另一个作用域来删除默认作用域。
await Project.scope('deleted').findAll(); // Removes the default scope
SELECT * FROM projects WHERE deleted = true
还可以将作用域模型包含在作用域定义中。这允许您避免重复 include
、attributes
或 where
定义。使用上面的示例,并在包含的 User 模型上调用 active
作用域(而不是在该 include 对象中直接指定条件)
// The `activeUsers` scope defined in the example above could also have been defined this way:
Project.addScope('activeUsers', {
include: [{ model: User.scope('active') }],
});
用法
通过在模型定义上调用 .scope
并传递一个或多个作用域的名称来应用作用域。.scope
返回一个具有所有常规方法的功能齐全的模型实例:.findAll
、.update
、.count
、.destroy
等。您可以保存此模型实例并在以后重用它。
const DeletedProjects = Project.scope('deleted');
await DeletedProjects.findAll();
// The above is equivalent to:
await Project.findAll({
where: {
deleted: true,
},
});
作用域适用于 .find
、.findAll
、.count
、.update
、.increment
和 .destroy
。
作为函数的作用域可以通过两种方式调用。如果作用域不带任何参数,则可以像往常一样调用它。如果作用域带参数,则传递一个对象。
await Project.scope('random', { method: ['accessLevel', 19] }).findAll();
生成的 SQL
SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19
合并
可以通过将作用域数组传递给 .scope
或将作用域作为连续参数传递来同时应用多个作用域。
// These two are equivalent
await Project.scope('deleted', 'activeUsers').findAll();
await Project.scope(['deleted', 'activeUsers']).findAll();
生成的 SQL
SELECT * FROM projects
INNER JOIN users ON projects.userId = users.id
WHERE projects.deleted = true
AND users.active = true
如果要与默认作用域一起应用另一个作用域,请将键 defaultScope
传递给 .scope
。
await Project.scope('defaultScope', 'deleted').findAll();
生成的 SQL
SELECT * FROM projects WHERE active = true AND deleted = true
调用多个作用域时,后续作用域的键将覆盖前面的键(类似于Object.assign),除了 where
和 include
,它们将被合并。考虑两个作用域
YourModel.addScope('scope1', {
where: {
firstName: 'bob',
age: {
[Op.gt]: 20,
},
},
limit: 2,
});
YourModel.addScope('scope2', {
where: {
age: {
[Op.lt]: 30,
},
},
limit: 10,
});
使用 .scope('scope1', 'scope2')
将产生以下 WHERE 子句
WHERE firstName = 'bob' AND age < 30 LIMIT 10
注意 limit
和 age
如何被 scope2
覆盖,而 firstName
如何被保留。limit
、offset
、order
、paranoid
、lock
和 raw
字段被覆盖,而 where
默认情况下是浅合并(意味着相同的键将被覆盖)。如果标志 whereMergeStrategy
设置为 and
(在模型或 Sequelize 实例上),where
字段将使用 and
运算符合并。
例如,如果 YourModel
初始化如下
YourModel.init(
{
/* attributes */
},
{
// ... other init options
whereMergeStrategy: 'and',
},
);
使用 .scope('scope1', 'scope2')
将产生以下 WHERE 子句
WHERE firstName = 'bob' AND age > 20 AND age < 30 LIMIT 10
请注意,多个应用作用域的 attributes
键以以下方式合并:始终保留 attributes.exclude
。这允许合并多个作用域,并且在最终作用域中永远不会泄漏敏感字段。
相同的合并逻辑适用于将查找对象直接传递给作用域模型上的 findAll
(以及类似的查找器)
Project.scope('deleted').findAll({
where: {
firstName: 'john',
},
});
生成的 where 子句
WHERE deleted = true AND firstName = 'john'
这里 deleted
作用域与查找器合并。如果我们将 where: { firstName: 'john', deleted: false }
传递给查找器,则 deleted
作用域将被覆盖。
合并 includes
Includes 基于被包含的模型递归合并。这是一个非常强大的合并,在 v5 中添加,最好通过示例来理解。
考虑模型 Foo
、Bar
、Baz
和 Qux
,以及如下所示的一对多关联
const Foo = sequelize.define('Foo', { name: Sequelize.STRING });
const Bar = sequelize.define('Bar', { name: Sequelize.STRING });
const Baz = sequelize.define('Baz', { name: Sequelize.STRING });
const Qux = sequelize.define('Qux', { name: Sequelize.STRING });
Foo.hasMany(Bar, { foreignKey: 'fooId' });
Bar.hasMany(Baz, { foreignKey: 'barId' });
Baz.hasMany(Qux, { foreignKey: 'bazId' });
现在,考虑在 Foo 上定义的以下四个作用域
Foo.addScope('includeEverything', {
include: {
model: Bar,
include: [
{
model: Baz,
include: Qux,
},
],
},
});
Foo.addScope('limitedBars', {
include: [
{
model: Bar,
limit: 2,
},
],
});
Foo.addScope('limitedBazs', {
include: [
{
model: Bar,
include: [
{
model: Baz,
limit: 2,
},
],
},
],
});
Foo.addScope('excludeBazName', {
include: [
{
model: Bar,
include: [
{
model: Baz,
attributes: {
exclude: ['name'],
},
},
],
},
],
});
这四个作用域可以轻松地深度合并,例如通过调用 Foo.scope('includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName').findAll()
,这将完全等同于调用以下内容
await Foo.findAll({
include: {
model: Bar,
limit: 2,
include: [
{
model: Baz,
limit: 2,
attributes: {
exclude: ['name'],
},
include: Qux,
},
],
},
});
// The above is equivalent to:
await Foo.scope(['includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName']).findAll();
观察这四个作用域是如何合并成一个的。作用域的 includes 基于被包含的模型合并。如果一个作用域包含模型 A,另一个包含模型 B,则合并的结果将包含模型 A 和 B。另一方面,如果两个作用域都包含相同的模型 A,但具有不同的选项(例如嵌套 includes 或其他属性),则这些选项将递归合并,如上所示。
上面说明的合并策略无论作用域应用的顺序如何,都以完全相同的方式工作。顺序只会影响两个不同的作用域设置了某个特定选项的情况 - 这在上面的示例中并非如此,因为每个作用域都执行不同的操作。
此合并策略也以完全相同的方式适用于传递给 .findAll
、.findOne
等的选项。