TypeORM如何播种数据库

时间:2018-07-05 19:43:48

标签: typeorm

我正在使用typeorm ORM运行Node JS后端。

来自Entity Framework,通过几行代码(例如,

Database.SetInitializer(new DbInitializer()); 

DbInitializer类将包含所有播种信息。

是否有类似的方法将数据库植入TypeOrm? 如果没有,推荐的做法是什么?

1)使用数据插入语句创建新迁移? 2)创建一个实例化并保存实体的任务吗?

7 个答案:

答案 0 :(得分:11)

不幸的是,TypeORM(在发布此答案时)没有正式发布的解决方案。

但是我们可以使用一个不错的解决方法:

  1. ormconfig.js文件中创建另一个连接,并指定另一个 “迁移”文件夹-实际上是我们的种子
  2. 使用-c <connection name>生成并运行您的种子。就是这样!

样本 ormconfig.js

module.exports = [
  {
    ...,
    migrations: [
      'src/migrations/*.ts'
    ],
    cli: {
      migrationsDir: 'src/migrations',
    }
  },
  {
    name: 'seed',
    ...,
    migrations: [
      'src/seeds/*.ts'
    ],
    cli: {
      migrationsDir: 'src/seeds',
    }
  }
]

示例 package.json

{
  ...
  scripts: {
    "seed:generate": "ts-node typeorm migration:generate -c seed -n ",
    "seed:run": "ts-node typeorm migration:run -c seed",
    "seed:revert": "ts-node typeorm migration:revert -c seed",
  },
  ...
}

答案 1 :(得分:5)

我也希望看到这样的功能(和we're not alone),但是at the moment没有播种的正式功能。

由于缺乏这种内置功能,我认为下一个最好的办法是创建一个名为0-Seed的迁移脚本(因此它可以在您可能拥有的任何其他迁移脚本之前)并在其中填充种子数据

@bitwit创建了a snippet,可能对您很方便;该功能可以从yaml文件中读取数据,您可以将其合并到种子迁移脚本中。

然而,经过一些研究,我发现了另一种有趣的方法:将after_create事件绑定到表,并在侦听器中绑定initialize the data
我还没有实现,所以我不确定可以直接用TypeORM完成。

答案 2 :(得分:1)

同样对于 NestJS,您可以使用 nestjs-console 包来执行任务。这样你就可以访问实体、服务、存储库等。我比@B12Toaster 提出的中间件解决方案更喜欢这个,因为你不需要将它作为生产代码进行维护。

创建一个 seed 命令,如下所示,然后简单地:yarn console seed

这里有一个工作示例(在 CI 中运行):https://github.com/thisismydesign/nestjs-starter/tree/ee7abf6d481b1420708e87dea3cb99ca110cc168

沿着这些路线:

src/console.ts

import { BootstrapConsole } from 'nestjs-console';
import { AppModule } from 'src/server/app/app.module';

const bootstrap = new BootstrapConsole({
  module: AppModule,
  useDecorators: true,
});
bootstrap.init().then(async (app) => {
  try {
    await app.init();
    await bootstrap.boot();
    app.close();

    process.exit(0);
  } catch (e) {
    app.close();

    process.exit(1);
  }
});

src/console/seed.service.ts

import { Inject } from '@nestjs/common';
import { Console, Command } from 'nestjs-console';
import { UsersService } from 'src/users/users.service';

@Console()
export class SeedService {
  constructor(
    @Inject(UsersService) private usersService: UsersService,
  ) {}

  @Command({
    command: 'seed',
    description: 'Seed DB',
  })
  async seed(): Promise<void> {
    await this.seedUsers();
  }

  async seedUsers() {
    await this.usersService.create({ name: 'Joe' });
  }
}

package.json

{
  "scripts": {
    "console": "ts-node -r tsconfig-paths/register src/console.ts",

答案 3 :(得分:0)

对于那些将TypeORM与Nest.js一起使用的人,这是一个从代码内以编程方式执行播种的解决方案。

粗略的想法:

  • 我们创建了一个包含“播种中间件”的专用“播种模块”,该模块负责进行播种并确保在所有请求得到响应之前完成所有播种。
  • 对于到达的任何请求,播种中间件将对其进行拦截并将其推迟,直到确认播种已完成。
  • 如果数据库已播种,则“播种中间件”会将请求传递到下一个中​​间件。
  • 为了加快处理速度,“种子中间件”在内存中保留了“种子完成”标志作为状态,以避免在种子发生后进行任何进一步的数据库检查。

实施:

为此,首先创建一个模块,该模块注册一个侦听所有传入请求的中间件:

// file: src/seeding/SeedingModule.ts

@Module({})
export class SeedingModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(SeedingMiddleware)
      .forRoutes('*')
  }
}

现在创建中间件:

// file: src/seeding/SeedingMiddleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
import { EntityManager } from 'typeorm';
import { SeedingLogEntry } from './entities/SeedingLogEntry.entity';

@Injectable()
export class SeedingMiddleware implements NestMiddleware {

  // to avoid roundtrips to db we store the info about whether
  // the seeding has been completed as boolean flag in the middleware
  private isSeedingComplete: boolean;

  constructor(
    private readonly entityManager: EntityManager,
  ) {}

  async use(req: Request, res: Response, next: Function) {

    if (this.isSeedingComplete) {
      // seeding has already taken place,
      // we can short-circuit to the next middleware
      return next();
    }

    // for example you start with an initial seeding entry called 'initial-seeding'
    // on 2019-06-27. if 'initial-seeding' already exists in db, then this
    // part is skipped
    if (!await this.entityManager.findOne(SeedingLogEntry, { id: 'initial-seeding' })) {
      await this.entityManager.transaction(async transactionalEntityManager => {
        await transactionalEntityManager.save(User, initialUsers);
        await transactionalEntityManager.save(Role, initialRoles);
        // persist in db that 'initial-seeding' is complete
        await transactionalEntityManager.save(new SeedingLogEntry('initial-seeding'));

      });
    }

    // now a month later on 2019-07-25 you add another seeding
    // entry called 'another-seeding-round' since you want to initialize
    // entities that you just created a month later
    // since 'initial-seeding' already exists it is skipped but 'another-seeding-round'
    // will be executed now.
    if (!await this.entityManager.findOne(SeedingLogEntry, { id: 'another-seeding-round' })) {
      await this.entityManager.transaction(async transactionalEntityManager => {
        await transactionalEntityManager.save(MyNewEntity, initalSeedingForNewEntity);
        // persist in db that 'another-seeding-round' is complete
        await transactionalEntityManager.save(new SeedingLogEntry('another-seeding-round'));
      });
    }

    this.isSeedingComplete = true;
    next();
  }
}

最后,这是我们用来在数据库中记录某种类型的种子的实体。确保在您的TypeOrmModule.forRoot通话中将其注册为实体。

// file: src/seeding/entities/Seeding.entity.ts

import { Entity, PrimaryColumn, CreateDateColumn } from 'typeorm';

@Entity()
export class Seeding {

  @PrimaryColumn()
  public id: string;

  @CreateDateColumn()
  creationDate: Date;

  constructor(id?: string) {
    this.id = id;
  }
}

答案 4 :(得分:0)

看起来typeorm-seeding为此正在构建一个模块。 虽然使用初始迁移进行播种也可以工作,但是对于通过新种子DB才能通过测试的测试来说,它并不是很有用。一旦开始创建更多迁移,就无法删除,同步和运行迁移而不会出错。可以通过为单个迁移文件运行migration:run来解决此问题,但是使用CLI则无法完成。我的解决方案是一种轻量级脚本,该脚本通过typeorm连接访问QueryRunner对象:

// testSeed.ts

import { ConnectionOptions, createConnection, QueryRunner } from "typeorm";

import { config } from "../config";

import { DevSeed } from "./DevSeed";

createConnection(config.typeOrmConfig as ConnectionOptions).then(async connection => {
    let queryRunner = connection.createQueryRunner("master");

    // runs all seed SQL commands in this function.
    await DevSeed(queryRunner);

    await queryRunner.release();
    return connection.close();
});

然后运行node ./dist/path/to/testSeed.js

答案 5 :(得分:0)

所以这是我如何使用插入语句从sql文件中播种数据。 添加种子后,继承了我的整个迁移文件


import { MigrationInterface, QueryRunner } from 'typeorm';
import * as path from 'path';
import * as fs from 'fs';

let insertPermissionQueries = fs
    .readFileSync(path.resolve(__dirname, '../../scripts/sql/insert.sql'))
    .toString()
    .replace(/(\r\n|\n|\r)/gm, ' ') // remove newlines
    .replace(/\s+/g, ' '); // excess white space

export class init1591103087130 implements MigrationInterface {
    name = 'init1591103087130';

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(
            `CREATE TABLE "public"."RoleTemp" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" text, "created_on" TIMESTAMP DEFAULT now(), "is_active" boolean DEFAULT true, "role_type" text, "created_by" uuid NOT NULL, "status" text, "alias" text, "operation" text, "rejection_reason" text, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer, CONSTRAINT "UQ_835baad60041a3413f9ef95bc07" UNIQUE ("idx"), CONSTRAINT "PK_a76dd0012be252eefbdd4a2a589" PRIMARY KEY ("id"))`,
        );
        await queryRunner.query(
            `CREATE UNIQUE INDEX "RoleTemp_idx_key" ON "public"."RoleTemp" ("idx") `,
        );
        await queryRunner.query(
            `CREATE TABLE "public"."PermissionRoleTemp" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "permission_base_name" text, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer NOT NULL, "permission_id" integer NOT NULL, CONSTRAINT "PK_c1f2648a18ac911e096f08c187d" PRIMARY KEY ("id"))`,
        );
        await queryRunner.query(
            `CREATE TABLE "public"."Permission" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "base_name" text NOT NULL, "url" text NOT NULL, "method" text NOT NULL, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "permission_type" text, "alias" text NOT NULL, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "PK_28657fa560adca66b359c18b952" PRIMARY KEY ("id"))`,
        );
        await queryRunner.query(
            `CREATE TABLE "public"."PermissionRole" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "permission_base_name" text NOT NULL, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer NOT NULL, "permission_id" integer NOT NULL, CONSTRAINT "PK_b5e2271c229f65f17ee93677a0f" PRIMARY KEY ("id"))`,
        );
        await queryRunner.query(
            `CREATE TABLE "public"."UserRole" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer NOT NULL, "company_user_id" integer NOT NULL, CONSTRAINT "PK_431fc1ec3d46ac513ef3701604e" PRIMARY KEY ("id"))`,
        );
        await queryRunner.query(
            `CREATE TABLE "public"."UsersTemp" ("idx" uuid DEFAULT uuid_generate_v1(), "username" text, "first_name" text, "middle_name" text, "last_name" text, "password" text, "email" text, "address" text, "phone_number" text, "phone_ext" text, "company_idx" uuid, "is_superadmin" boolean NOT NULL DEFAULT false, "operation" text, "created_by" text, "status" text, "rejection_reason" text, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer, "user_id" integer, CONSTRAINT "PK_9d3fbcec3cc0b054324f93da038" PRIMARY KEY ("id"))`,
        );
        await queryRunner.query(
            `CREATE TABLE "public"."Role" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" text NOT NULL, "alias" text NOT NULL, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "role_type" text, "created_by" uuid NOT NULL, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "UQ_c9a53325a7642edb5f9bd44f5aa" UNIQUE ("idx"), CONSTRAINT "PK_422113329ddec949e76c7943c56" PRIMARY KEY ("id"))`,
        );
        await queryRunner.query(
            `CREATE UNIQUE INDEX "Role_idx_key" ON "public"."Role" ("idx") `,
        );
        await queryRunner.query(
            `CREATE TABLE "public"."Users" ("idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "username" text NOT NULL, "first_name" text NOT NULL, "middle_name" text, "last_name" text NOT NULL, "password" text NOT NULL, "email" text, "address" text, "phone_number" text, "phone_ext" text, "company_idx" uuid, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "is_superadmin" boolean NOT NULL DEFAULT false, "id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "role_id" integer NOT NULL, CONSTRAINT "PK_ac3c96e3c912cbda773b7c7edc9" PRIMARY KEY ("id"))`,
        );
        await queryRunner.query(
            `CREATE TABLE "public"."CompanyUser" ("id" SERIAL NOT NULL, "is_obsolete" boolean NOT NULL DEFAULT false, "modified_on" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "idx" uuid NOT NULL DEFAULT uuid_generate_v4(), "company_idx" uuid, "created_on" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "is_active" boolean NOT NULL DEFAULT true, "user_id" integer, CONSTRAINT "PK_4a915d69bf079a8e5dd10784cc3" PRIMARY KEY ("id"))`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."RoleTemp" ADD CONSTRAINT "FK_d304588d17c9349ca6e7ebee5d3" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."PermissionRoleTemp" ADD CONSTRAINT "FK_7e7cdde853500f56b3db43fc258" FOREIGN KEY ("role_id") REFERENCES "public"."RoleTemp"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."PermissionRoleTemp" ADD CONSTRAINT "FK_0068d3de1c59050561d35f17544" FOREIGN KEY ("permission_id") REFERENCES "public"."Permission"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."PermissionRole" ADD CONSTRAINT "FK_5b57492441a568bc7562fbbaa5b" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."PermissionRole" ADD CONSTRAINT "FK_1951a810af06342fcd4530ec61c" FOREIGN KEY ("permission_id") REFERENCES "public"."Permission"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."UserRole" ADD CONSTRAINT "FK_fb09d73b0dd011be81a272e1efa" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."UserRole" ADD CONSTRAINT "FK_b221977a41587e58d7c58e16db0" FOREIGN KEY ("company_user_id") REFERENCES "public"."CompanyUser"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."UsersTemp" ADD CONSTRAINT "FK_6d74dfaddaa94e1bba0c8c12a2f" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."UsersTemp" ADD CONSTRAINT "FK_e5b2930fe35042dab17945bb131" FOREIGN KEY ("user_id") REFERENCES "public"."Users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."Users" ADD CONSTRAINT "FK_34be125e29cee0e71d58456aed7" FOREIGN KEY ("role_id") REFERENCES "public"."Role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."CompanyUser" ADD CONSTRAINT "FK_1354e3e408b5ffdebe476a6fbd2" FOREIGN KEY ("user_id") REFERENCES "public"."Users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
        );
        await queryRunner.query(insertPermissionQueries);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(
            `ALTER TABLE "public"."CompanyUser" DROP CONSTRAINT "FK_1354e3e408b5ffdebe476a6fbd2"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."Users" DROP CONSTRAINT "FK_34be125e29cee0e71d58456aed7"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."UsersTemp" DROP CONSTRAINT "FK_e5b2930fe35042dab17945bb131"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."UsersTemp" DROP CONSTRAINT "FK_6d74dfaddaa94e1bba0c8c12a2f"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."UserRole" DROP CONSTRAINT "FK_b221977a41587e58d7c58e16db0"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."UserRole" DROP CONSTRAINT "FK_fb09d73b0dd011be81a272e1efa"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."PermissionRole" DROP CONSTRAINT "FK_1951a810af06342fcd4530ec61c"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."PermissionRole" DROP CONSTRAINT "FK_5b57492441a568bc7562fbbaa5b"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."PermissionRoleTemp" DROP CONSTRAINT "FK_0068d3de1c59050561d35f17544"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."PermissionRoleTemp" DROP CONSTRAINT "FK_7e7cdde853500f56b3db43fc258"`,
        );
        await queryRunner.query(
            `ALTER TABLE "public"."RoleTemp" DROP CONSTRAINT "FK_d304588d17c9349ca6e7ebee5d3"`,
        );
        await queryRunner.query(`DROP TABLE "public"."CompanyUser"`);
        await queryRunner.query(`DROP TABLE "public"."Users"`);
        await queryRunner.query(`DROP INDEX "public"."Role_idx_key"`);
        await queryRunner.query(`DROP TABLE "public"."Role"`);
        await queryRunner.query(`DROP TABLE "public"."UsersTemp"`);
        await queryRunner.query(`DROP TABLE "public"."UserRole"`);
        await queryRunner.query(`DROP TABLE "public"."PermissionRole"`);
        await queryRunner.query(`DROP TABLE "public"."Permission"`);
        await queryRunner.query(`DROP TABLE "public"."PermissionRoleTemp"`);
        await queryRunner.query(`DROP INDEX "public"."RoleTemp_idx_key"`);
        await queryRunner.query(`DROP TABLE "public"."RoleTemp"`);
    }
}



答案 6 :(得分:0)

在 Nest.js 中,这就是 B12Toaster 使用 OnApplicationBootstrap 的替代解决方案的样子。

src/seeding.service.ts

import { Injectable, Logger } from '@nestjs/common';
import { EntityManager } from 'typeorm';

import { UserEntity} from 'src/entities/user.entity';
import { RoleEntity } from 'src/entities/role.entity';

import { userSeeds } from 'src/seeds/user.seeds';
import { roleSeeds } from 'src/seeds/role.seeds';

@Injectable()
export class SeedingService {
  constructor(
    private readonly entityManager: EntityManager,
  ) {}

  async seed(): Promise<void> {

    // Replace with your own seeds
    await Promise.all([
      this.entityManager.save(UserEntity, userSeeds),
      this.entityManager.save(RoleEntity, roleSeeds),
    ]);

  }
}

src/app.module.ts

import { Module, OnApplicationBootstrap } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm';
import { getConnectionOptions } from 'typeorm';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      useFactory: async () =>
        Object.assign(await getConnectionOptions(), {
          autoLoadEntities: true,
        }),
    }),
    TypeOrmModule.forFeature([
      CompanyOrmEntity,
      ProductOrmEntity,
    ]),
  ],
  providers: [
    SeedingService,
    ...
  ],
  ...
})
export class AppModule implements OnApplicationBootstrap {
  constructor(
    private readonly seedingService: SeedingService,
  ) {}

  async onApplicationBootstrap(): Promise<void> {
    await this.seedingService.seed();
  }
}