我有以下型号:
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;
}
答案 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”上获取评论的全部内容导致很多评论的线索...