NestJS nodejs загружает вложенные комментарии в одном запросе с отношениями?

10

У меня есть следующие модели:

User, Customer,Comment

Пользователь может комментировать Customer, пользователь может отвечать на комментарии другого пользователя, рекурсивно без ограничений.

Я сделал это, но он ограничен одним ответом, и я хочу, чтобы все ответы были НЕЗАКОННЫМИ:

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

Однако ответ, который я получаю, находится только на одном уровне:

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

Как я могу сделать запрос, чтобы вложить их всех в typeorm?

Определение объекта (обратите внимание, что клиент переименован в 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
Можете ли вы добавить определения вашей сущности?
Зенбени
@zenbeni Добавил спасибо
Бен Бери

Ответы:

7

Вы в основном используете Adjacency list Tree.

Список смежности - это простая модель с самообращением. Преимущество этого подхода заключается в простоте, НО недостаток в том, что вы не можете обрабатывать глубокие деревья с этим.

Есть рекурсивный способ сделать это с помощью списка смежности, но он не работает с MySQL.

Решение состоит в том, чтобы использовать другой тип дерева. Другие возможные деревья:

  • Вложенный набор : очень эффективен для чтения, но плохо для записи. Вы не можете иметь несколько корней во вложенном множестве.
  • Материализованный путь : (также называемый перечислением пути) прост и эффективен.
  • Таблица закрытия : хранит отношения между родителем и дочерним элементом в отдельной таблице. Эффективен как для чтения, так и для записи (обновление или удаление родителя компонента еще не реализовано)
@Entity()
@Tree("nested-set") // or @Tree("materialized-path") or @Tree("closure-table")
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @TreeChildren()
    children: Category[];

    @TreeParent()
    parent: Category;
}

Для загрузки дерева используйте:

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

После того, как вы получите древовидный репозиторий, вы можете использовать следующие функции: findTrees(), findRoots(), findDescendants(), findDescendantsTree()и другие. Смотрите документацию для более.

Узнайте больше о различных типах деревьев: модели для иерархических данных

Габриэль Василе
источник
1

Как сказал Габриэль, другим моделям данных лучше делать то, что вы хотите, с точки зрения производительности. Тем не менее, если вы не можете изменить дизайн базы данных, вы можете использовать альтернативы (которые менее производительны или симпатичны, но в конечном итоге все, что работает в производстве) имеет значение.

Поскольку вы устанавливаете значение Lead в своем LeadComment, я могу предложить вам установить это значение также для ответов на корневой комментарий о создании ответов (должно быть легко в коде). Таким образом, вы можете получить все комментарии вашего клиента в одном запросе (включая ответы).

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

Конечно, вам нужно будет запустить пакет SQL, чтобы заполнить пропущенные значения столбцов, но это происходит один раз, и как только ваша кодовая база будет исправлена, вам не нужно будет ничего запускать впоследствии. И это не меняет структуру вашей базы данных (только способ заполнения данных).

Затем вы можете встроить в nodejs весь материал (списки ответов). Чтобы получить «корневой» комментарий, просто отфильтруйте комментарии, которые не являются ответами (у которых нет родителей). Если вам просто нужны корневые комментарии из базы данных, вы можете даже изменить запрос только на эти (с нулем parentComment в столбце 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);

Затем вы можете получить ответы на rootComments и рекурсивно построить весь список в узле.

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

Вероятно, есть более оптимизированные способы вычисления этих списков, для меня это один из самых простых, которые можно сделать.

В зависимости от количества комментариев оно может стать медленным (например, вы можете ограничить результаты по отметке времени и количеству, чтобы оно было достаточно хорошим?), Так что будьте осторожны, не выбирайте вселенную комментариев по отведению "Джастин Бибер", которое получает много комментариев ...

zenbeni
источник