NestJS+TypeORM更新含子实体列表的Chat实体报错求助
解决TypeORM更新Chat实体时的
Error: Cannot query across one-to-many for property messages错误 我在更新包含新消息的Chat实体时触发报错:Error: Cannot query across one-to-many for property messages,尝试拼接现有消息与新消息后调用chatRepository.update方法,结果出现了这个问题。相关代码如下:
chat.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany, OneToOne, } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { ChatMessage } from '../chatMessage/chatMessage.entity'; import { User } from 'src/auth/user.entity'; import { Session } from 'src/sessions/session.entity'; @Entity() export class Chat { @PrimaryGeneratedColumn() id: number; @ManyToOne(() => User, (user) => user.chats) @ApiProperty({ type: () => User, }) user: User; @ApiProperty({ example: '2024-02-09T22:15:11.293Z', description: 'session_id', }) @Column() session_id: string; @ApiProperty({ type: () => Session, example: '', description: '', }) @OneToOne(() => Session) @JoinColumn() session: Session; @ApiProperty({ type: () => ChatMessage, example: '', description: 'chat message', }) @OneToMany(() => ChatMessage, (chatMessage) => chatMessage.chat) @Column('jsonb', { default: [] }) @JoinColumn() messages: ChatMessage[]; }
chatMessage.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany, JoinColumn, } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { Chat } from '../chat/chat.entity'; export class DataDto { // Not sure why type is in both objects but that's how langchain structures it. @ApiProperty({ example: 'human', description: 'type' }) @Column() type: string; @ApiProperty({ example: 'Mr. Smith is an investment banker at a large bank and primarily works with water risk data. What product or products should we recommend?', description: 'content', }) @Column() content: string; @Column('jsonb', { nullable: true }) additional_kwargs: any; // TODO: Figure out how to define this type @ApiProperty({ example: false, description: 'Bool for example' }) @Column() example: boolean; } @Entity() export class ChatMessage { @PrimaryGeneratedColumn() id: number; @ApiProperty({ type: () => Chat, example: '', description: '', }) @OneToMany(() => Chat, (chat: Chat) => chat.messages) @JoinColumn() chat: Chat; @ApiProperty({ example: 'human', description: 'type', nullable: true, enum: ['human', 'bot'], }) @Column({ nullable: true, enum: ['human', 'bot'] }) type: string; @Column('jsonb', { nullable: true }) data: DataDto; }
chat.service.ts
@Injectable() export class ChatService { constructor( @InjectRepository(Chat) private chatRepository: Repository<Chat>, ) {} async patch(id: number, Chat: Partial<Chat>): Promise<any> { const chat = await this.chatRepository.findOne({ where: { id } }); if (!chat) { throw new Error('Chat not found'); } console.debug('ChatService.patch chat:', chat); console.debug('ChatService.patch Chat:', Chat); const concatenatedMessages = chat.messages.concat(Chat.messages); console.debug( 'ChatService.patch concatenatedMessages:', concatenatedMessages, ); return await this.chatRepository.update(id, { messages: concatenatedMessages, }); }
问题根源
- 实体关系定义错误:
- Chat实体的
messages字段同时用了@OneToMany(关系型关联)和@Column('jsonb')(JSON列存储),两者冲突,TypeORM无法同时处理。 - ChatMessage实体的
chat字段用了@OneToMany,实际应该是@ManyToOne——一条Chat对应多条ChatMessage,反过来一条ChatMessage只属于一个Chat。
- Chat实体的
- 更新逻辑错误:
- 查询Chat时未预加载
messages关联,直接访问chat.messages会触发懒加载,但update方法无法直接处理关联对象数组的拼接。
- 查询Chat时未预加载
修复方案
1. 修正实体关系
修改chat.entity.ts
去掉@Column('jsonb', { default: [] })和@JoinColumn()(@OneToMany不需要该装饰器,外键在ChatMessage侧),添加cascade: true让TypeORM自动处理子实体的保存:
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany, OneToOne, } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { ChatMessage } from '../chatMessage/chatMessage.entity'; import { User } from 'src/auth/user.entity'; import { Session } from 'src/sessions/session.entity'; @Entity() export class Chat { @PrimaryGeneratedColumn() id: number; @ManyToOne(() => User, (user) => user.chats) @ApiProperty({ type: () => User, }) user: User; @ApiProperty({ example: '2024-02-09T22:15:11.293Z', description: 'session_id', }) @Column() session_id: string; @ApiProperty({ type: () => Session, example: '', description: '', }) @OneToOne(() => Session) @JoinColumn() session: Session; @ApiProperty({ type: () => ChatMessage, example: '', description: 'chat message', }) @OneToMany(() => ChatMessage, (chatMessage) => chatMessage.chat, { cascade: true }) messages: ChatMessage[]; }
修改chatMessage.entity.ts
把@OneToMany改成@ManyToOne,修正关联方向:
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { Chat } from '../chat/chat.entity'; export class DataDto { // Not sure why type is in both objects but that's how langchain structures it. @ApiProperty({ example: 'human', description: 'type' }) @Column() type: string; @ApiProperty({ example: 'Mr. Smith is an investment banker at a large bank and primarily works with water risk data. What product or products should we recommend?', description: 'content', }) @Column() content: string; @Column('jsonb', { nullable: true }) additional_kwargs: any; // TODO: Figure out how to define this type @ApiProperty({ example: false, description: 'Bool for example' }) @Column() example: boolean; } @Entity() export class ChatMessage { @PrimaryGeneratedColumn() id: number; @ApiProperty({ type: () => Chat, example: '', description: '', }) @ManyToOne(() => Chat, (chat) => chat.messages) chat: Chat; @ApiProperty({ example: 'human', description: 'type', nullable: true, enum: ['human', 'bot'], }) @Column({ nullable: true, enum: ['human', 'bot'] }) type: string; @Column('jsonb', { nullable: true }) data: DataDto; }
2. 调整ChatService的更新逻辑
使用TypeORM的关联操作,先预加载messages,再添加新消息并保存:
@Injectable() export class ChatService { constructor( @InjectRepository(Chat) private chatRepository: Repository<Chat>, ) {} async patch(id: number, newChatData: Partial<Chat>): Promise<Chat> { // 预加载messages关联 const chat = await this.chatRepository.findOne({ where: { id }, relations: ['messages'] }); if (!chat) { throw new Error('Chat not found'); } // 处理传入的新消息 if (newChatData.messages?.length) { // 为每条新消息绑定当前Chat newChatData.messages.forEach(msg => { msg.chat = chat; }); // 合并消息并保存Chat,cascade会自动保存新的ChatMessage chat.messages.push(...newChatData.messages); return this.chatRepository.save(chat); } return chat; } }
额外说明
- 如果你更倾向于用JSON列存储消息而不是关系型关联,那直接去掉
@OneToMany装饰器,只保留@Column('jsonb', { default: [] }),此时更新逻辑可以直接拼接数组后调用update,但这样会失去数据库层面的关联约束和查询灵活性。 - 关联关系中,
cascade: true会让TypeORM在保存Chat时自动保存关联的ChatMessage,无需手动调用ChatMessage的save方法。
内容的提问来源于stack exchange,提问作者CreativeUsername
相关产品推荐
相关产品推荐

