Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 431
baku Avatar asked Jun 25 '19 20:06

baku


1 Answers

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
like image 162
Tim Schumacher Avatar answered Sep 18 '22 15:09

Tim Schumacher