首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >TypeORM多态关系

TypeORM多态关系
EN

Stack Overflow用户
提问于 2018-10-25 17:47:50
回答 1查看 5.5K关注 0票数 9

我正在使用TypeORM将一个Laravel应用程序迁移到Node应用程序。有人能在TypeOrm中实现类似于TypeOrm的东西吗?

我正在尝试复制的示例模式:

代码语言:javascript
复制
export class Notification {
    id: string;
    attachable_id: number;
    attachable_type: string;
}

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

EN

回答 1

Stack Overflow用户

发布于 2019-11-20 13:26:00

编辑:

所以我做了一个重构/重写,并将其全部放入回购https://github.com/bashleigh/typeorm-polymorphic中。

所以,我已经考虑了一段时间来尝试实现一些东西。我有两天时间匆忙完成一些事情,所以我做了这个破东西。

代码语言:javascript
复制
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;
        },
      ),
    );
  }
}

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

然后你就可以像这样使用它

代码语言:javascript
复制
@Entity()
export class TestEntity {
  @PolyMorphic(SomeOtherEntity)
  property: SomeOtherEntity[];
}

输入非常糟糕,但这只是因为我有1天的时间来实现这个特性,并且我在飞机上完成了这个功能。

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52995280

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档