Skip to main content
Version: 6.6

过滤器

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 of EntityManager

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.

每个全局过滤器名称在单个 EntityManager 中必须是唯一的。使用相同的名称再次调用 addFilter() 将替换之前的定义。

¥Each global filter name must be unique within a single EntityManager. Calling addFilter() again with the same name replaces the previous definition.

在 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。对于可空属性,我们使用 LEFT JOIN 结合 WHERE 条件,确保在值存在时应用过滤器(并丢弃该行),而如果值为 NULL,则不会丢弃根实体。

¥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. For nullable properties, we use a LEFT JOIN combined with a WHERE condition that ensures the filter is applied (and the row discarded) when the value is present (while not discarding the root entity if the value is NULL).

这对于通过过滤器实现软删除尤其重要,因为外键可能指向软删除的实体。当发生这种情况时,自动 INNER JOIN 将导致根本不返回这样的记录。你可以通过 autoJoinRefsForFilters ORM 选项禁用此行为。

¥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.

要完全禁用关系过滤器,请在 ORM 配置中使用 filtersOnRelations: false。请注意,如果关系上的过滤器被禁用,则 select-in 加载策略的行为会有所不同,因为将使用单独的查询来加载每个关系,从而有效地在该级别应用过滤器,而不是通过 JOIN 条件。禁用关系上的过滤器也会禁用 autoJoinRefsForFilters 选项,除非显式启用。

¥To disable filters on relations completely, use filtersOnRelations: false in your ORM config. Note that with disabled filters on relations, select-in loading strategy will behave differently, since a separate query will be used to load each relation, effectively applying filters on that level instead of via a JOIN conditions. Disabling filters on relations also disable the autoJoinRefsForFilters option unless enabled explicitly.

你还可以在实体定义级别控制关系过滤器。当你想为关系上使用的过滤器提供默认选项时,这将很有用。这些值将与通过 FindOptionsem.fork() 提供的值合并。

¥You can also control relation filters on the entity definition level. This is useful when you want to provide default options for filters used on a relation. Those values will be merged with the ones provided via FindOptions or em.fork().

// Disable all filters by setting:
@ManyToOne({ filters: false })
book!: Book;

// Disable a specific filter by setting:
@ManyToOne({ filters: { [filterName]: false } })
book!: Book;

// Set the param that will be passed to the filter callback:
@ManyToOne({ filters: { [filterName]: { foo: bar } } })
book!: Book;

严格的关系过滤器

¥Strict relation filters

过滤器也可以标记为 strict,即使过滤掉可空关系,也会丢弃所属实体。这对于其他用例也很有用,例如检查租户。

¥Filters can be also marked as strict, which results in discarding the owning entity even if a nullable relation is filtered out. This is handy for other use cases, like checking for a tenant.

em.addFilter('tenant', { tenant: tenantId }, User, { strict: true });

即使使用严格的过滤器,对于可空关系,仍然会发出 LEFT JOIN 查询,并添加一个 WHERE 查询,如果值已定义且过滤器不允许,则该查询将丢弃拥有实体。

¥A strict filter will still issue a LEFT JOIN on the nullable relation, adding a WHERE query that will discard the owning entity if the value is defined and the filter disallows it.

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: { tenant: 123 } }); // `tenant` filter with `{ tenant: 123 }` parameter
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: { tenant: 123 } }, // `tenant` filter with `{ tenant: 123 }` parameter
});