I nodijs NestJS caricano commenti nidificati in una query con relazioni?


10

Ho i seguenti modelli:

User, Customer ,Comment

L'utente può commentare a Customer , l'utente può rispondere al commento di un altro utente, ricorsivamente illimitato.

L'ho fatto ma è limitato a una sola risposta e desidero ricevere tutte le risposte NESTED:

public async getCommentsForCustomerId(customerId: string): Promise<CustomerComment[]> {
    return this.find({where: {customer: {id: customerId}, parentComment: null}, relations: ['childComments']});
}

Tuttavia, la risposta che ottengo è nidificata solo a un livello:

[
    {
        "id": "7b5b654a-efb0-4afa-82ee-c00c38725072",
        "content": "test",
        "created_at": "2019-12-03T15:14:48.000Z",
        "updated_at": "2019-12-03T15:14:49.000Z",
        "childComments": [
            {
                "id": "7b5b654a-efb0-4afa-82ee-c00c38725073",
                "content": "test reply",
                "created_at": "2019-12-03T15:14:48.000Z",
                "updated_at": "2019-12-03T15:14:49.000Z",
                "parentCommentId": "7b5b654a-efb0-4afa-82ee-c00c38725072"
            }
        ]
    }
]

Come posso fare una query per nidificarli tutti in typeorm?

Definizione entità (nota cliente rinominato in Lead) :

@Entity('leads_comments')
export class LeadComment {

  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ManyToOne(type => LeadComment, comment => comment.childComments, {nullable: true})
  parentComment: LeadComment;

  @OneToMany(type => LeadComment, comment => comment.parentComment)
  @JoinColumn({name: 'parentCommentId'})
  childComments: LeadComment[];

  @RelationId((comment: LeadComment) => comment.parentComment)
  parentCommentId: string;

  @ManyToOne(type => User, {cascade: true})
  user: User | string;

  @RelationId((comment: LeadComment) => comment.user, )
  userId: string;

  @ManyToOne(type => Lead, lead => lead.comments, {cascade: true})
  lead: Lead | string;

  @RelationId((comment: LeadComment) => comment.lead)
  leadId: string;

  @Column('varchar')
  content: string;

  @CreateDateColumn()
  created_at: Date;

  @UpdateDateColumn()
  updated_at: Date;
}

1
Puoi aggiungere le definizioni delle tue entità?
zenbeni,

@zenbeni Aggiunto grazie
Ben Beri il

Risposte:


7

In pratica stai usando un Adjacency list Tree.

L'elenco di adiacenza è un modello semplice con autoreferenziazione. Il vantaggio di questo approccio è la semplicità, ma lo svantaggio è che non puoi gestire alberi profondi con quello.

C'è un modo ricorsivo per farlo con l'elenco Adiacenza, ma non funziona con MySQL.

La soluzione è utilizzare un altro tipo di albero. Altri possibili alberi sono:

  • Set nidificato : è molto efficace per le letture, ma è male per le scritture. Non è possibile avere più radici nel set nidificato.
  • Path materializzato : (chiamato anche Path Enumeration) è semplice ed efficace.
  • Tabella di chiusura : memorizza le relazioni tra padre e figlio in una tabella separata. È efficiente sia in lettura che in scrittura (l'aggiornamento o la rimozione del genitore di un componente non è ancora stato implementato)
@Entity()
@Tree("nested-set") // or @Tree("materialized-path") or @Tree("closure-table")
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @TreeChildren()
    children: Category[];

    @TreeParent()
    parent: Category;
}

Per caricare un albero usare:

const manager = getManager();
const trees = await manager.getTreeRepository(Category).findTrees();

Dopo aver ottenuto un repository ad albero, è possibile utilizzare le funzioni successive: findTrees(), findRoots(), findDescendants(), findDescendantsTree()e altre. Vedi la documentazione per di più.

Ulteriori informazioni sui diversi tipi di alberi: modelli per dati gerarchici


1

Come ha detto Gabriel, è meglio che altri modelli di dati facciano ciò che desideri in termini di prestazioni. Tuttavia, se non è possibile modificare il design del database, è possibile utilizzare alternative (che sono meno performanti o carine, ma ciò che funziona in produzione è tutto ciò che conta alla fine).

Quando imposti il ​​valore Lead nel tuo LeadComment, posso suggerire di impostare questo valore anche sulle risposte sul commento radice sulla creazione della risposta (dovrebbe essere facile nel codice). In questo modo puoi recuperare tutti i commenti sul tuo cliente in una query (comprese le risposte).

const lead = await leadRepository.findOne(id);
const comments = await commentRepository.find({lead});

Ovviamente, dovrai eseguire un batch SQL per popolare i valori di colonna mancanti, ma è una cosa una volta, e una volta che anche la tua base di codice sarà patchata, non dovrai eseguire nulla in seguito. E non cambia la struttura del database (solo il modo in cui i dati vengono popolati).

Quindi puoi creare in nodejs tutto il materiale (elenchi di risposte). Per ottenere il commento "radice", è sufficiente filtrare per commento che non sono risposte (che non hanno genitori). Se vuoi solo i commenti di root dal database, puoi anche cambiare la query in solo quelli (con parentComment null nella colonna SQL).

function sortComment(c1: LeadComment , c2: LeadComment ): number {
    if (c1.created_at.getTime() > c2.created_at.getTime()) {
    return 1;
    }
    if (c1.created_at.getTime() < c2.created_at.getTime()) {
        return -1;
    }
    return 0;
}
const rootComments = comments
    .filter(c => !c.parentComment)
    .sort(sortComment);

Quindi puoi ottenere risposte su rootComments e creare ricorsivamente l'intero elenco nel nodo.

function buildCommentList(currentList: LeadComment[], allComments: LeadComment[]): LeadComment[] {
    const lastComment = currentList[currentList.length - 1];
    const childComments = allComments
        .filter(c => c.parentComment?.id === lastComment.id)
        .sort(sortComment);
    if (childComments.length === 0) {
        return currentList;
    }
    const childLists = childComments.flatMap(c => buildCommentList([c], allComments));
    return [...currentList, ...childLists];
}

const listsOfComments = rootComments.map(r => buildCommentList([r], comments));

Probabilmente ci sono modi più ottimizzati per calcolare questi elenchi, questo è per me uno dei più semplici che si possano fare.

A seconda del numero di commenti può rallentare (è possibile limitare i risultati in base alla data e all'ora, ad esempio in modo che dovrebbe essere abbastanza buono?) Quindi attenzione, non recuperare l'universo dei commenti su un lead "Justin Bieber" che ottiene molti commenti ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.