Smart Nested Populate

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 has one Publisher (M:1), one Author (M:1) and many BookTags (M:N)
  • Publisher has many Tests (M:N)

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

const tags = await orm.em.findAll(BookTag, ['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. Load all BookTags
  2. Load all Books associated with previously loaded BookTags
  3. Load all Publishers associated with previously loaded Books
  4. Load all Tests associated with previously loaded Publishers
  5. Load all Authors associated with previously loaded Books

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

For mongo driver its 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();

Using EntityLoader manually

Under the hood, EntityManager uses EntityLoader to populate other entities. You can use it manually if you already have list of entities (e.g. queried via QueryBuilder):

import { EntityLoader } from 'mikro-orm';
const loader = new EntityLoader(orm.em);
const res = await orm.em.createQueryBuilder(Author).select('*').execute();
const authors = res.map(data => orm.em.merge(Author, data));
await loader.populate(Author, 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

Back to table of contents