baku
baku

Reputation: 775

TypeORM: how to implement bidirectional relationship, multiple fields --> one entity type

I've created a 'document' entity:

e.g.

@Entity()
export class Document {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  path: string;
   ...

}

Multiple documents can be related to different entity types: post, userProfile etc

in the post entity for example, I have several fields which all specify document relationships.

  @OneToOne(type => DocumentEntity)
  @JoinColumn({ name: 'default_document' })
  defaultDocument: DocumentEntity;

  @OneToOne(type => DocumentEntity)
  @JoinColumn({ name: 'featured_document' })
  featuredDocument: DocumentEntity;

  @OneToMany(type => DocumentEntity, document => document.post)
  @JoinColumn({ name: 'other_documents' })
  otherDocs: DocumentEntity[]; 

I'm unclear how to make the document relationships bidirectional. I had hoped to have a single field on document like:

  @ManyToOne(type => abstractEntity, entity => entity.document)
  parentEntity: abstractEntity;

This way if I'm querying document entities for their parent relationships, I would have a result like:

documents: [
{
id: 1,
name: 'document 1', 
path: 'https://image.hosted.service/1.jpg', 
parentEntityId: 23
}, 
{
id: 2
name: 'document 2', 
path: 'https://image.hosted.service/2.jpg'
parentEntityId: 27
}
] 

But Typeorm seems to want me to define an exact matching field for each parent relationship field on documentEntity like:

@Entity()
export class Document {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  path: string;
  ...

  @OneToOne(type => PostEntity, post => post.defaultDocument)
  postEntityDefaultDoc: PostEntity;

  @OneToOne(type => PostEntity, post => post.featuredDocument)
  postEntityFeaturedDoc: PostEntity;

  @ManyToOne(type => PostEntity, post => post.otherDocs)
  otherDocs: PostEntity[];


}

For the sake of simplicity in this example, there are no M:N relationships: document can have at most one parent.

It doesn't seem correct that I would have to define a new field on document entity, for every possible instance where a parent entity field references a document. A query on document would not return a list with one field defining the parent entity, instead I have to parse/aggregate an arbitrary number of fields.

I can't seem to find any tutorials/examples in which a single entity has many fields each referencing the same other entity, which is making me think my basic approach is flawed.

Upvotes: 9

Views: 5564

Answers (1)

Tim Schumacher
Tim Schumacher

Reputation: 153

The secret ingridient is leftJoinAndMapMany which allows you to join abitrary entities and map it onto attributes.

Here is what I would do in your case. The DocumentEntity would look like that:

@Entity()
class DocumentEntity {
    @PrimaryGeneratedColumn()
    public id!: number;

    @Column()
    public entity!: string;

    @Column({
        name: 'entity_id',
    })
    public entityId!: string;

    @Column()
    public name!: string;
}

Your PostEntity would look like that:

@Entity()
class PostEntity {
    @PrimaryGeneratedColumn()
    public id!: number;

    @Column()
    public name: string;

    public documents?: DocumentEntity[];
}

As you might notice, the documents on the post has no anotation. Thats because we will do the join with the aforementioned method. Your query would look something like that:

connection
    .getRepository(PostEntity)
    .createQueryBuilder('p')
    .leftJoinAndMapMany(
        'p.documents',
        DocumentEntity,
        'p__d',
        '(p.id = md.entityId AND md.entity = :documentEntity)',
        {
            documentEntity: PostEntity.name,
        },
    )
    .getMany()

These methods are available for joining these entities:

  • leftJoinAndMapMany
  • innerJoinAndMapMany
  • leftJoinAndMapOne
  • innerJoinAndMapOne

Upvotes: 1

Related Questions