跳至主要内容
版本:v6 - 稳定版

作用域

作用域用于帮助您重用代码。您可以定义常用的查询,指定选项,例如 whereincludelimit 等。

本指南涉及模型作用域。您可能也对关联作用域指南感兴趣,它们类似但并非同一事物。

定义

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

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

还可以将作用域模型包含在作用域定义中。这允许您避免重复 includeattributeswhere 定义。使用上面的示例,并在包含的 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),除了 whereinclude,它们将被合并。考虑两个作用域

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

注意 limitage 如何被 scope2 覆盖,而 firstName 如何被保留。limitoffsetorderparanoidlockraw 字段被覆盖,而 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 中添加,最好通过示例来理解。

考虑模型 FooBarBazQux,以及如下所示的一对多关联

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 等的选项。