我正在使用typeorm ORM运行Node JS后端。
来自Entity Framework,通过几行代码(例如,
Database.SetInitializer(new DbInitializer());
DbInitializer类将包含所有播种信息。
是否有类似的方法将数据库植入TypeOrm? 如果没有,推荐的做法是什么?
1)使用数据插入语句创建新迁移? 2)创建一个实例化并保存实体的任务吗?
答案 0 :(得分:11)
不幸的是,TypeORM(在发布此答案时)没有正式发布的解决方案。
但是我们可以使用一个不错的解决方法:
ormconfig.js
文件中创建另一个连接,并指定另一个
“迁移”文件夹-实际上是我们的种子 -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();
}
}