diff --git a/src/services/crud.service.ts b/src/services/crud.service.ts index 38e80dbd..0621598e 100755 --- a/src/services/crud.service.ts +++ b/src/services/crud.service.ts @@ -300,15 +300,23 @@ private async prepareManyToManyAuditSnapshot(entity: T,id: number,modelSingularN field.relationType !== 'one-to-many' ); if (auditRelationFields.length > 0) { - const relations: any = {}; - auditRelationFields.forEach(field => relations[field.name] = true); + // Fetch each audit-tracked relation independently rather than joining all of + // them into a single query: for an entity with several many-to-many + // relations, one combined query multiplies row counts across every joined + // relation at once and can blow past the statement timeout as data grows. const auditBeforeEntity = await this.repo.findOne({ where: { id: id, } as unknown as FindOptionsWhere, - relations: relations as any, }); if (auditBeforeEntity) { + for (const field of auditRelationFields) { + (auditBeforeEntity as any)[field.name] = await this.repo.manager + .createQueryBuilder() + .relation(this.repo.target, field.name) + .of(id) + .loadMany(); + } Object.defineProperty(entity, AUDIT_BEFORE_SNAPSHOT, { configurable: true, enumerable: false, diff --git a/src/subscribers/audit.subscriber.ts b/src/subscribers/audit.subscriber.ts index 31213fbf..cae375de 100644 --- a/src/subscribers/audit.subscriber.ts +++ b/src/subscribers/audit.subscriber.ts @@ -98,19 +98,26 @@ export class AuditSubscriber implements EntitySubscriberInterface { return null; } - const relations: Record = {}; - - auditRelationFields.forEach(field => { - relations[field.name] = true; - }); - const relationBefore = event.entity?.[AUDIT_BEFORE_SNAPSHOT] ?? null; - const relationAfter = await event.queryRunner.manager.getRepository(event.metadata.target as any).findOne({ + // Same fix as CRUDService.prepareManyToManyAuditSnapshot: load each audit-tracked + // relation independently instead of joining them all into one query, so the row + // count of one relation doesn't multiply against every other joined relation. + const targetRepo = event.queryRunner.manager.getRepository(event.metadata.target as any); + const relationAfter = await targetRepo.findOne({ where: { id: entityId } as any, - relations: relations as any, }); + if (relationAfter) { + for (const field of auditRelationFields) { + (relationAfter as any)[field.name] = await event.queryRunner.manager + .createQueryBuilder() + .relation(event.metadata.target as any, field.name) + .of(entityId) + .loadMany(); + } + } + if (relationBefore && relationAfter) { auditRelationFields.forEach(field => { const oldIds = Array.isArray(relationBefore[field.name])