我正在使用TypeORM将Laravel应用迁移到Node应用。有人能在TypeOrm中实现类似于Laravel's Polymorphic Relations的东西吗?
我要重现的示例架构:
export class Notification {
id: string;
attachable_id: number;
attachable_type: string;
}
我希望能够有一个notification.attachable关系,该关系可以是任何类型。然后,理想情况下,我可以急于向用户加载他们的最后x条通知,并在每个通知上附加附件。
答案 0 :(得分:1)
下面的示例可能会对您有所帮助。
@ManyToOne(type => tblabc, tblabc => tblabc.tblghi, {})
@JoinColumn({ name: 'abcID' })
abc: tblabc | null;
@ManyToOne(type => tbldef, tbldef => tbldef.ghi, {})
@JoinColumn({ name: 'defID' })
def: tbldef | null;
要在实体文件中建立表之间关系的几行内容。
谢谢。
答案 1 :(得分:0)
因此,一段时间以来,我一直在考虑尝试为此实现一些功能。我有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天的时间来实现此功能,而且我是在飞机上完成的