我正在使用typeorm ORM运行我的Node JS后端.
来自Entity Framework,很容易用几行代表db
Database.SetInitializer(new DbInitializer());
Run Code Online (Sandbox Code Playgroud)
DbInitializer类将包含所有种子信息.
是否有类似的方法在TypeOrm中播种数据库?如果没有,建议的方法是什么?
1)使用数据插入语句创建新的迁移?2)创建一个实例并保存实体的任务?
ole*_*hko 19
不幸的是,没有正式发布的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',
}
}
]
Run Code Online (Sandbox Code Playgroud)
示例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",
},
...
}
Run Code Online (Sandbox Code Playgroud)
B12*_*ter 10
对于那些在 Nest.js 中使用TypeORM 的人,这里有一个解决方案,可以从代码中以编程方式执行播种。
粗略的想法:
执行:
为此,首先创建一个模块来注册一个侦听所有传入请求的中间件:
// file: src/seeding/SeedingModule.ts
@Module({})
export class SeedingModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(SeedingMiddleware)
.forRoutes('*')
}
}
Run Code Online (Sandbox Code Playgroud)
现在创建中间件:
// 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
// we use a promise to avoid concurrency cases. Concurrency cases may
// occur if other requests also trigger a seeding while it has already
// been started by the first request. The promise can be used by other
// requests to wait for the seeding to finish.
private isSeedingComplete: Promise<boolean>;
constructor(
private readonly entityManager: EntityManager,
) {}
async use(req: Request, res: Response, next: Function) {
if (await this.isSeedingComplete) {
// seeding has already taken place,
// we can short-circuit to the next middleware
return next();
}
this.isSeedingComplete = (async () => {
// 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'));
});
}
return true;
})();
await this.isSeedingComplete;
next();
}
}
Run Code Online (Sandbox Code Playgroud)
最后,这里是我们用来在我们的数据库中记录某种类型的种子已经发生的实体。确保在您的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;
}
}
Run Code Online (Sandbox Code Playgroud)
使用生命周期事件的替代播种解决方案:
使用 Nest.js,您还可以实现 OnApplicationBootstrap接口(请参阅生命周期事件),而不是使用基于中间件的解决方案来处理您的种子。该onApplicationBootstrap方法将“在应用程序完全启动并引导后调用”。但是,与中间件解决方案相比,这种方法不允许您在多租户环境中为 db 播种,在该环境中,不同租户的 db-schema 将在运行时创建,并且需要在运行时进行多次播种创建后的不同租户。
我很想看到这样的功能,以及(和我们并不孤单),但在 那一刻,有播种没有官方的功能。
由于缺乏这种内置功能,我认为下一个最好的方法是创建一个名为的迁移脚本0-Seed(这样它就可以在您可能拥有的所有其他迁移脚本之前)并在其中填充种子数据。
@bitwit创建了一个片段,可能对您有用;此功能可从yaml文件读取数据,您可以将其合并到种子迁移脚本中。
但是,经过一些研究,我发现了另一种有趣的方法:将after_create事件绑定到表,并在侦听器中初始化数据。
我还没有实现它,所以我不确定可以直接用TypeORM完成它。
在 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),
]);
}
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)