Skip to main content
Version: 6.4

填充关系

MikroORM 能够加载大型嵌套结构,同时保持良好的性能,每个数据库表仅查询一次。假设你有这个嵌套结构:

¥MikroORM is capable of loading large nested structures while maintaining good performance, querying each database table only once. Imagine you have this nested structure:

  • Book 有一个 Publisher (M:1)、一个 Author (M:1) 和许多 BookTag (M:N)

    ¥Book has one Publisher (M:1), one Author (M:1) and many BookTags (M:N)

  • Publisher 有许多 Test (M:N)

    ¥Publisher has many Tests (M:N)

当你在查询所有 BookTag 时使用嵌套填充时,后台会发生以下情况:

¥When you use nested populate while querying all BookTags, this is what happens in the background:

const tags = await em.find(BookTag, {}, {
populate: ['books.publisher.tests', 'books.author'],
});
console.log(tags[0].books[0].publisher.tests[0].name); // prints name of nested test
console.log(tags[0].books[0].author.name); // prints name of nested author
  1. 加载所有 BookTag

    ¥Load all BookTags

  2. 加载与之前加载的 BookTag 相关联的所有 Book

    ¥Load all Books associated with previously loaded BookTags

  3. 加载与之前加载的 Book 相关联的所有 Publisher

    ¥Load all Publishers associated with previously loaded Books

  4. 加载与之前加载的 Publisher 相关联的所有 Test

    ¥Load all Tests associated with previously loaded Publishers

  5. 加载与之前加载的 Book 相关联的所有 Author

    ¥Load all Authors associated with previously loaded Books

对于具有枢轴表的 SQL 驱动程序,这意味着:

¥For SQL drivers with pivot tables this means:

SELECT `e0`.* FROM `book_tag` AS `e0`;

SELECT `e0`.*, `e1`.`book_id`, `e1`.`book_tag_id`
FROM `book` AS `e0` LEFT JOIN `book_to_book_tag` AS `e1` ON `e0`.`id` = `e1`.`book_id`
WHERE `e1`.`book_tag_id` IN (?, ?, ?, ?, ?)
ORDER BY `e1`.`id` ASC;

SELECT `e0`.* FROM `publisher` AS `e0` WHERE `e0`.`id` IN (?, ?, ?);

SELECT `e0`.*, `e1`.`test_id`, `e1`.`publisher_id`
FROM `test` AS `e0` LEFT JOIN `publisher_to_test` AS `e1` ON `e0`.`id` = `e1`.`test_id`
WHERE `e1`.`publisher_id` IN (?, ?, ?)
ORDER BY `e1`.`id` ASC;

SELECT `e0`.* FROM `author` AS `e0` WHERE `e0`.`id` IN (?);

对于 mongo 驱动程序,它甚至更简单,因为不涉及数据透视表:

¥For mongo driver it's even simpler as no pivot tables are involved:

db.getCollection("book-tag").find({}).toArray();
db.getCollection("book").find({"tags":{"$in":[...]}}).toArray();
db.getCollection("publisher").find({"_id":{"$in":[...]}}).toArray();
db.getCollection("test").find({"_id":{"$in":[...]}}).toArray();
db.getCollection("author").find({"_id":{"$in":[...]}}).toArray();

填充所有关系

¥Populating all relations

你还可以通过传递 populate: ['*'] 来填充所有关系。结果也将严格输入(Loaded 类型尊重星号提示)。

¥You can also populate all relationships by passing populate: ['*']. The result will be also strictly typed (the Loaded type respects the star hint).

const tags = await em.find(BookTag, {}, {
populate: ['*'],
});

这将始终使用 select-in 策略来处理可能的循环。

¥This will always use select-in strategy to deal with possible cycles.

从过滤器推断填充提示

¥Inferring populate hint from filter

如果你想要自动选择过滤查询中的所有关系,请使用 populate: ['$infer']:

¥If you want to automatically select all the relations that are part of your filter query, use populate: ['$infer']:

// this will populate all the books and their authors, all via a single query
const tags = await em.find(BookTag, {
books: { author: { name: '...' } },
}, {
populate: ['$infer'],
});

这将始终使用连接策略,因为我们已经将关系连接起来,因为它们在过滤器中。此功能在 MongoDB 驱动程序中不可用,因为不支持连接。

¥This will always use joined strategy as we already have the relations joined because they are in the filter. This feature is not available in MongoDB driver as there is no join support.

对填充的实体进行过滤

¥Filter on populated entities

填充请求可能不明确。例如,假设有一个名为 'One'Book,带有标签 'Fiction''Hard Cover'

¥The request to populate can be ambiguous. For example, let's say as a hypothetical that there's a Book called 'One' with tags 'Fiction' and 'Hard Cover'.

然后你运行以下命令:

¥Then you run the following:

const books = await em.find(Book, { tags: { name: 'Fiction' } }, {
populate: ['tags'],
});

你正在请求带有 'Fiction' 标签的书籍,然后要求在每本书上填充标签。你的意思是你想填充与过滤器匹配的每本书上的所有标签吗?如果是这样,你会期望图书 'One' 会同时填充 'Fiction''Hard Cover'。或者你的意思是我们只应填充与外部过滤器匹配的标签?如果是这样,你会期望图书 'One' 在填充的集合中只有 'Fiction',因为外部过滤器指定了这一点。

¥You're requesting books that have the tag of 'Fiction' then asking to populate the tags on each book. Did you mean that you want to populate all tags on each book that matches the filter? If so, you'd expect that book 'One' would have both 'Fiction' and 'Hard Cover' populated. Or did you mean that we should only populate the tags that match the outer filter? If so you'd expect that book 'One' would only have 'Fiction' in the populated collection because the outer filter specified that.

这两种行为在不同情况下都很有用,因此 MikroORM 提供了一个允许你控制此行为的选项,称为 populateWhere。有两个选项,INFERALL。默认值为 ALL,它将确保在填充时获取集合的所有可能成员(例如上面的第一个解释)。

¥Both behaviors are useful in different cases, so MikroORM provides an option that allows you to control this called populateWhere. There are two options, INFER and ALL. The default is ALL which will ensure that all possible members of the collection are fetched in the populate (e.g. the first interpretation above).

你可以全局指定:

¥You can specify this globally:

const orm = await MikroORM.init({
// We want our populate fetches to respect the outer filter passed in a where condition.
populateWhere: PopulateHint.INFER,
});

或者你可以根据查询逐个覆盖它:

¥Or you can override this on a query by query basis:

const books = await em.find(Book, { tags: { name: 'Fiction' } }, {
populate: ['tags'],
populateWhere: PopulateHint.INFER,
});

在这种情况下,使用 PopulateHint.INFER 会指示 MikroORM 按照上面的第二种解释来解释 find。

¥Using PopulateHint.INFER in this case instructs MikroORM to interpret the find as per the second interpretation above.

特定查询上提供的值将覆盖全局指定的任何默认值。

¥A value provided on a specific query overrides whatever default is specified globally.

加载策略

¥Loading strategies

MikroORM 根据填充提示获取数据的方式也是可配置的。默认情况下,MikroORM 使用 "选择" 策略,该策略为填充的每个级别运行一个单独的查询。如果你使用 SQL 数据库,你还可以要求 MikroORM 对填充中涉及的所有表使用连接并将其作为单个查询运行。这再次可以全局或按查询进行配置。

¥The way that MikroORM fetches the data based on populate hint is also configurable. By default, MikroORM uses a "select in" strategy which runs one separate query for each level of a populate. If you're using an SQL database you can also ask MikroORM to use a join for all tables involved in the populate and run it as a single query. This is again configurable globally or per query.

有关更多信息,请参阅 加载策略部分

¥For more information see the Loading Strategies section.

填充已加载的实体

¥Populating already loaded entities

要填充现有实体,你可以使用 em.populate()

¥To populate existing entities, you can use em.populate().

const authors = await em.createQueryBuilder(Author).select('*').getResult();
await em.populate(authors, ['books.tags']);

// now your Author entities will have `books` collections populated,
// as well as they will have their `tags` collections populated.
console.log(authors[0].books[0].tags[0]); // initialized BookTag