使用MikroORM并获得此错误:
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
它对应的代码如下:
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);
});
我不确定在哪里需要使用.fork()方法
不要在不了解验证的情况下禁用验证
我简直不敢相信我在这里看到的回复。对于任何来到这里的人,请不要禁用验证(通过MIKRO_ORM_ALLOW_GLOBAL_CONTEXT
env var或通过allowGlobalContext
配置)。只有在非常特殊的情况下才可以禁用验证,主要是在单元测试中。
如果你不认识我,我是MikroORM背后的人,也是添加此验证的人-原因很充分,所以请不要只是禁用它,这意味着你有一个问题需要解决,而不是说你应该在配置中添加一行来关闭它
这个验证被添加到MikroORM v5中(所以不是typeorm,请不要混淆这两个),它的意思正是它所说的——你正在尝试使用全局上下文,而你应该使用特定于请求的上下文。请参阅文档,了解为什么需要此处的请求上下文:https://mikro-orm.io/docs/identity-map#why-是所需的请求上下文。一般来说,使用单个(全局)上下文将导致API响应不稳定,基本上是一个巨大的内存泄漏。
所以现在我们应该明白为什么验证存在,为什么我们不应该禁用它。接下来如何正确绕过它。
正如其他人所提到的(也正如验证错误消息所提到的),我们可以创建fork并使用它:
const fork = orm.em.fork();
const res = await fork.find(...);
但这将是相当乏味的,在现实世界的应用程序中,我们通常有可以自动为我们做这件事的中间件。这就是RequestContext
助手发挥作用的地方。它在后台使用AsyncLocalStorage
,并在ORM中得到本机支持。
以下文本主要是对MikroORM文档的提取。
RequestContext
助手是如何工作的
在内部,所有使用Identity Map的EntityManager
方法(例如em.find()
或em.getReference()
)都会首先调用em.getContext()
来访问上下文分支。此方法将首先检查我们是否在RequestContext
处理程序内部运行,并从中选择EntityManager
分支。
// 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, {});
然后,RequestContext.getEntityManager()
方法检查我们用于在RequestContext.create()
方法中创建新EM分叉的AsyncLocalStorage
静态实例。
Node.js核心的AsyncLocalStorage
类就是这里的魔术师。它允许我们在整个异步调用中跟踪上下文。它允许我们通过全局EntityManager
实例将EntityManager
分叉创建(通常在中间件中,如前一节所示)与其使用解耦。
通过中间件使用RequestContext
助手
如果我们使用像inversify
或nestjs
框架中的依赖注入容器,可能很难实现这一点,因为我们通常想通过DI容器访问我们的存储库,但它总是为我们提供相同的实例,而不是为每个请求提供新的实例。
为了解决这个问题,我们可以使用RequestContext
助手,它将在后台使用node
的AsyncLocalStorage
来隔离请求上下文。MikroORM将始终使用请求特定(分叉)实体管理器(如果可用),所以我们所需要做的就是创建新的请求上下文,最好是作为中间件:
app.use((req, res, next) => {
RequestContext.create(orm.em, next);
});
我们应该在请求处理程序之前以及在使用ORM的任何自定义中间件之前将该中间件注册为最后一个中间件。当我们在请求处理中间件(如queryParser
或bodyParser
)之前注册它时,可能会出现问题,所以一定要在它们之后注册上下文。
稍后,我们可以通过RequestContext.getEntityManager()
访问请求范围的EntityManager
。这种方法是在引擎盖下自动使用的,所以我们不应该需要它
RequestContext.getEntityManager()
将返回undefined
。
没有助手的简单用法
现在,OP中的示例代码是非常基本的,因为分叉似乎是最简单的事情,因为它非常简单,你没有任何web服务器,所以没有中间件:
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
但我们也可以在这里使用RequestContext
来演示它的工作原理:
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" });
});
@UseRequestContext()
装饰器
中间件仅针对常规HTTP请求处理程序执行,如果我们需要请求范围之外的方法?其中一个例子是队列处理程序或计划任务(例如CRON作业)。
我们可以使用@UseRequestContext()
装饰器。它需要我们首先注入MikroORM
实例到当前上下文,然后将用于创建上下文在后台,decorator将为我们的方法,并在上下文中执行它。
这个decorator将在RequestContext.createAsync()
调用中封装底层方法。对这种方法的每次调用都将创建新的上下文(新的EntityManager
fork),该上下文将在内部使用。
@UseRequestContext()
应仅用于顶级方法。它不应该嵌套-用它修饰的方法不应该调用另一个也用它修饰过的方法
@Injectable()
export class MyService {
constructor(private readonly orm: MikroORM) { }
@UseRequestContext()
async doSomething() {
// this will be executed in a separate context
}
}
或者,我们可以提供一个回调,返回MikroORM
实例。
import { DI } from '..';
export class MyService {
@UseRequestContext(() => DI.orm)
async doSomething() {
// this will be executed in a separate context
}
}
注意,这不是一个通用的解决方法,你不应该盲目地把装饰器放在任何地方-事实恰恰相反,它应该只用于非常特定的用例,比如CRON作业,在其他可以使用中间件的环境中,这根本不需要。
我今天在将mikrorm设置从v4升级到v5时遇到了类似的问题。在做了一些RnD之后,我发现以下更改帮助我解决了上述错误。
- 在传递给
MikroORM.init
调用的配置对象中,传递以下属性
allowGlobalContext: true
- 不要直接使用
em
到create
数据库条目。请使用以下代码
const post = orm.em.fork({}).create(Post, {
title: "my first post",
});
以上更改将帮助您修复错误。
我也是MikroORM的新手。所以,我不知道为什么会出现这个错误。但我没有受过教育的猜测是,他们正在限制对全局EntityManagerem
实例的任何更改的访问。
经过一些挖掘,我找到了这个解决方案:
-
纱线安装dotenv
-
在项目的根目录中创建.env文件
-
在.env文件中粘贴以下内容:
MIKRO_ORM_ALLOW_GLOBAL_CONTEXT = true
问题解决了!