NestJS nodejs memuat komentar bersarang dalam satu kueri dengan relasi?

10

Saya memiliki model berikut:

User, Customer,Comment

Pengguna dapat mengomentari Customer, pengguna dapat membalas komentar pengguna lain, tanpa batas secara rekursif.

Saya telah melakukan ini tetapi terbatas hanya pada satu balasan, dan saya ingin mendapatkan semua balasan TERTULIS:

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

Namun respons yang saya dapatkan hanya bersarang di satu tingkat:

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

Bagaimana saya bisa membuat kueri untuk menyarangkan semuanya dalam formatorm?

Definisi entitas (perhatikan pelanggan diubah namanya menjadi 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;
}
Ben Beri
sumber
1
Bisakah Anda menambahkan definisi entitas Anda?
zenbeni
@zenbeni Ditambahkan terima kasih
Ben Beri

Jawaban:

7

Anda pada dasarnya menggunakan Adjacency list Tree.

Daftar adjacency adalah model sederhana dengan referensi diri. Manfaat dari pendekatan ini adalah kesederhanaan, TETAPI kelemahannya adalah Anda tidak bisa menangani pohon yang dalam dengan itu.

Ada cara rekursif untuk melakukannya dengan daftar Adjacency, tetapi tidak bekerja dengan MySQL.

Solusinya adalah dengan menggunakan jenis pohon lain. Pohon lain yang mungkin adalah:

  • Nested set : Sangat efisien untuk membaca, tetapi buruk untuk menulis. Anda tidak dapat memiliki beberapa root di set bersarang.
  • Jalur Terwujud : (juga disebut Jalur Pencacahan) sederhana dan efektif.
  • Tabel penutupan : menyimpan hubungan antara orang tua dan anak dalam tabel terpisah. Efisien dalam membaca dan menulis (Memperbarui atau menghapus induk komponen belum dilaksanakan)
@Entity()
@Tree("nested-set") // or @Tree("materialized-path") or @Tree("closure-table")
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @TreeChildren()
    children: Category[];

    @TreeParent()
    parent: Category;
}

Untuk memuat pohon gunakan:

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

Setelah Anda mendapatkan repositori pohon, Anda dapat menggunakan fungsi berikutnya: findTrees(), findRoots(), findDescendants(), findDescendantsTree()dan yang lainnya. Lihat dokumentasi untuk lebih lanjut.

Pelajari lebih lanjut tentang berbagai jenis pohon: Model untuk data hierarkis

Gabriel Vasile
sumber
1

Seperti yang dikatakan Gabriel, model data lain lebih baik untuk melakukan apa yang Anda inginkan dari segi kinerja. Namun jika Anda tidak dapat mengubah desain basis data, Anda dapat menggunakan alternatif (yang kurang berkinerja atau cantik, tetapi yang berfungsi di produksi adalah yang paling penting pada akhirnya).

Saat Anda menetapkan nilai Timbal dalam LeadComment Anda, saya dapat menyarankan agar Anda menetapkan nilai ini juga pada balasan pada komentar root pada penciptaan balasan (harus mudah dalam kode). Dengan cara ini Anda dapat mengambil semua komentar pada pelanggan Anda dalam satu permintaan (termasuk balasan).

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

Tentu saja, Anda harus menjalankan batch SQL untuk mengisi nilai-nilai kolom yang hilang, tetapi itu adalah hal satu kali, dan sekali basis kode Anda ditambal juga Anda tidak perlu menjalankan apa pun setelahnya. Dan itu tidak mengubah struktur basis data Anda (seperti cara data terisi).

Kemudian Anda dapat membangun seluruh simpul (daftar balasan). Untuk mendapatkan komentar "root", cukup filter berdasarkan komentar yang bukan balasan (yang tidak memiliki orang tua). Jika Anda hanya ingin komentar root dari database, Anda bahkan dapat mengubah kueri hanya untuk yang ini (dengan parentComment null dalam kolom 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);

Kemudian Anda bisa mendapatkan balasan pada rootComments dan membangun seluruh daftar secara rekursif dalam simpul.

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

Mungkin ada cara yang lebih dioptimalkan untuk menghitung daftar ini, ini bagi saya salah satu yang paling sederhana yang dapat dibuat.

Bergantung pada jumlah komentar itu bisa lambat (Anda dapat membatasi hasil dengan cap waktu dan nomor misalnya sehingga itu harus cukup baik?) Jadi berhati-hatilah, jangan mengambil semesta komentar pada Lead "Justin Bieber" yang mendapatkan banyak komentar ...

zenbeni
sumber