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

1

9

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.

Necessitous answered 25/6, 2019 at 20:21 Comment(2)
Did you ever find a solution to this?Asphyxia
i did not. I cam to the conclusion it would require a whole other join table that i needed to keep updated, which seemed awkward. i only query documents through their parent entities. If i'm showing a full list of documents, I do it through querying all entites which can support documents. I came to conclusion this was enough for my use case.Necessitous
J
1

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
Jerroldjerroll answered 7/2, 2020 at 10:39 Comment(2)
hi tim, thanks for you answer. But i'm afraid you've misunderstood the question, which probably is not clearly written enough. The question is not about needing to specify additional fields on the post side, which leftJoinAndMap does help solve, but about not wanting to create additional fields on the document side. But that meta must be defined somewhere. You are still querying on posts, and I would still have to create additional fields on documents to show which field 'role' the document fills ('default', 'featured', 'other' and so on for all other entities which reference documents)Necessitous
Perhaps this would help make it more clear: imagine now I define an additional entity 'User'. 'User' has field 'featuredProfile' which also has a relationship to documents. How do i query documents to return all documents, and also their parent entities. Currently my conclusion is this is not possible in typeORM without custom join tables, or some kind of manual 'flexible' field which stores lists of entity types and their IDs, which could then be used in a custom query,Necessitous

© 2022 - 2024 — McMap. All rights reserved.