如何正确使用 EntityRepository 实例?

How to use properly EntityRepository instances?

文档强调我应该为每个请求使用一个新的 EntityManager,甚至还有一个中间件可以自动生成它,或者我可以使用 em.fork()。到目前为止一切顺利。

EntityRepository 是使代码可读的好方法。我在文档中找不到任何关于它们如何与 EntityManager 实例相关的信息。 express-ts-example-app 示例使用存储库的单个实例和 RequestContext 中间件。这表明至少在 RequestContext 中有一些隐藏的魔法可以找到正确的 EntityManager 实例。真的是这样吗?

还有,如果我手动fork EM,它还能找到合适的吗?考虑以下示例:

(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;
    }
  }
}

fetchInvoicesOfACompany()中的DI.invoiceRepository.persist()是否使用了正确的EM实例?如果没有,我该怎么办?

此外,如果我没记错的话,fetchInvoicesOfACompany() 中的 em.flush() 不会更新公司,因为它属于另一个 EM - 我应该如何处理这种情况?

首先,存储库只是 EM 之上的一个薄层(如果需要,可以是扩展点),它包含实体名称,因此您不必将其传递给 EM 方法的第一个参数(例如 em.find(Ent, ...)repo.find(...).

然后是上下文 - 每个请求都需要一个专用的上下文,因此它有自己的身份映射。如果您使用 RequestContext 帮助程序,上下文将通过 domain API 创建和保存。由于这一点,在域处理程序中执行的所有方法都将自动使用正确的实例 - 这发生在 em.getContext() 方法中,它首先检查 RequestContext 助手。

https://mikro-orm.io/docs/identity-map/#requestcontext-helper-for-di-containers

检查测试以更好地了解其工作原理:

https://github.com/mikro-orm/mikro-orm/blob/master/tests/RequestContext.test.ts

因此,如果您使用带有 RequestContext 助手的存储库,它将正常工作,因为单例存储库实例将使用单例 EM 实例,然后通过 em.getContext() 使用正确的基于请求的实例,其中合适的。

但是如果您改为使用手动分叉,则您有责任使用正确的存储库实例 - 每个 EM 分叉都有自己的一个。所以在这种情况下你不能使用单例,你需要做 forkedEm.getRepository(Ent).

顺便说一句,如果您在节点 12+ 上,您也可以使用 AsyncLocalStorage 更快(并且未弃用)。 RequestContext 帮助程序实现将在 v5 中使用 ALS,因为需要节点 12+。

https://mikro-orm.io/docs/async-local-storage

您可以做的另一件事是手动使用 RequestContext 帮助程序而不是通过中间件 - 如下所示:

(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;
    }
  }
}