Skip to main content
Version: v6 - stable

范围

范围用于帮助你重用代码。你可以定义常用的查询,指定 whereincludelimit 等选项。

¥Scopes are used to help you reuse code. You can define commonly used queries, specifying options such as where, include, limit, etc.

本指南涉及模型范围。你可能还对 关联范围指南 感兴趣,它们相似但不相同。

¥This guide concerns model scopes. You might also be interested in the guide for association scopes, which are similar but not the same thing.

定义

¥Definition

范围在模型定义中定义,可以是查找器对象,也可以是返回查找器对象的函数 - 除了默认作用域,它只能是一个对象:

¥Scopes are defined in the model definition and can be finder objects, or functions returning finder objects - except for the default scope, which can only be an object:

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 添加范围。这对于包含包含的范围特别有用,其中包含中的模型在定义其他模型时可能尚未定义。

¥You can also add scopes after a model has been defined by calling YourModel.addScope. This is especially useful for scopes with includes, where the model in the include might not be defined at the time the other model is being defined.

始终应用默认范围。这意味着,根据上面的模型定义,Project.findAll() 将创建以下查询:

¥The default scope is always applied. This means, that with the model definition above, Project.findAll() will create the following query:

SELECT * FROM projects WHERE active = true

可以通过调用 .unscoped().scope(null) 或调用另一个作用域来删除默认作用域:

¥The default scope can be removed by calling .unscoped(), .scope(null), or by invoking another scope:

await Project.scope('deleted').findAll(); // Removes the default scope
SELECT * FROM projects WHERE deleted = true

还可以在范围定义中包含范围模型。这使你可以避免重复 includeattributeswhere 定义。使用上面的示例,并在包含的 User 模型上调用 active 范围(而不是直接在该包含对象中指定条件):

¥It is also possible to include scoped models in a scope definition. This allows you to avoid duplicating include, attributes or where definitions. Using the above example, and invoking the active scope on the included User model (rather than specifying the condition directly in that include object):

// The `activeUsers` scope defined in the example above could also have been defined this way:
Project.addScope('activeUsers', {
include: [
{ model: User.scope('active') }
]
});

用法

¥Usage

通过在模型定义上调用 .scope 并传递一个或多个范围的名称来应用范围。.scope 返回一个具有所有常规方法的功能齐全的模型实例:.findAll.update.count.destroy 等。你可以保存此模型实例并在以后重复使用:

¥Scopes are applied by calling .scope on the model definition, passing the name of one or more scopes. .scope returns a fully functional model instance with all the regular methods: .findAll, .update, .count, .destroy etc. You can save this model instance and reuse it later:

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

¥Scopes apply to .find, .findAll, .count, .update, .increment and .destroy.

作为函数的作用域可以通过两种方式调用。如果作用域不带任何参数,则可以正常调用。如果作用域接受参数,则传递一个对象:

¥Scopes which are functions can be invoked in two ways. If the scope does not take any arguments it can be invoked as normally. If the scope takes arguments, pass an object:

await Project.scope('random', { method: ['accessLevel', 19] }).findAll();

生成的 SQL:

¥Generated SQL:

SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19

合并

¥Merging

通过将范围数组传递给 .scope 或将范围作为连续参数传递,可以同时应用多个范围。

¥Several scopes can be applied simultaneously by passing an array of scopes to .scope, or by passing the scopes as consecutive arguments.

// These two are equivalent
await Project.scope('deleted', 'activeUsers').findAll();
await Project.scope(['deleted', 'activeUsers']).findAll();

生成的 SQL:

¥Generated SQL:

SELECT * FROM projects
INNER JOIN users ON projects.userId = users.id
WHERE projects.deleted = true
AND users.active = true

如果你想在默认范围之外应用另一个范围,请将键 defaultScope 传递到 .scope

¥If you want to apply another scope alongside the default scope, pass the key defaultScope to .scope:

await Project.scope('defaultScope', 'deleted').findAll();

生成的 SQL:

¥Generated SQL:

SELECT * FROM projects WHERE active = true AND deleted = true

当调用多个作用域时,后续作用域中的键将覆盖之前的键(类似于 Object.assign),但 whereinclude 除外,它们将被合并。考虑两个范围:

¥When invoking several scopes, keys from subsequent scopes will overwrite previous ones (similarly to Object.assign), except for where and include, which will be merged. Consider two scopes:

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 子句:

¥Using .scope('scope1', 'scope2') will yield the following WHERE clause:

WHERE firstName = 'bob' AND age < 30 LIMIT 10

请注意 limitage 如何被 scope2 覆盖,而 firstName 被保留。limitoffsetorderparanoidlockraw 字段将被覆盖,而 where 默认情况下会进行浅层合并(意味着相同的键将被覆盖)。如果标志 whereMergeStrategy 设置为 and(在模型或后续实例上),则将使用 and 运算符合并 where 字段。

¥Note how limit and age are overwritten by scope2, while firstName is preserved. The limit, offset, order, paranoid, lock and raw fields are overwritten, while where is by default shallowly merged (meaning that identical keys will be overwritten). If the flag whereMergeStrategy is set to and (on the model or on the sequelize instance), where fields will be merged using the and operator.

例如,如果 YourModel 被初始化为:

¥For instance, if YourModel was initialized as such:

YourModel.init({ /* attributes */ }, {
// ... other init options
whereMergeStrategy: 'and',
});

使用 .scope('scope1', 'scope2') 将产生以下 WHERE 子句:

¥Using .scope('scope1', 'scope2') will yield the following WHERE clause:

WHERE firstName = 'bob' AND age > 20 AND age < 30 LIMIT 10

请注意,多个应用范围的 attributes 键以始终保留 attributes.exclude 的方式合并。这允许合并多个范围并且永远不会泄漏最终范围中的敏感字段。

¥Note that attributes keys of multiple applied scopes are merged in such a way that attributes.exclude are always preserved. This allows merging several scopes and never leaking sensitive fields in final scope.

当将查找对象直接传递到作用域模型上的 findAll (和类似的查找器)时,适用相同的合并逻辑:

¥The same merge logic applies when passing a find object directly to findAll (and similar finders) on a scoped model:

Project.scope('deleted').findAll({
where: {
firstName: 'john'
}
})

生成的 where 子句:

¥Generated where clause:

WHERE deleted = true AND firstName = 'john'

这里 deleted 瞄准镜与取景器合并。如果我们将 where: { firstName: 'john', deleted: false } 传递给查找器,deleted 范围将被覆盖。

¥Here the deleted scope is merged with the finder. If we were to pass where: { firstName: 'john', deleted: false } to the finder, the deleted scope would be overwritten.

合并包括

¥Merging includes

包含根据包含的模型递归合并。这是一个非常强大的合并,在 v5 中添加,通过示例可以更好地理解。

¥Includes are merged recursively based on the models being included. This is a very powerful merge, added on v5, and is better understood with an example.

考虑模型 FooBarBazQux,其一对多关联如下:

¥Consider the models Foo, Bar, Baz and Qux, with One-to-Many associations as follows:

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 上定义的以下四个范围:

¥Now, consider the following four scopes defined on 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(),这完全相当于调用以下内容:

¥These four scopes can be deeply merged easily, for example by calling Foo.scope('includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName').findAll(), which would be entirely equivalent to calling the following:

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

观察四个范围如何合并为一个。范围的包含内容根据包含的模型进行合并。如果一个范围包含模型 A,另一个范围包含模型 B,则合并结果将同时包含模型 A 和 B。另一方面,如果两个作用域都包含相同的模型 A,但具有不同的选项(例如嵌套包含或其他属性),则它们将递归合并,如上所示。

¥Observe how the four scopes were merged into one. The includes of scopes are merged based on the model being included. If one scope includes model A and another includes model B, the merged result will include both models A and B. On the other hand, if both scopes include the same model A, but with different options (such as nested includes or other attributes), those will be merged recursively, as shown above.

无论应用于范围的顺序如何,上面所示的合并都以完全相同的方式工作。仅当某个选项由两个不同的范围设置时,顺序才会产生影响 - 上面示例的情况并非如此,因为每个作用域执行不同的操作。

¥The merge illustrated above works in the exact same way regardless of the order applied to the scopes. The order would only make a difference if a certain option was set by two different scopes - which is not the case of the above example, since each scope does a different thing.

此合并策略也以完全相同的方式工作,将选项传递给 .findAll.findOne 等。

¥This merge strategy also works in the exact same way with options passed to .findAll, .findOne and the like.