Using MikroORM and getting this error:
ValidationError: Using global EntityManager instance methods for context specific actions is disallowed.
If you need to work with the global instance's identity map, use `allowGlobalContext` configuration option or `fork()` instead
The code that it corresponds to is below:
import { MikroORM } from "@mikro-orm/core";
import { __prod__ } from "./constants";
import { Post } from "./entities/Post";
import mikroConfig from "./mikro-orm.config";
const main = async () => {
const orm = await MikroORM.init(mikroConfig);
const post = orm.em.create(Post, {
title: "my first post",
});
await orm.em.persistAndFlush(post);
await orm.em.nativeInsert(Post, { title: "my first post 2" });
};
main().catch((error) => {
console.error(error);
});
I am unsure where I need to use the .fork() method
2
3 Answers
Don’t disable validations without understanding them!
I can’t believe what I see in the replies here. For anybody coming here, please don’t disable the validation (either via MIKRO_ORM_ALLOW_GLOBAL_CONTEXT
env var or via allowGlobalContext
configuration). Disabling the validation is fine only under very specific circumstances, mainly in unit tests.
In case you don’t know me, I am the one behind MikroORM, as well as the one who added this validation – for a very good reason, so please don’t just disable that, it means you have a problem to solve, not that you should add one line to your configuration to shut it up.
This validation was added to MikroORM v5 (so not typeorm, please dont confuse those two), and it means exactly what it says – you are trying to work with the global context, while you should be working with request specific one. Consult the docs for why you need request context here: https://mikro-orm.io/docs/identity-map#why-is-request-context-needed. In general using single (global) context will result in instable API response and basically a one huge memory leak.
So now we should understand why the validation is there and why we should not disable it. Next how to get around it properly.
As others mentined (and as the validation error message mentioned too), we can create fork and use that instead:
const fork = orm.em.fork();
const res = await fork.find(...);
But that would be quite tedious, in real world apps, we usually have middlewares we can use to do this for us automatically. That is where the RequestContext
helper comes into play. It uses the AsyncLocalStorage
under the hood and is natively supported in the ORM.
Following text is mostly an extraction of the MikroORM docs.
How does RequestContext
helper work?
Internally all EntityManager
methods that work with the Identity Map (e.g. em.find()
or em.getReference()
) first call em.getContext()
to access the contextual fork. This method will first check if we are running inside RequestContext
handler and prefer the EntityManager
fork from it.
// we call em.find() on the global EM instance
const res = await orm.em.find(Book, {});
// but under the hood this resolves to
const res = await orm.em.getContext().find(Book, {});
// which then resolves to
const res = await RequestContext.getEntityManager().find(Book, {});
The RequestContext.getEntityManager()
method then checks AsyncLocalStorage
static instance we use for creating new EM forks in the RequestContext.create()
method.
The AsyncLocalStorage
class from Node.js core is the magician here. It allows us to track the context throughout the async calls. It allows us to decouple the EntityManager
fork creation (usually in a middleware as shown in previous section) from its usage through the global EntityManager
instance.
Using RequestContext
helper via middleware
If we use dependency injection container like inversify
or the one in nestjs
framework, it can be hard to achieve this, because we usually want to access our repositories via DI container, but it will always provide we with the same instance, rather than new one for each request.
To solve this, we can use RequestContext
helper, that will use node
‘s AsyncLocalStorage
in the background to isolate the request context. MikroORM will always use request specific (forked) entity manager if available, so all we need to do is to create new request context preferably as a middleware:
app.use((req, res, next) => {
RequestContext.create(orm.em, next);
});
We should register this middleware as the last one just before request handlers and before any of our custom middleware that is using the ORM. There might be issues when we register it before request processing middleware like queryParser
or bodyParser
, so definitely register the context after them.
Later on we can then access the request scoped EntityManager
via RequestContext.getEntityManager()
. This method is used under the hood automatically, so we should not need it.
RequestContext.getEntityManager()
will returnundefined
if the context was not started yet.
Simple usage without the helper
Now your example code from the OP is very basic, for that forking seems like the easiest thing to do, as its very bare bones, you dont have any web server there, so no middlewares:
const orm = await MikroORM.init(mikroConfig);
const emFork = orm.em.fork(); // <-- create the fork
const post = emFork.create(Post, { // <-- use the fork instead of global `orm.em`
title: "my first post",
});
await emFork.persistAndFlush(post); // <-- use the fork instead of global
await orm.em.nativeInsert(Post, { title: "my first post 2" }); // <-- this line could work with the global EM too, why? because `nativeInsert` is not touching the identity map = the context
But we can use the RequestContext
here too, to demonstrate how it works:
const orm = await MikroORM.init(mikroConfig);
// run things in the `RequestContext` handler
await RequestContext.createAsync(orm.em, async () => {
// inside this handler the `orm.em` will actually use the contextual fork, created via `RequestContext.createAsync()`
const post = orm.em.create(Post, {
title: "my first post",
});
await orm.em.persistAndFlush(post);
await orm.em.nativeInsert(Post, { title: "my first post 2" });
});
The @UseRequestContext()
decorator
The
@UseRequestContext()
has been renamed to@CreateRequestContext()
in v6, which also adds a new@EnsureRequestContext()
decorator – the difference being the latter only ensures there is a non-global context, while the former always creates a new context.
Middlewares are executed only for regular HTTP request handlers, what if we need
a request scoped method outside that? One example of that is queue handlers or
scheduled tasks (e.g. CRON jobs).
We can use the @UseRequestContext()
decorator. It requires us to first inject the
MikroORM
instance to current context, it will be then used to create the context
for us. Under the hood, the decorator will register new request context for our
method and execute it inside the context.
This decorator will wrap the underlying method in RequestContext.createAsync()
call. Every call to such method will create new context (new EntityManager
fork) which will be used inside.
@UseRequestContext()
should be used only on the top level methods. It should not be nested – a method decorated with it should not call another method that is also decorated with it.
@Injectable()
export class MyService {
constructor(private readonly orm: MikroORM) { }
@UseRequestContext()
async doSomething() {
// this will be executed in a separate context
}
}
Alternatively we can provide a callback that will return the MikroORM
instance.
import { DI } from '..';
export class MyService {
@UseRequestContext(() => DI.orm)
async doSomething() {
// this will be executed in a separate context
}
}
Note that this is not a universal workaround, you should not blindly put the decorator everywhere – its actually the opposite, it should be used only for a very specific use case like CRON jobs, in other contexts where you can use middlewares this is not needed at all.
11
-
Following the discussion from github.com/mikro-orm/mikro-orm/discussions/2531. Thank you very much for the explanation.
– AsheshJul 15, 2022 at 23:35
-
Thanks for the explanation @martin-adámek , I have a question, I have a cron job that calls a function on another service, if I use UserRequestContext decorator, does that mean I need to pass the injected orm to the function I am calling ?
– Mohamed MirghaniSep 27, 2022 at 7:19
-
The decorator will behave the same way as if you would have a middleware, you can use your services from the DI container as usual, and the contextual fork is used automatically. So nope, no need to pass any instance, you can work with the global ones.
– Martin AdámekSep 27, 2022 at 10:46
-
this should be the accepted answer, very informative
– elpmidOct 2, 2022 at 13:10
-
1
you can inject it anywhere, including any "typical nestjs service". nowadays the decorator also accepts EM, not just the ORM helper object
– Martin Adámek1 hour ago
I faced a similar issue today when I upgraded the mikrorm setup from v4 to v5. After doing some RnD, I found the following changes helped me solve the mentioned error.
- In the config object which is passed to the
MikroORM.init
call, pass the following property
allowGlobalContext: true
- Don’t directly use
em
tocreate
database entry. Instead use the following code
const post = orm.em.fork({}).create(Post, {
title: "my first post",
});
The above changes should help you fix the error.
I am also very new to MikroORM. so, I am not sure why this error appears. But my uneducated guess is, they are restricting access to any changes to the global EntityManager em
instance.
2
-
5
This is wrong, dont disable validations without understanding them.
– Martin AdámekJun 29, 2022 at 10:20
-
-1 overall it is not recommended to disable this validation. this shouldn't be the accepted answer. it's easy to have it enabled. no one should have to take this measure
– arthur simasSep 19 at 6:10
Thanks for asking this question. I also got here by watching Ben Awad's Fullstack React GraphQL TypeScript tutorial on YouTube.
Apr 27, 2022 at 12:58
Please consider marking my answer as accepted one, or at least read it carefuly, because disabling the validation is simply not the right thing to do (I would say its the opposite).
Jun 29, 2022 at 10:42