Node.js+TypeORM+PostgreSQL下如何正确为一对多/多对一关系实体播种?
TypeORM多对一关联绑定失效问题排查
尽管TypeORM官方文档已给出多对一/一对多关联的实现方案,但实际使用中仍存在陷阱。我遇到的情况是:代码无报错,但关联绑定未正确执行,查询博客文章时其关联分类为空。
实体定义
我的场景中BlogPost实体依赖BlogPostCategory,实体代码如下:
BlogPost 实体
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, Relation } from "typeorm"; import { isUndefined } from "@yamato-daiwa/es-extensions"; @Entity() export default class BlogPost { @PrimaryGeneratedColumn("uuid") public readonly ID!: BlogPost.ID; @Column({ type: "varchar", nullable: false }) public readonly heading!: string; @Column({ type: "text", nullable: false }) public readonly HTML!: string; /* 关于`Relation`类型的说明 */ @ManyToOne( (): typeof BlogPostCategory => BlogPostCategory, (blogPostCategory: BlogPostCategory): ReadonlyArray<BlogPost> => blogPostCategory.blogPosts ) public readonly category!: Relation<BlogPostCategory>; @Column({ type: "varchar", nullable: false }) public readonly metaDescription!: string; @Column({ type: "timestamp with time zone", nullable: false }) public readonly publishingDateTime__ISO8601!: string; public constructor( properties?: Readonly<Omit<BlogPost, "ID">> ) { if (isUndefined(properties)) { return; } this.heading = properties.heading; this.HTML = properties.HTML; this.category = properties.category; this.metaDescription = properties.metaDescription; this.publishingDateTime__ISO8601 = properties.publishingDateTime__ISO8601; } }
BlogPostCategory 实体
@Entity() @Unique([ "text" ]) export default class BlogPostCategory { @PrimaryGeneratedColumn("uuid") public readonly ID!: BlogPostCategory.ID; @Column({ type: "varchar", nullable: false }) public text!: string; @OneToMany( (): typeof BlogPost => BlogPost, (blogPost: BlogPost): BlogPostCategory => blogPost.category ) public blogPosts!: Array<BlogPost>; public constructor(properties?: Readonly<Omit<BlogPostCategory, "ID">>) { if (isUndefined(properties)) { return; } this.text = properties.text; } }
数据播种脚本
由于未找到兼容最新TypeORM的播种库,我使用了可通过node或ts-node运行的自定义脚本。逻辑上先创建BlogPostCategory实例,再创建关联的BlogPost:
import { getArrayElementSatisfiesThePredicateIfSuchElementIsExactlyOne } from "@yamato-daiwa/es-extensions"; import { DataSource } from "typeorm"; const dataSource: DataSource = new DataSource(/* ... */); await dataSource.initialize(); // ... const blogPostCategories: ReadonlyArray<BlogPostCategory> = await dataSource.manager.save( Object. values(initialBlogPostsCategories). map( (blogPostCategory): BlogPostCategory => new BlogPostCategory({ text: blogPostCategory.text }) ) ); console.log("=== CHECKPOINT 1 =========================================================================") console.log(blogPostCategories); const blogPosts: Array<BlogPost> = await dataSource.manager.save( initialBlogPosts.map( (blogPost: Readonly<Omit<BlogPost, "ID">>): BlogPost => new BlogPost({ ...blogPost, category: getArrayElementSatisfiesThePredicateIfSuchElementIsExactlyOne( blogPostCategories, (blogPostCategory: BlogPostCategory): boolean => blogPostCategory.text === blogPost.category.text, { mustThrowErrorIfElementNotFoundOrMatchesAreMultiple: true } ) }) ) ); console.log("=== CHECKPOINT 2 =========================================================================") console.log(blogPosts)
诊断输出
CHECKPOINT 1 输出(分类创建完成)
[ BlogPostCategory { ID: '1c23cf90-f112-442f-9c12-240d6749d26d', text: 'Category 1', blogPosts: undefined }, BlogPostCategory { ID: 'f433eeb3-22e2-4218-81ed-d3ff21fe4936', text: 'Category 2', blogPosts: undefined } ]
此时blogPosts为undefined是因为博客文章尚未创建,属于正常现象。
CHECKPOINT 2 输出(文章创建完成)
[ BlogPost { ID: 'e0403e10-4659-4363-88e7-8e7529823b31', heading: 'Post 1', HTML: "<h1>Post 1</h1>", category: BlogPostCategory { ID: 'ae525873-91dc-4b84-92cc-ea31dcd326cb', text: 'Category 1', blogPosts: undefined }, metaDescription: 'Post 1', publishingDateTime__ISO8601: '2024-01-25T07:23:59.840Z' }, BlogPost { ID: '5f7dd7de-82c6-4119-8653-288d7e20a79f', heading: 'Post 2', HTML: "<h2>Post 2</h2>", category: BlogPostCategory { ID: '1c23cf90-f112-442f-9c12-240d6749d26d', text: 'Category 2', blogPosts: undefined }, metaDescription: 'Post 2', publishingDateTime__ISO8601: '2023-12-31T15:00:00.000Z' } ]
这里存在递归结构:BlogPost包含BlogPostCategory,而BlogPostCategory又包含BlogPost数组,暂时不确定是否正常,但查询数据库时博客文章的关联分类字段为空,问题根源应该在此。
数据库实际情况


注:实体中使用构造器符合TypeORM文档要求,文档说明只要构造器参数为可选即可使用。
内容的提问来源于stack exchange,提问作者Takeshi Tokugawa YD
相关产品推荐
相关产品推荐

