如何正确使用 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;
}
}
}
文档强调我应该为每个请求使用一个新的 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;
}
}
}