【问题标题】:TypeORM Polymorphic Relations类型ORM 多态关系
【发布时间】:2018-10-25 17:47:50
【问题描述】:

我正在使用 TypeORM 将 Laravel 应用程序迁移到 Node 应用程序。有没有人能够在 TypeOrm 中实现类似于Laravel's Polymorphic Relations 的东西?

我试图重现的示例架构:

export class Notification {
    id: string;
    attachable_id: number;
    attachable_type: string;
}

我希望能够拥有一个可以是任何类型的 notification.attachable 关系。然后,理想情况下,我可以急切地为用户加载他们最近的 x 个通知,并在每个通知上附加附件。

【问题讨论】:

    标签: typeorm


    【解决方案1】:

    编辑:

    所以我进行了重构/重写,并将其全部放入一个 repo https://github.com/bashleigh/typeorm-polymorphic

    所以,我一直在考虑尝试为此实现一些东西。我有 2 天的时间匆忙实施一些东西,所以我做了这个粗鲁的东西。

    import {
      FindManyOptions,
      DeepPartial,
      ObjectID,
      FindConditions,
      UpdateResult,
      Repository,
      SaveOptions,
    } from 'typeorm';
    import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
    
    export interface PolymorphicInterface {
      entityId: string;
      entityType: string;
    }
    
    export type PolyMorphicType<K> = PolymorphicInterface & DeepPartial<K>;
    
    export const POLYMORPHIC_RELATIONSHIP = 'POLYMORPHIC_RELATIONSHIP';
    
    export interface PolymorphicOptions {
      type: Function;
      parent: Function;
      property: string | Symbol;
    }
    
    export const PolyMorphic = (type: Function): PropertyDecorator => (
      target: Object,
      propertyKey: string | Symbol,
    ): void =>
      Reflect.defineMetadata(
        `${POLYMORPHIC_RELATIONSHIP}::${propertyKey}`,
        {
          type,
          parent: target.constructor.name,
          property: propertyKey,
        },
        target,
      );
    
    export class PolymorphicRepository<T extends DeepPartial<T>> extends Repository<T> {
      private getMetadata(): Array<PolymorphicOptions> {
        let keys = Reflect.getMetadataKeys((this.metadata.target as Function)['prototype']);
    
        if (!Array.isArray(keys)) {
          return [];
        }
    
        keys = keys.filter((key: string) => {
          const parts = key.split('::');
          return parts[0] === POLYMORPHIC_RELATIONSHIP;
        });
    
        if (!keys) {
          return [];
        }
    
        return keys.map(
          (key: string): PolymorphicOptions =>
            Reflect.getMetadata(key, (this.metadata.target as Function)['prototype']),
        );
      }
    
      async find(findOptions?: FindConditions<T> | FindManyOptions<T>): Promise<T[]> {
        const polymorphicMetadata = this.getMetadata();
    
        if (Object.keys(polymorphicMetadata).length === 0) {
          return super.find(findOptions);
        }
    
        const entities = await super.find(findOptions);
    
        return this.hydratePolymorphicEntities(entities);
      }
    
      public async hydratePolymorphicEntities(entities: Array<T>): Promise<Array<T>> {
        const metadata = this.getMetadata();
    
        metadata.forEach(
          async (data: PolymorphicOptions): Promise<void> => {
            await Promise.all(
              entities.map(
                async (entity: T): Promise<void> => {
                  const repository = this.manager.getRepository(data.type);
                  const property = data.property;
                  const parent = data.parent;
    
                  if (!repository) {
                    throw new Error(
                      `Repository not found for type [${
                        data.type
                      }] using property [${property}] on parent entity [${parent}]`,
                    );
                  }
    
                  const morphValues = await repository.find({
                    where: {
                      //@ts-ignore
                      entityId: entity.id, // TODO add type AbstractEntity
                      entityType: this.metadata.targetName,
                    },
                  });
    
                  //@ts-ignore
                  entity[property] = morphValues;
                },
              ),
            );
          },
        );
    
        return entities;
      }
    
      public async update(
        criteria:
          | string
          | string[]
          | number
          | number[]
          | Date
          | Date[]
          | ObjectID
          | ObjectID[]
          | FindConditions<T>,
        partialEntity: QueryDeepPartialEntity<T>,
      ): Promise<UpdateResult> {
        const polymorphicMetadata = this.getMetadata();
        if (Object.keys(polymorphicMetadata).length === 0) {
          return super.update(criteria, partialEntity);
        }
    
        const result = super.update(criteria, partialEntity);
    
        // TODO update morphs
        throw new Error("CBA I'm very tired");
    
        return result;
      }
    
      public async save<E extends DeepPartial<T>>(
        entity: E | Array<E>,
        options?: SaveOptions & { reload: false },
      ): Promise<E & T | Array<E & T>> {
        const polymorphicMetadata = this.getMetadata();
    
        if (Object.keys(polymorphicMetadata).length === 0) {
          return Array.isArray(entity) ? super.save(entity, options) : super.save(entity);
        }
    
        const result = Array.isArray(entity)
          ? await super.save(entity, options)
          : await super.save(entity);
    
        Array.isArray(result)
          ? await Promise.all(result.map((res: T) => this.saveMorphs(res)))
          : await this.saveMorphs(result);
    
        return result;
      }
    
      private async saveMorphs(entity: T): Promise<void> {
        const metadata = this.getMetadata();
    
        await Promise.all(
          metadata.map(
            async (data: PolymorphicOptions): Promise<void> => {
              const repository: Repository<PolymorphicInterface> = this.manager.getRepository(
                data.type,
              );
              const property = data.property;
              const parent = data.parent;
              const value: Partial<PolymorphicInterface> | Array<Partial<PolymorphicInterface>> =
                //@ts-ignore
                entity[property];
    
              if (typeof value === 'undefined' || value === undefined) {
                return new Promise(resolve => resolve());
              }
    
              if (!repository) {
                throw new Error(
                  `Repository not found for type [${
                    data.type
                  }] using property [${property}] on parent entity [${parent}]`,
                );
              }
    
              let result: Array<any> | any;
    
              if (Array.isArray(value)) {
                //@ts-ignore
                result = await Promise.all(
                  value.map(val => {
                    // @ts-ignore
                    val.entityId = entity.id;
                    val.entityType = this.metadata.targetName;
                    return repository.save(
                      value instanceof data.type ? value : repository.create(value),
                    );
                  }),
                );
              } else {
                // @ts-ignore
                value.entityId = entity.id; // TODO resolve AbstractEntity for T
                value.entityType = this.metadata.targetName;
    
                result = await repository.save(
                  value instanceof data.type ? value : repository.create(value),
                );
              }
    
              // @ts-ignore
              entity[property] = result;
            },
          ),
        );
      }
    }
    
    

    这很粗糙,但这就是我为解决这个问题而实施的。基本上我已经实现了我自己的方法来处理通过创建我自己的存储库来处理元数据中定义的实体的保存。

    那你就可以这样使用了

    @Entity()
    export class TestEntity {
      @PolyMorphic(SomeOtherEntity)
      property: SomeOtherEntity[];
    }
    

    打字真的很糟糕,但这只是因为我有 1 天的时间来实现这个功能,而且我是在飞机上完成的

    【讨论】:

      猜你喜欢
      • 2020-10-23
      • 2017-09-30
      • 1970-01-01
      • 2020-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-05
      相关资源
      最近更新 更多