过滤器
MikroORM 能够预定义过滤条件并将这些过滤器附加到给定的实体。然后,应用可以在运行时决定是否应启用某些过滤器以及它们的参数值应该是什么。过滤器可以像数据库视图一样使用,但它们在应用内部参数化。
¥MikroORM has the ability to pre-define filter criteria and attach those filters to given entities. The application can then decide at runtime whether certain filters should be enabled and what their parameter values should be. Filters can be used like database views, but they are parameterized inside the application.
过滤器可以在实体级别定义,通过 EM(全局过滤器)动态定义或在 ORM 配置中定义。
¥Filter can be defined at the entity level, dynamically via EM (global filters) or in the ORM configuration.
过滤器应用于 EntityManager
的那些方法:find()
、findOne()
、findAndCount()
、findOneOrFail()
、count()
、nativeUpdate()
和 nativeDelete()
。
¥Filters are applied to those methods of EntityManager
: find()
, findOne()
, findAndCount()
, findOneOrFail()
, count()
, nativeUpdate()
and nativeDelete()
.
cond
参数可以是回调,可能是异步的。¥The
cond
parameter can be a callback, possibly asynchronous.
@Entity()
@Filter({ name: 'expensive', cond: { price: { $gt: 1000 } } })
@Filter({ name: 'long', cond: { 'length(text)': { $gt: 10000 } } })
@Filter({ name: 'hasAuthor', cond: { author: { $ne: null } }, default: true })
@Filter({ name: 'writtenBy', cond: args => ({ author: { name: args.name } }) })
export class Book {
...
}
const books1 = await orm.em.find(Book, {}, {
filters: ['long', 'expensive'],
});
const books2 = await orm.em.find(Book, {}, {
filters: { hasAuthor: false, long: true, writtenBy: { name: 'God' } },
});
属性过滤器
¥Properties of filter
你可以使用三个参数:
¥There are three parameters you can use:
-
name
- 可用于启用查询过滤器。也可用于传递参数¥
name
- can be used to enable a filter on the query. Can also be used to pass a parameter -
cond
- 是启用过滤器时应添加到查询中的条件。这可以是回调,甚至是异步的¥
cond
- is the condition that should be added to the query when the filter is enabled. This can be a callback, even async -
default
- 表示过滤器是否在查询中默认启用¥
default
- indicates if the filter is enabled by default on the query
参数
¥Parameters
你可以将 cond
动态定义为回调。此回调也可以是异步的。它将获得三个参数:
¥You can define the cond
dynamically as a callback. This callback can be also asynchronous. It will get three arguments:
-
args
- 用户提供的参数字典¥
args
- dictionary of parameters provided by user -
type
- 要过滤的操作类型,'read'
、'update'
、'delete'
之一¥
type
- type of operation that is being filtered, one of'read'
,'update'
,'delete'
-
em
-EntityManager
的当前实例¥
em
- current instance ofEntityManager
import type { EntityManager } from '@mikro-orm/mysql';
@Entity()
@Filter({ name: 'writtenBy', cond: async (args, type, em: EntityManager) => {
if (type === 'update') {
return {}; // do not apply when updating
}
return {
author: { name: args.name },
publishedAt: { $lte: raw('now()') },
};
} })
export class Book {
...
}
const books = await orm.em.find(Book, {}, {
filters: { writtenBy: { name: 'God' } },
});
无过滤器参数
¥Filters without parameters
如果我们想要一个不需要参数的过滤条件,但我们想要访问 type
参数,我们将需要明确设置 args: false
,否则会由于缺少参数而引发错误:
¥If we want to have a filter condition that do not need arguments, but we want to access the type
parameter, we will need to explicitly set args: false
, otherwise error will be raised due to missing parameters:
@Filter({
name: 'withoutParams',
cond(_, type) {
return { ... };
},
args: false,
default: true,
})
全局过滤器
¥Global filters
我们还可以通过 EntityManager
API 动态注册过滤器。我们将此类过滤器称为全局过滤器。它们默认启用(除非通过 addFilter()
方法中的最后一个参数禁用),并应用于所有实体。你可以将全局过滤器限制为仅指定的实体。
¥We can also register filters dynamically via EntityManager
API. We call such filters global. They are enabled by default (unless disabled via last parameter in addFilter()
method), and applied to all entities. You can limit the global filter to only specified entities.
在 EM 上设置的过滤器以及过滤器参数将被复制到其所有分支。
¥Filters as well as filter params set on the EM will be copied to all its forks.
// bound to entity, enabled by default
em.addFilter('writtenBy', args => ({ author: args.id }), Book);
// global, enabled by default, for all entities
em.addFilter('tenant', args => { ... });
// global, enabled by default, for only specified entities
em.addFilter('tenant', args => { ... }, [Author, Book]);
...
// set params (probably in some middleware)
em.setFilterParams('tenant', { tenantId: 123 });
em.setFilterParams('writtenBy', { id: 321 });
全局过滤器也可以通过 ORM 配置注册:
¥Global filters can be also registered via ORM configuration:
MikroORM.init({
filters: { tenant: { cond: args => ({ tenant: args.tenant }), entity: ['Author', 'User'] } },
...
})
使用过滤器
¥Using filters
我们可以通过 FindOptions
中的 filter
参数控制将应用哪些过滤器。我们可以提供要启用的过滤器名称数组或选项对象,我们也可以在其中禁用过滤器(默认情况下已启用),或将一些参数传递给需要它们的那些。
¥We can control what filters will be applied via filter
parameter in FindOptions
. We can either provide an array of names of filters you want to enable, or options object, where we can also disable a filter (that was enabled by default), or pass some parameters to those that are expecting them.
通过传递
filters: false
,我们还可以禁用给定调用的所有过滤器。¥By passing
filters: false
we can also disable all the filters for given call.
em.find(Book, {}); // same as `{ tenantId: 123 }`
em.find(Book, {}, { filters: ['writtenBy'] }); // same as `{ author: 321, tenantId: 123 }`
em.find(Book, {}, { filters: { tenant: false } }); // disabled tenant filter, so truly `{}`
em.find(Book, {}, { filters: false }); // disabled all filters, so truly `{}`
过滤器和关系
¥Filters and relationships
自 v6 起,过滤器也应用于关系,作为 JOIN ON
条件的一部分。如果 M:1 或 1:1 关系目标上存在过滤器,则会自动连接此类实体,并且当外键定义为 NOT NULL
时,将导致 INNER JOIN
而不是 LEFT JOIN
。这对于通过过滤器实现软删除尤其重要,因为外键可能指向软删除的实体。当发生这种情况时,自动 INNER JOIN
将导致根本不返回这样的记录。你可以通过 autoJoinRefsForFilters
ORM 选项禁用此行为。
¥Since v6, filters are applied to the relations too, as part of JOIN ON
condition. If a filter exists on a M:1 or 1:1 relation target, such an entity will be automatically joined, and when the foreign key is defined as NOT NULL
, it will result in an INNER JOIN
rather than LEFT JOIN
. This is especially important for implementing soft deletes via filters, as the foreign key might point to a soft-deleted entity. When this happens, the automatic INNER JOIN
will result in such a record not being returned at all. You can disable this behavior via autoJoinRefsForFilters
ORM option.
QueryBuilder
过滤器通常仅适用于通过 EntityManager
完成的查询,要在 QueryBuilder
中使用它们,你可以使用 qb.applyFilters()
方法。它接受一个参数,相当于 FindOptions.filters
。
¥Filters are normally applied only to the queries done via EntityManager
, to use them in your QueryBuilder
, you can use the qb.applyFilters()
method. It takes a single argument, which is equivalent of FindOptions.filters
.
const qb = em.createQueryBuilder(Author);
await qb.applyFilters({ tenant: 123 });
const authors = await qb.getResult();
过滤器命名
¥Naming of filters
当通过 FindOptions
切换过滤器时,我们不关心实体名称。这意味着当你在不同的实体上定义了多个过滤器,但名称相同时,它们将通过 FindOptions
中的单个切换进行控制。
¥When toggling filters via FindOptions
, we do not care about the entity name. This means that when you have multiple filters defined on different entities, but with the same name, they will be controlled via single toggle in the FindOptions
.
@Entity()
@Filter({ name: 'tenant', cond: args => ({ tenant: args.tenant }) })
export class Author {
...
}
@Entity()
@Filter({ name: 'tenant', cond: args => ({ tenant: args.tenant }) })
export class Book {
...
}
// this will apply the tenant filter to both Author and Book entities (with SELECT_IN loading strategy)
const authors = await orm.em.find(Author, {}, {
populate: ['books'],
filters: { tenant: 123 },
});