验证错误:不允许将全局实体管理器实例方法用于上下文特定的操作



使用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_CONTEXTenv 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助手

如果我们使用像inversifynestjs框架中的依赖注入容器,可能很难实现这一点,因为我们通常想通过DI容器访问我们的存储库,但它总是为我们提供相同的实例,而不是为每个请求提供新的实例。

为了解决这个问题,我们可以使用RequestContext助手,它将在后台使用nodeAsyncLocalStorage来隔离请求上下文。MikroORM将始终使用请求特定(分叉)实体管理器(如果可用),所以我们所需要做的就是创建新的请求上下文,最好是作为中间件:

app.use((req, res, next) => {
RequestContext.create(orm.em, next);
});

我们应该在请求处理程序之前以及在使用ORM的任何自定义中间件之前将该中间件注册为最后一个中间件。当我们在请求处理中间件(如queryParserbodyParser)之前注册它时,可能会出现问题,所以一定要在它们之后注册上下文。

稍后,我们可以通过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()调用中封装底层方法。对这种方法的每次调用都将创建新的上下文(新的EntityManagerfork),该上下文将在内部使用。

@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之后,我发现以下更改帮助我解决了上述错误。

  1. 在传递给MikroORM.init调用的配置对象中,传递以下属性

allowGlobalContext: true

  1. 不要直接使用emcreate数据库条目。请使用以下代码
const post = orm.em.fork({}).create(Post, {
title: "my first post",
});

以上更改将帮助您修复错误。

我也是MikroORM的新手。所以,我不知道为什么会出现这个错误。但我没有受过教育的猜测是,他们正在限制对全局EntityManagerem实例的任何更改的访问。

经过一些挖掘,我找到了这个解决方案:

  1. 纱线安装dotenv

  2. 在项目的根目录中创建.env文件

  3. 在.env文件中粘贴以下内容:

MIKRO_ORM_ALLOW_GLOBAL_CONTEXT = true

问题解决了!

最新更新