NestJS nodejs ładuje zagnieżdżone komentarze w jednym zapytaniu z relacjami?


10

Mam następujące modele:

User, Customer,Comment

Użytkownik może komentować Customer, użytkownik może odpowiadać na komentarze innego użytkownika, rekurencyjnie nieograniczony.

Zrobiłem to, ale ogranicza się to tylko do jednej odpowiedzi i chcę otrzymać wszystkie odpowiedzi NESTED:

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

Jednak odpowiedź, którą otrzymuję, jest zagnieżdżona tylko na jednym poziomie:

[
    {
        "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"
            }
        ]
    }
]

Jak mogę utworzyć zapytanie, aby zagnieździć je wszystkie wormorm?

Definicja jednostki (uwaga klienta zmieniono nazwę na 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
Czy możesz dodać definicje encji?
zenbeni

@zenbeni Dodane podziękowania
Ben Beri

Odpowiedzi:


7

Zasadniczo używasz Adjacency list Tree.

Lista adiacyencji jest prostym modelem z samodzielnymi odniesieniami. Zaletą tego podejścia jest prostota, ALE wadą jest to, że nie radzisz sobie z głębokimi drzewami.

Jest rekurencyjny sposób na zrobienie tego z listą Adjacency, ale nie działa z MySQL.

Rozwiązaniem jest użycie innego rodzaju drzewa. Inne możliwe drzewa to:

  • Zestaw zagnieżdżony : bardzo wydajny dla odczytów, ale zły dla zapisów. Nie możesz mieć wielu korzeni w zagnieżdżonym zestawie.
  • Ścieżka zmaterializowana : (zwana także wyliczaniem ścieżek) jest prosta i skuteczna.
  • Tabela zamknięcia : przechowuje relacje między rodzicem a dzieckiem w osobnej tabeli. Jest skuteczny zarówno w odczytach, jak i zapisach (aktualizacja lub usuwanie elementu nadrzędnego składnika nie zostało jeszcze zaimplementowane)
@Entity()
@Tree("nested-set") // or @Tree("materialized-path") or @Tree("closure-table")
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @TreeChildren()
    children: Category[];

    @TreeParent()
    parent: Category;
}

Aby załadować drzewo, użyj:

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

Po otrzymaniu repozytorium drzewa możesz użyć kolejnych funkcji: findTrees(), findRoots(), findDescendants(), findDescendantsTree()i innych. Więcej informacji znajduje się w dokumentacji .

Dowiedz się więcej o różnych typach drzew: Modele danych hierarchicznych


1

Jak powiedział Gabriel, inne modele danych lepiej robić to, co chcesz pod względem wydajności. Mimo to, jeśli nie możesz zmienić projektu bazy danych, możesz użyć alternatyw (które są mniej wydajne lub ładne, ale to, co działa w produkcji, jest w końcu najważniejsze).

Gdy ustawiasz wartość Lead w swoim LeadComment, mogę zasugerować, abyś ustawił tę wartość również w odpowiedziach na komentarz roota podczas tworzenia odpowiedzi (powinno to być łatwe w kodzie). W ten sposób możesz pobrać wszystkie komentarze dotyczące klienta w jednym zapytaniu (łącznie z odpowiedziami).

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

Oczywiście będziesz musiał uruchomić pakiet SQL, aby uzupełnić brakujące wartości kolumn, ale jest to jednorazowa sprawa, a gdy baza kodów zostanie załatana, nie będziesz musiał później nic uruchamiać. I to nie zmienia struktury bazy danych (tylko sposób zapełniania danych).

Następnie możesz wbudować w nodejs całe rzeczy (listy odpowiedzi). Aby uzyskać komentarz „root”, po prostu filtruj według komentarzy, które nie są odpowiedziami (które nie mają rodziców). Jeśli chcesz tylko główne komentarze z bazy danych, możesz nawet zmienić zapytanie tylko na te (z parentComment null w kolumnie 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);

Następnie możesz uzyskać odpowiedzi na rootComments i zbudować rekurencyjnie całą listę w węźle.

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));

Prawdopodobnie istnieją bardziej zoptymalizowane sposoby obliczania tych list, jest to dla mnie jeden z najprostszych, jakie można wykonać.

W zależności od liczby komentarzy może się spowolnić (możesz na przykład ograniczyć wyniki według znacznika czasu i liczby, aby było wystarczająco dobre?), Więc uważaj, nie ściągaj uniwersum komentarzy na temat potencjalnej szansy „Justin Bieber” wiele komentarzy ...

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.