You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 04:55:57