NestJS nodejs在具有关系的一个查询中加载嵌套注释?

时间:2019-12-03 15:35:48

标签: node.js typeorm

我有以下型号:

UserCustomerComment

用户可以在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;
}

2 个答案:

答案 0 :(得分:6)

您基本上是在使用Adjacency list Tree

邻接表是带有自引用的简单模型。这种方法的好处是简单,但是缺点是您不能用它来处理深树。

有一种使用Adjacency列表进行递归的方法,但不适用于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()等。有关更多信息,请参见documentation

进一步了解不同类型的树:Models for hierarchical data

答案 1 :(得分:1)

正如Gabriel所说,其他数据模型更好地执行您想要的性能明智的选择。仍然,如果您不能更改数据库设计,则可以使用替代方法(性能较差或不错,但最终有效的是生产中起作用的所有内容)。

当您在LeadComment中设置Lead值时,我建议您在回复创建的根注释上的回复中也设置此值(在代码中应该很容易)。这样,您可以在一个查询中获取有关客户的所有评论(包括答复)。

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

当然,您将必须运行SQL批处理来填充缺少的列值,但这是一次性的事情,并且一旦对代码库进行了修补,您就不必再执行任何操作。而且,它不会改变数据库的结构(只是填充数据的方式)。

然后,您可以在nodejs中构建全部内容(答复列表)。要获得“根”评论,只需按未回复(没有父母)的评论进行过滤。如果您只想从数据库中获取根注释,甚至可以将查询更改为仅这些(在SQL列中parentComment为null)。

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的答复,并在node中以递归方式构建整个列表。

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

可能有更多优化的方法来计算这些列表,这对我来说是最简单的方法之一。

根据评论的数量,它可能变慢(例如,您可以通过时间戳和数字来限制结果,以便它应该足够好?)所以要当心,不要在“ Justin Bieber”上获取评论的全部内容导致很多评论的线索...