Working with Entity Manager
Persist and Flush
There are 2 methods we should first describe to understand how persisting works in MikroORM: em.persist()
and em.flush()
.
em.persist(entity, flush?: boolean)
is used to mark new entities for future persisting. It will make the entity managed by given EntityManager
and once flush
will be called, it will be written to the database. Second boolean parameter can be used to invoke flush
immediately. Its default value is configurable via autoFlush
option.
To understand flush
, lets first define what managed entity is: An entity is managed if it’s fetched from the database (via em.find()
, em.findOne()
or via other managed entity) or registered as new through em.persist()
.
em.flush()
will go through all managed entities, compute appropriate change sets and perform according database queries. As an entity loaded from database becomes managed automatically, you do not have to call persist on those, and flush is enough to update them.
const book = await orm.em.findOne(Book, 1);
book.title = 'How to persist things...';
// no need to persist `book` as its already managed by the EM
await orm.em.flush();
Persisting and Cascading
To save entity state to database, you need to persist it. Persist determines whether to use insert
or update
and computes appropriate change-set. Entity references that are not persisted yet (does not have identifier) will be cascade persisted automatically.
// use constructors in your entities for required parameters
const author = new Author('Jon Snow', 'snow@wall.st');
author.born = new Date();
const publisher = new Publisher('7K publisher');
const book1 = new Book('My Life on The Wall, part 1', author);
book1.publisher = publisher;
const book2 = new Book('My Life on The Wall, part 2', author);
book2.publisher = publisher;
const book3 = new Book('My Life on The Wall, part 3', author);
book3.publisher = publisher;
// just persist books, author and publisher will be automatically cascade persisted
await orm.em.persistAndFlush([book1, book2, book3]);
// or one by one
orm.em.persistLater(book1);
orm.em.persistLater(book2);
orm.em.persistLater(book3);
await orm.em.flush(); // flush everything to database at once
Auto-flushing
Since MikroORM v3, default value for autoFlush
is false
. That means you need to call em.flush()
yourself to persist changes into database. You can still change this via ORM's options to ease the transition but generally it is not recommended as it can cause unwanted small transactions being created around each persist
.
orm.em.persist(new Entity()); // no auto-flushing by default
await orm.em.flush();
await orm.em.persist(new Entity(), true); // you can still use second parameter to auto-flush
Fetching Entities with EntityManager
To fetch entities from database you can use find()
and findOne()
of EntityManager
:
Example:
const author = await orm.em.findOne(Author, '...id...');
const books = await orm.em.find(Book, {});
for (const author of authors) {
console.log(author.name); // Jon Snow
for (const book of author.books) {
console.log(book.title); // initialized
console.log(book.author.isInitialized()); // true
console.log(book.author.id);
console.log(book.author.name); // Jon Snow
console.log(book.publisher); // just reference
console.log(book.publisher.isInitialized()); // false
console.log(book.publisher.id);
console.log(book.publisher.name); // undefined
}
}
To populate entity relations, you can use populate
parameter.
const books = await orm.em.find(Book, { foo: 1 }, ['author.friends']);
You can also use em.populate()
helper to populate relations (or to ensure they are fully populated) on already loaded entities. This is also handy when loading entities via QueryBuilder
:
const authors = await orm.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
Conditions Object (FilterQuery<T>
)
Querying entities via conditions object (where
in em.find(Entity, where: FilterQuery<T>)
) supports many different ways:
// search by entity properties
const users = await orm.em.find(User, { firstName: 'John' });
// for searching by reference you can use primary key directly
const id = 1;
const users = await orm.em.find(User, { organization: id });
// or pass unpopulated reference (including `Reference` wrapper)
const ref = await orm.em.getReference(Organization, id);
const users = await orm.em.find(User, { organization: ref });
// fully populated entities as also supported
const ent = await orm.em.findOne(Organization, id);
const users = await orm.em.find(User, { organization: ent });
// complex queries with operators
const users = await orm.em.find(User, { $and: [{ id: { $nin: [3, 4] } }, { id: { $gt: 2 } }] });
// you can also search for array of primary keys directly
const users = await orm.em.find(User, [1, 2, 3, 4, 5]);
// and in findOne all of this works, plus you can search by single primary key
const user1 = await orm.em.findOne(User, 1);
As you can see in the fifth example, one can also use operators like $and
, $or
, $gte
, $gt
, $lte
, $lt
, $in
, $nin
, $eq
, $ne
, $like
, $re
. More about that can be found in Query Conditions section.
Mitigating Type instantiation is excessively deep and possibly infinite.ts(2589)
error
Sometimes you might be facing TypeScript errors caused by too complex query for it to properly infer all types. Usually it can be solved by providing the type argument explicitly.
You can also opt in to use repository instead, as there the type inference should not be problematic.
As a last resort, you can always type cast the query to
any
.
const books = await orm.em.find<Book>(Book, { ... your complex query ... });
// or
const books = await orm.em.getRepository(Book).find({ ... your complex query ... });
// or
const books = await orm.em.find<any>(Book, { ... your complex query ... }) as Book[];
Another problem you might be facing is RangeError: Maximum call stack size exceeded
error thrown during TypeScript compilation (usually from file node_modules/typescript/lib/typescript.js
). The solution to this is the same, just provide the type argument explicitly.