The documentation stresses that I should use a new EntityManager
for each request and there's even a middleware for automatically generating it or alternatively I can use em.fork()
. So far so good.
The EntityRepository
is a great way to make the code readable. I could not find anything in the documentation about how they relate to EntityManager
instances. The express-ts-example-app
example uses single instances of repositories and the RequestContext
middleware. This suggests that there is some under-the-hood magic that finds the correct EntityManager
instances at least with the RequestContext
. Is it really so?
Also, if I fork the EM manually can it still find the right one? Consider the following example:
(async () => {
DI.orm = await MikroORM.init();
DI.em = DI.orm.em;
DI.companyRepository = DI.orm.em.getRepository(Company);
DI.invoiceRepository = DI.orm.em.getRepository(Invoice);
...
fetchInvoices(em.fork());
}
async function fetchInvoices(em) {
for (const company of await DI.companyRepository.findAll()) {
fetchInvoicesOfACompany(company, em.fork())
}
}
async function fetchInvoicesOfACompany(company, em) {
let done = false;
while (!done) {
const invoice = await getNextInvoice(company.taxnumber, company.lastInvoice);
if ( invoice ) {
DI.invoiceRepository.persist(invoice);
company.lastInvoice = invoice.id;
em.flush();
} else {
done = true;
}
}
}
Does the DI.invoiceRepository.persist()
in fetchInvoicesOfACompany()
use the right EM instance? If not, what should I do?
Also, if I'm not mistaken, the em.flush()
in fetchInvoicesOfACompany()
does not update company, since that belongs to another EM - how should I handle situations like this?
First of all, repository is just a thin layer on top of EM (an extension point if you want), that bares the entity name so you don't have to pass it to the first parameter of EM method (e.g. em.find(Ent, ...)
vs repo.find(...)
.
Then the contexts - you need a dedicated context for each request, so it has its own identity map. If you use RequestContext
helper, the context is created and saved via domain
API. Thanks to this, all the methods that are executed inside the domain handler will use the right instance automatically - this happens in the em.getContext()
method, that first checks the RequestContext
helper.
https://mikro-orm.io/docs/identity-map/#requestcontext-helper-for-di-containers
Check the tests for better understanding of how it works:
https://github.com/mikro-orm/mikro-orm/blob/master/tests/RequestContext.test.ts
So if you use repositories, with RequestContext
helper it will work just fine as the singleton repository instance will use the singleton EM instance that will then use the right request based instance via em.getContext()
where approapriate.
But if you use manual forking instead, you are responsible use the right repository instance - each EM fork will have its own one. So in this case you can't use a singleton, you need to do forkedEm.getRepository(Ent)
.
Btw alternatively you can also use AsyncLocalStorage
which is faster (and not deprecated), if you are on node 12+. The RequestContext
helper implementation will use ALS in v5, as node 12+ will be requried.
https://mikro-orm.io/docs/async-local-storage
Another thing you could do is to use the RequestContext
helper manually instead of via middlewares - something like the following:
(async () => {
DI.orm = await MikroORM.init();
DI.em = DI.orm.em;
DI.companyRepository = DI.orm.em.getRepository(Company);
DI.invoiceRepository = DI.orm.em.getRepository(Invoice);
...
await RequestContext.createAsync(DI.em, async () => {
await fetchInvoices();
})
});
async function fetchInvoices() {
for (const company of await DI.companyRepository.findAll()) {
await fetchInvoicesOfACompany(company)
}
}
async function fetchInvoicesOfACompany(company) {
let done = false;
while (!done) {
const invoice = await getNextInvoice(company.taxnumber, company.lastInvoice);
if (invoice) {
company.lastInvoice = invoice; // passing entity instance, no need to persist as `company` is managed entity and this change will be cascaded
await DI.em.flush();
} else {
done = true;
}
}
}