From 8425ce4987dcbf927b49a330476ab37e8a40dbc9 Mon Sep 17 00:00:00 2001 From: Weiko Date: Thu, 22 Feb 2024 10:27:15 +0100 Subject: [PATCH] Add onDeleteAction to RelationMetadata (#4100) * Add onDeleteAction to relationMetadata * rename to SET NULL * fix migration * fix migration * fix after review --- ...922-addOnDeleteActionToRelationMetadata.ts | 25 ++++++ .../object-metadata.service.ts | 4 +- .../relation-metadata.entity.ts | 14 ++++ .../relation-metadata.service.ts | 2 +- .../workspace-migration.entity.ts | 18 +++- .../workspace-health-issue.interface.ts | 2 + .../workspace-table-definition.interface.ts | 2 + .../services/database-structure.service.ts | 7 +- .../relation-metadata.health.service.ts | 10 +++ .../workspace-migration-relation.factory.ts | 84 ++++++++++++++++++- .../workspace-migration-runner.service.ts | 70 +++++++++++++++- .../workspace-relation.comparator.spec.ts | 22 +++++ .../workspace-relation.comparator.ts | 62 +++++++++++--- .../decorators/relation-metadata.decorator.ts | 9 +- .../factories/standard-relation.factory.ts | 1 + .../interfaces/comparator.interface.ts | 3 +- .../partial-relation-metadata.interface.ts | 10 +++ .../reflect-relation-metadata.interface.ts | 7 +- .../workspace-metadata-updater.service.ts | 11 +++ ...orkspace-sync-relation-metadata.service.ts | 14 +++- .../storage/workspace-sync.storage.ts | 11 +++ 21 files changed, 357 insertions(+), 31 deletions(-) create mode 100644 packages/twenty-server/src/database/typeorm/metadata/migrations/1708449210922-addOnDeleteActionToRelationMetadata.ts create mode 100644 packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/partial-relation-metadata.interface.ts diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1708449210922-addOnDeleteActionToRelationMetadata.ts b/packages/twenty-server/src/database/typeorm/metadata/migrations/1708449210922-addOnDeleteActionToRelationMetadata.ts new file mode 100644 index 000000000..60b7bce0c --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1708449210922-addOnDeleteActionToRelationMetadata.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddOnDeleteActionToRelationMetadata1708449210922 + implements MigrationInterface +{ + name = 'AddOnDeleteActionToRelationMetadata1708449210922'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "metadata"."relationMetadata_ondeleteaction_enum" AS ENUM('CASCADE', 'RESTRICT', 'SET_NULL')`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ADD "onDeleteAction" "metadata"."relationMetadata_ondeleteaction_enum" NOT NULL DEFAULT 'SET_NULL'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" DROP COLUMN "onDeleteAction"`, + ); + await queryRunner.query( + `DROP TYPE "metadata"."relationMetadata_ondeleteaction_enum"`, + ); + } +} diff --git a/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts index 0563bd040..03ccaa811 100644 --- a/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts @@ -347,7 +347,7 @@ export class ObjectMetadataService extends TypeOrmQueryService, + relationMetadataCollection: RelationMetadataEntity[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + + for (const relationMetadata of relationMetadataCollection) { + const toObjectMetadata = + originalObjectMetadataMap[relationMetadata.toObjectMetadataId]; + const fromObjectMetadata = + originalObjectMetadataMap[relationMetadata.fromObjectMetadataId]; + + if (!toObjectMetadata) { + throw new Error( + `ObjectMetadata with id ${relationMetadata.toObjectMetadataId} not found`, + ); + } + + if (!fromObjectMetadata) { + throw new Error( + `ObjectMetadata with id ${relationMetadata.fromObjectMetadataId} not found`, + ); + } + + const toFieldMetadata = toObjectMetadata.fields.find( + (field) => field.id === relationMetadata.toFieldMetadataId, + ); + + if (!toFieldMetadata) { + throw new Error( + `FieldMetadata with id ${relationMetadata.toFieldMetadataId} not found`, + ); + } + + const migrations: WorkspaceMigrationTableAction[] = [ + { + name: computeObjectTargetTable(toObjectMetadata), + action: 'alter', + columns: [ + { + action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY, + columnName: `${camelCase(toFieldMetadata.name)}Id`, + }, + ], + }, + { + name: computeObjectTargetTable(toObjectMetadata), + action: 'alter', + columns: [ + { + action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, + columnName: `${camelCase(toFieldMetadata.name)}Id`, + referencedTableName: computeObjectTargetTable(fromObjectMetadata), + referencedTableColumnName: 'id', + isUnique: + relationMetadata.relationType === + RelationMetadataType.ONE_TO_ONE, + onDelete: relationMetadata.onDeleteAction, + }, + ], + }, + ]; + + workspaceMigrations.push({ + workspaceId: relationMetadata.workspaceId, + name: generateMigrationName( + `update-relation-from-${fromObjectMetadata.nameSingular}-to-${toObjectMetadata.nameSingular}`, + ), + isCustom: false, + migrations, + }); + } + + return workspaceMigrations; + } + private async createRelationMigration( originalObjectMetadataMap: Record, relationMetadataCollection: RelationMetadataEntity[], @@ -88,13 +169,14 @@ export class WorkspaceMigrationRelationFactory { action: 'alter', columns: [ { - action: WorkspaceMigrationColumnActionType.RELATION, + action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, columnName: `${camelCase(toFieldMetadata.name)}Id`, referencedTableName: computeObjectTargetTable(fromObjectMetadata), referencedTableColumnName: 'id', isUnique: relationMetadata.relationType === RelationMetadataType.ONE_TO_ONE, + onDelete: relationMetadata.onDeleteAction, }, ], }, diff --git a/packages/twenty-server/src/workspace/workspace-migration-runner/workspace-migration-runner.service.ts b/packages/twenty-server/src/workspace/workspace-migration-runner/workspace-migration-runner.service.ts index ac7af163f..b60e6f9c3 100644 --- a/packages/twenty-server/src/workspace/workspace-migration-runner/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/workspace/workspace-migration-runner/workspace-migration-runner.service.ts @@ -15,8 +15,9 @@ import { WorkspaceMigrationColumnAction, WorkspaceMigrationColumnActionType, WorkspaceMigrationColumnCreate, - WorkspaceMigrationColumnRelation, + WorkspaceMigrationColumnCreateRelation, WorkspaceMigrationColumnAlter, + WorkspaceMigrationColumnDropRelation, } from 'src/metadata/workspace-migration/workspace-migration.entity'; import { WorkspaceCacheVersionService } from 'src/metadata/workspace-cache-version/workspace-cache-version.service'; import { WorkspaceMigrationEnumService } from 'src/workspace/workspace-migration-runner/services/workspace-migration-enum.service'; @@ -200,7 +201,7 @@ export class WorkspaceMigrationRunnerService { columnMigration, ); break; - case WorkspaceMigrationColumnActionType.RELATION: + case WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY: await this.createRelation( queryRunner, schemaName, @@ -208,6 +209,14 @@ export class WorkspaceMigrationRunnerService { columnMigration, ); break; + case WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY: + await this.dropRelation( + queryRunner, + schemaName, + tableName, + columnMigration, + ); + break; case WorkspaceMigrationColumnActionType.DROP: await queryRunner.dropColumn( `${schemaName}.${tableName}`, @@ -325,7 +334,7 @@ export class WorkspaceMigrationRunnerService { queryRunner: QueryRunner, schemaName: string, tableName: string, - migrationColumn: WorkspaceMigrationColumnRelation, + migrationColumn: WorkspaceMigrationColumnCreateRelation, ) { await queryRunner.createForeignKey( `${schemaName}.${tableName}`, @@ -334,7 +343,7 @@ export class WorkspaceMigrationRunnerService { referencedColumnNames: [migrationColumn.referencedTableColumnName], referencedTableName: migrationColumn.referencedTableName, referencedSchema: schemaName, - onDelete: 'CASCADE', + onDelete: migrationColumn.onDelete?.replace(/_/g, ' '), }), ); @@ -349,4 +358,57 @@ export class WorkspaceMigrationRunnerService { ); } } + + private async dropRelation( + queryRunner: QueryRunner, + schemaName: string, + tableName: string, + migrationColumn: WorkspaceMigrationColumnDropRelation, + ) { + const foreignKeyName = await this.getForeignKeyName( + queryRunner, + schemaName, + tableName, + migrationColumn.columnName, + ); + + if (!foreignKeyName) { + throw new Error( + `Foreign key not found for column ${migrationColumn.columnName}`, + ); + } + + await queryRunner.dropForeignKey( + `${schemaName}.${tableName}`, + foreignKeyName, + ); + } + + private async getForeignKeyName( + queryRunner: QueryRunner, + schemaName: string, + tableName: string, + columnName: string, + ): Promise { + const foreignKeys = await queryRunner.query( + ` + SELECT + tc.constraint_name AS constraint_name + FROM + information_schema.table_constraints AS tc + JOIN + information_schema.key_column_usage AS kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + WHERE + tc.constraint_type = 'FOREIGN KEY' + AND tc.table_schema = $1 + AND tc.table_name = $2 + AND kcu.column_name = $3 + `, + [schemaName, tableName, columnName], + ); + + return foreignKeys[0]?.constraint_name; + } } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/comparators/__tests__/workspace-relation.comparator.spec.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/comparators/__tests__/workspace-relation.comparator.spec.ts index 09b602617..74b824e62 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/comparators/__tests__/workspace-relation.comparator.spec.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/comparators/__tests__/workspace-relation.comparator.spec.ts @@ -48,6 +48,28 @@ describe('WorkspaceRelationComparator', () => { ]); }); + it('should generate UPDATE action for changed relations', () => { + const original = [ + createMockRelationMetadata({ onDeleteAction: 'CASCADE' }), + ]; + const standard = [ + createMockRelationMetadata({ onDeleteAction: 'SET_NULL' }), + ]; + + const result = comparator.compare(original, standard); + + expect(result).toEqual([ + { + action: ComparatorAction.UPDATE, + object: expect.objectContaining({ + fromObjectMetadataId: 'object-1', + fromFieldMetadataId: 'field-1', + onDeleteAction: 'SET_NULL', + }), + }, + ]); + }); + it('should not generate any action for identical relations', () => { const relation = createMockRelationMetadata({}); const original = [{ id: '1', ...relation }]; diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/comparators/workspace-relation.comparator.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/comparators/workspace-relation.comparator.ts index 727a8c5f5..33f5d7c3a 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/comparators/workspace-relation.comparator.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/comparators/workspace-relation.comparator.ts @@ -11,6 +11,7 @@ import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation- import { transformMetadataForComparison } from 'src/workspace/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; const relationPropertiesToIgnore = ['createdAt', 'updatedAt'] as const; +const relationPropertiesToUpdate = ['onDeleteAction']; @Injectable() export class WorkspaceRelationComparator { @@ -51,19 +52,54 @@ export class WorkspaceRelationComparator { ); for (const difference of relationMetadataDifference) { - if (difference.type === 'CREATE') { - results.push({ - action: ComparatorAction.CREATE, - object: difference.value, - }); - } else if ( - difference.type === 'REMOVE' && - difference.path[difference.path.length - 1] !== 'id' - ) { - results.push({ - action: ComparatorAction.DELETE, - object: difference.oldValue, - }); + switch (difference.type) { + case 'CREATE': + results.push({ + action: ComparatorAction.CREATE, + object: difference.value, + }); + break; + case 'REMOVE': + if (difference.path[difference.path.length - 1] !== 'id') { + results.push({ + action: ComparatorAction.DELETE, + object: difference.oldValue, + }); + } + break; + case 'CHANGE': + const fieldName = difference.path[0]; + const property = difference.path[difference.path.length - 1]; + + if (!relationPropertiesToUpdate.includes(property as string)) { + continue; + } + + const originalRelationMetadata = + originalRelationMetadataMap[fieldName]; + + if (!originalRelationMetadata) { + throw new Error( + `Relation ${fieldName} not found in originalRelationMetadataMap`, + ); + } + + results.push({ + action: ComparatorAction.UPDATE, + object: { + id: originalRelationMetadata.id, + fromObjectMetadataId: + originalRelationMetadata.fromObjectMetadataId, + fromFieldMetadataId: originalRelationMetadata.fromFieldMetadataId, + toObjectMetadataId: originalRelationMetadata.toObjectMetadataId, + toFieldMetadataId: originalRelationMetadata.toFieldMetadataId, + workspaceId: originalRelationMetadata.workspaceId, + ...{ + [property]: difference.value, + }, + }, + }); + break; } } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator.ts index 62c5b11a2..448f7380b 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator.ts @@ -1,9 +1,13 @@ import 'reflect-metadata'; -import { RelationMetadataDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/reflect-relation-metadata.interface'; +import { + ReflectRelationMetadata, + RelationMetadataDecoratorParams, +} from 'src/workspace/workspace-sync-metadata/interfaces/reflect-relation-metadata.interface'; import { TypedReflect } from 'src/utils/typed-reflect'; import { convertClassNameToObjectMetadataName } from 'src/workspace/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util'; +import { RelationOnDeleteAction } from 'src/metadata/relation-metadata/relation-metadata.entity'; export function RelationMetadata( params: RelationMetadataDecoratorParams, @@ -29,8 +33,9 @@ export function RelationMetadata( toObjectNameSingular: params.objectName, fromFieldMetadataName: fieldKey, toFieldMetadataName: params.inverseSideFieldName ?? objectName, + onDelete: params.onDelete ?? RelationOnDeleteAction.SET_NULL, gate, - }, + } satisfies ReflectRelationMetadata, ], target.constructor, ); diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-relation.factory.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-relation.factory.ts index 33e2709a5..a18adacd0 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-relation.factory.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-relation.factory.ts @@ -110,6 +110,7 @@ export class StandardRelationFactory { fromFieldMetadataId: fromFieldMetadata?.id, toFieldMetadataId: toFieldMetadata?.id, workspaceId: context.workspaceId, + onDeleteAction: relationMetadata.onDelete, }; }); } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/comparator.interface.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/comparator.interface.ts index a90201192..8b4c722c2 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/comparator.interface.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/comparator.interface.ts @@ -43,4 +43,5 @@ export type FieldComparatorResult = export type RelationComparatorResult = | ComparatorCreateResult> - | ComparatorDeleteResult; + | ComparatorDeleteResult + | ComparatorUpdateResult>; diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/partial-relation-metadata.interface.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/partial-relation-metadata.interface.ts new file mode 100644 index 000000000..a9111a3fe --- /dev/null +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/partial-relation-metadata.interface.ts @@ -0,0 +1,10 @@ +import { ReflectRelationMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/reflect-Relation-metadata.interface'; + +export type PartialRelationMetadata = ReflectRelationMetadata & { + id: string; + workspaceId: string; + fromObjectMetadataId: string; + toObjectMetadataId: string; + fromFieldMetadataId: string; + toFieldMetadataId: string; +}; diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/reflect-relation-metadata.interface.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/reflect-relation-metadata.interface.ts index ee9d7af5f..06393424f 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/reflect-relation-metadata.interface.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/interfaces/reflect-relation-metadata.interface.ts @@ -1,11 +1,15 @@ import { GateDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/gate-decorator.interface'; -import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { + RelationOnDeleteAction, + RelationMetadataType, +} from 'src/metadata/relation-metadata/relation-metadata.entity'; export interface RelationMetadataDecoratorParams { type: RelationMetadataType; objectName: string; inverseSideFieldName?: string; + onDelete?: RelationOnDeleteAction; } export interface ReflectRelationMetadata { @@ -15,4 +19,5 @@ export interface ReflectRelationMetadata { fromFieldMetadataName: string; toFieldMetadataName: string; gate?: GateDecoratorParams; + onDelete: RelationOnDeleteAction; } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service.ts index 09b8bde0e..cc1620164 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service.ts @@ -209,6 +209,7 @@ export class WorkspaceMetadataUpdaterService { storage: WorkspaceSyncStorage, ): Promise<{ createdRelationMetadataCollection: RelationMetadataEntity[]; + updatedRelationMetadataCollection: RelationMetadataEntity[]; }> { const relationMetadataRepository = manager.getRepository( RelationMetadataEntity, @@ -223,6 +224,15 @@ export class WorkspaceMetadataUpdaterService { storage.relationMetadataCreateCollection, ); + /** + * Update relation metadata + */ + + const updatedRelationMetadataCollection = + await relationMetadataRepository.save( + storage.relationMetadataUpdateCollection, + ); + /** * Delete relation metadata */ @@ -250,6 +260,7 @@ export class WorkspaceMetadataUpdaterService { return { createdRelationMetadataCollection, + updatedRelationMetadataCollection, }; } } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts index 8f4d274c0..171678c97 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts @@ -84,6 +84,8 @@ export class WorkspaceSyncRelationMetadataService { for (const relationComparatorResult of relationComparatorResults) { if (relationComparatorResult.action === ComparatorAction.CREATE) { storage.addCreateRelationMetadata(relationComparatorResult.object); + } else if (relationComparatorResult.action === ComparatorAction.UPDATE) { + storage.addUpdateRelationMetadata(relationComparatorResult.object); } else if (relationComparatorResult.action === ComparatorAction.DELETE) { storage.addDeleteRelationMetadata(relationComparatorResult.object); } @@ -103,6 +105,16 @@ export class WorkspaceSyncRelationMetadataService { WorkspaceMigrationBuilderAction.CREATE, ); - return createRelationWorkspaceMigrations; + const updateRelationWorkspaceMigrations = + await this.workspaceMigrationRelationFactory.create( + originalObjectMetadataCollection, + metadataRelationUpdaterResult.updatedRelationMetadataCollection, + WorkspaceMigrationBuilderAction.UPDATE, + ); + + return [ + ...createRelationWorkspaceMigrations, + ...updateRelationWorkspaceMigrations, + ]; } } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/storage/workspace-sync.storage.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/storage/workspace-sync.storage.ts index e68c69912..4820c36da 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/storage/workspace-sync.storage.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/storage/workspace-sync.storage.ts @@ -1,5 +1,6 @@ import { PartialObjectMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-object-metadata.interface'; import { PartialFieldMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-field-metadata.interface'; +import { PartialRelationMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-relation-metadata.interface'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity'; @@ -25,6 +26,8 @@ export class WorkspaceSyncStorage { []; private readonly _relationMetadataDeleteCollection: RelationMetadataEntity[] = []; + private readonly _relationMetadataUpdateCollection: Partial[] = + []; constructor() {} @@ -56,6 +59,10 @@ export class WorkspaceSyncStorage { return this._relationMetadataCreateCollection; } + get relationMetadataUpdateCollection() { + return this._relationMetadataUpdateCollection; + } + get relationMetadataDeleteCollection() { return this._relationMetadataDeleteCollection; } @@ -90,6 +97,10 @@ export class WorkspaceSyncStorage { this._relationMetadataCreateCollection.push(relation); } + addUpdateRelationMetadata(relation: Partial) { + this._relationMetadataUpdateCollection.push(relation); + } + addDeleteRelationMetadata(relation: RelationMetadataEntity) { this._relationMetadataDeleteCollection.push(relation); }