本文介绍了TypeORM 多态关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 TypeORM 将 Laravel 应用程序迁移到 Node 应用程序.有没有人能够在 TypeOrm 中实现类似于 Laravel 的多态关系的东西?

我试图重现的示例架构:

导出类通知{id:字符串;attachable_id:数字;attachable_type: 字符串;}

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

解决方案

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

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

import {FindManyOptions,深偏,对象 ID,查找条件,更新结果,存储库,保存选项,来自'typeorm';从'typeorm/query-builder/QueryPartialEntity'导入{QueryDeepPartialEntity};导出接口多态接口{entityId:字符串;实体类型:字符串;}导出类型 PolyMorphicType<K>= 多态接口 &DeepPartial<K>;export const POLYMORPHIC_RELATIONSHIP = 'POLYMORPHIC_RELATIONSHIP';导出接口 PolymorphicOptions {类型:功能;父:函数;属性:字符串 |象征;}export const PolyMorphic = (type: Function): PropertyDecorator =>(目标:对象,属性键:字符串 |象征,): 无效 =>Reflect.defineMetadata(`${POLYMORPHIC_RELATIONSHIP}::${propertyKey}`,{类型,父级:target.constructor.name,属性:propertyKey,},目标,);导出类 PolymorphicRepository>扩展存储库<T>{private getMetadata(): Array{let keys = Reflect.getMetadataKeys((this.metadata.target as Function)['prototype']);如果 (!Array.isArray(keys)) {返回 [];}键 = 键.过滤器((键:字符串)=> {const 部分 = key.split('::');返回部分[0] === POLYMORPHIC_RELATIONSHIP;});如果(!键){返回 [];}返回键.map((key: string): PolymorphicOptions =>Reflect.getMetadata(key, (this.metadata.target as Function)['prototype']),);}async find(findOptions?: FindConditions | FindManyOptions): Promise{const polymorphicMetadata = this.getMetadata();if (Object.keys(polymorphicMetadata).length === 0) {返回 super.find(findOptions);}const entity = await super.find(findOptions);返回 this.hydratePolymorphicEntities(entities);}public async hydratePolymorphicEntities(entities: Array): Promise{const metadata = this.getMetadata();元数据.forEach(异步(数据:PolymorphicOptions):Promise=>{等待 Promise.all(实体.map(异步(实体:T):Promise=>{const repository = this.manager.getRepository(data.type);const 属性 = data.property;const parent = data.parent;如果(!存储库){抛出新错误(`找不到类型 [${ 的存储库数据类型}] 在父实体 [${parent}]` 上使用属性 [${property}],);}const morphValues = await repository.find({在哪里: {//@ts-忽略entityId: entity.id,//TODO 添加类型 AbstractEntity实体类型:this.metadata.targetName,},});//@ts-忽略实体[属性] = morphValues;},),);},);返回实体;}公共异步更新(标准:|细绳|细绳[]|数字|数字[]|日期|日期[]|对象ID|对象 ID[]|FindConditions<T>,部分实体:QueryDeepPartialEntity,): Promise{const polymorphicMetadata = this.getMetadata();if (Object.keys(polymorphicMetadata).length === 0) {返回 super.update(criteria, partialEntity);}const 结果 = super.update(criteria, partialEntity);//TODO 更新变形throw new Error("CBA 我很累");返回结果;}public async save(实体:E |阵列E,选项?:SaveOptions &{重新加载:假},):承诺<E&T |阵列<E&>>{const polymorphicMetadata = this.getMetadata();if (Object.keys(polymorphicMetadata).length === 0) {返回 Array.isArray(entity) ?super.save(entity, options) : super.save(entity);}const 结果 = Array.isArray(entity)?等待 super.save(实体,选项): 等待 super.save(entity);Array.isArray(结果)?await Promise.all(result.map((res: T) => this.saveMorphs(res))): 等待 this.saveMorphs(result);返回结果;}私有异步 saveMorphs(entity: T): Promise{const metadata = this.getMetadata();等待 Promise.all(元数据.map(异步(数据:PolymorphicOptions):Promise=>{const 存储库:存储库= this.manager.getRepository(数据类型,);const 属性 = data.property;const parent = data.parent;const 值:部分<PolymorphicInterface>|数组<部分<多态接口>>=//@ts-忽略实体[财产];if (typeof value === 'undefined' || value === undefined) {return new Promise(resolve => resolve());}如果(!存储库){抛出新错误(`找不到类型 [${ 的存储库数据类型}] 在父实体 [${parent}]` 上使用属性 [${property}],);}让结果:Array|任何;如果 (Array.isArray(value)) {//@ts-忽略结果 = 等待 Promise.all(value.map(val => {//@ts-忽略val.entityId = entity.id;val.entityType = this.metadata.targetName;返回repository.save(值 instanceof data.type ?值:repository.create(value),);}),);} 别的 {//@ts-忽略value.entityId = entity.id;//TODO 为 T 解析 AbstractEntityvalue.entityType = this.metadata.targetName;结果 = 等待存储库.save(值 instanceof data.type ?值:repository.create(value),);}//@ts-忽略实体[属性] = 结果;},),);}}

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

然后就可以这样使用了

@Entity()导出类 TestEntity {@PolyMorphic(SomeOtherEntity)属性:SomeOtherEntity[];}

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

I am migrating a Laravel app to Node app using TypeORM. Has anyone been able to implement something similar to Laravel's Polymorphic Relations in TypeOrm?

Example schema I am trying to reproduce:

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

I want to be able to to have a notification.attachable relation that could be of any type. Then, ideally, I can eager load a user with their last x notifications, with the attachable on each notification.

解决方案

EDIT:

So I done a refactor/rewrite and put it all in a repo https://github.com/bashleigh/typeorm-polymorphic

So, I've been thinking of trying to implement something for this for a while. I had 2 days to implement something in a hurry so I made this crud thing.

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;
        },
      ),
    );
  }
}

It's pretty rough but that's what I implemented to tackle this. Essentially I've implemented is my own methods to handle saving of entities that are defined within the metadata by creating my own repository.

Then you can use it like so

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

这篇关于TypeORM 多态关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 01:45