Skip to main content
Version: 6.4

播种

初始化应用或测试时,为数据库创建示例数据可能非常耗时。解决方案是使用种子。为你的实体创建工厂并在种子脚本中使用它们或组合多个种子脚本。

¥When initializing your application or testing it can be exhausting to create sample data for your database. The solution is to use seeding. Create factories for your entities and use them in the seed script or combine multiple seed scripts.

信息

要使用种子,你需要先安装 @mikro-orm/seeder,并在 ORM 配置中注册 SeedManager 扩展。

¥To use seeder, you need to first install @mikro-orm/seeder, and register the SeedManager extension in your ORM config.

mikro-orm.config.ts
import { SeedManager } from '@mikro-orm/seeder';

export default defineConfig({
// ...
extensions: [SeedManager],
})

配置

¥Configuration

seeder.pathseeder.pathTs 在实体发现中的工作方式与 entitiesentitiesTs 相同。

¥seeder.path and seeder.pathTs works the same way as entities and entitiesTs in entity discovery.

种子有一些默认设置,可以通过 MikroORM 配置轻松更改。在下面你可以找到配置选项及其默认值。

¥The seeder has a few default settings that can be changed easily through the MikroORM config. Underneath you find the configuration options with their defaults.

MikroORM.init({
seeder: {
path: './seeders', // path to the folder with seeders
pathTs: undefined, // path to the folder with TS seeders (if used, we should put path to compiled files in `path`)
defaultSeeder: 'DatabaseSeeder', // default seeder class name
glob: '!(*.d).{js,ts}', // how to match seeder files (all .js and .ts files, but not .d.ts)
emit: 'ts', // seeder generation mode
fileName: (className: string) => className, // seeder file naming convention
},
});

我们还可以使用 环境变量 覆盖这些默认值:

¥We can also override these default using the environment variables:

  • MIKRO_ORM_SEEDER_PATH

  • MIKRO_ORM_SEEDER_PATH_TS

  • MIKRO_ORM_SEEDER_EMIT

  • MIKRO_ORM_SEEDER_GLOB

  • MIKRO_ORM_SEEDER_DEFAULT_SEEDER

种子

¥Seeders

种子类包含一个方法 run。当你使用命令 npx mikro-orm seeder:run 时,将调用此方法。在 run 方法中,你可以定义如何以及要将哪些数据插入数据库。你可以使用 EntityManager 创建实体,也可以使用 工厂

¥A seeder class contains one method run. This method is called when you use the command npx mikro-orm seeder:run. In the run method you define how and what data you want to insert into the database. You can create entities using the EntityManager or you can use Factories.

你可以使用以下 CLI 命令创建自己的种子类:

¥You can create your own seeder classes using the following CLI command:

npx mikro-orm seeder:create DatabaseSeeder  # generates the class DatabaseSeeder
npx mikro-orm seeder:create test # generates the class TestSeeder
npx mikro-orm seeder:create project-names # generates the class ProjectNamesSeeder

这将创建一个新的种子类。默认情况下,它将在 ./seeders/ 目录中生成。你可以使用密钥 seeder.path 或使用 环境变量 MIKRO_ORM_SEEDER_PATH 在配置中配置目录。你可以使用名称、类名或带连字符的名称调用 seeder:create 命令。

¥This creates a new seeder class. By default, it will be generated in the ./seeders/ directory. You can configure the directory in the config with the key seeder.path or using the environment variable MIKRO_ORM_SEEDER_PATH. You are allowed to call the seeder:create command with a name, class name or hyphenated name.

作为示例,我们将查看一个非常基本的种子。

¥As an example we will look at a very basic seeder.

请注意,种子中可用的 EntityManager 将启用 persistOnCreate(即使你在 ORM 配置中明确禁用它),因此调用 em.create() 将自动在创建的实体上调用 em.persist()。如果我们改用实体构造函数,则需要明确调用 em.persist()

¥Note that the EntityManager available in seeders will have persistOnCreate enabled (even if you explicitly disable it in the ORM config), hence calling em.create() will automatically call em.persist() on the created entity. If we use entity constructor instead, we need to call em.persist() explicitly.

./database/seeder/database.seeder.ts
import { EntityManager } from '@mikro-orm/core';
import { Seeder } from '@mikro-orm/seeder';
import { Author } from './author'

export class DatabaseSeeder extends Seeder {

async run(em: EntityManager): Promise<void> {
// will get persisted automatically
const author = em.create(Author, {
name: 'John Snow',
email: 'snow@wall.st'
});

// but if we would do `const author = new Author()` instead,
// we would need to call `em.persist(author)` explicitly.
}
}

从命令行或以编程方式运行种子程序将在 run 方法完成后自动调用 flushclear

¥Running a seeder from the command line or programmatically will automatically call flush and clear after the run method has completed.

使用实体工厂

¥Using entity factories

你也可以使用 实体工厂,而不必为每个实体指定所有属性。这些可用于生成大量数据库记录。请阅读 关于如何定义工厂的文档 以了解如何定义你的工厂。

¥Instead of specifying all the attributes for every entity, you can also use entity factories. These can be used to generate large amounts of database records. Please read the documentation on how to define factories to learn how to define your factories.

作为示例,我们将生成 10 位作者。

¥As an example we will generate 10 authors.

import { EntityManager } from '@mikro-orm/core';
import { Seeder } from '@mikro-orm/seeder';
import { AuthorFactory } from '../factories/author.factory'

export class DatabaseSeeder extends Seeder {

async run(em: EntityManager): Promise<void> {
new AuthorFactory(em).make(10);
}
}

调用其他种子程序

¥Calling additional seeders

run 方法内部,你可以指定其他种子类。你可以使用 call 方法将数据库种子分成多个文件,以防止种子文件变得太大。call 方法需要 em 和种子类数组。

¥Inside the run method you can specify other seeder classes. You can use the call method to break up the database seeder into multiple files to prevent a seeder file from becoming too large. The call method requires an em and an array of seeder classes.

import { EntityManager } from '@mikro-orm/core';
import { Seeder } from '@mikro-orm/seeder';
import { AuthorSeeder, BookSeeder } from '../seeders'

export class DatabaseSeeder extends Seeder {

run(em: EntityManager): Promise<void> {
return this.call(em, [
AuthorSeeder,
BookSeeder,
]);
}
}

共享上下文

¥Shared context

通常,我们可能希望生成引用其他种子创建的其他实体的实体。为此,我们可以使用第二个参数或 run 方法中提供的共享上下文对象。使用 this.call() 时会自动创建它,并传递给每个种子的 run 方法。让我们看看上一个示例中的 AuthorSeederBookSeeder 是什么样子:

¥Often we might want to generate entities that are referencing other entities, created by other seeders. For that we can use the shared context object provided in the second parameter or run method. It is automatically created when using this.call() and passed down to each seeder's run method. Let's see how the AuthorSeeder and BookSeeder from previous example could look like:

export class AuthorSeeder extends Seeder {
async run(em: EntityManager, context: Dictionary): Promise<void> {
// save the entity to the context
context.author = em.create(Author, {
name: '...',
email: '...',
});
}
}
export class BookSeeder extends Seeder {
async run(em: EntityManager, context: Dictionary): Promise<void> {
em.create(Book, {
title: '...',
author: context.author, // use the entity from context
});
}
}

实体工厂

¥Entity factories

在测试时,你可以在开始测试之前将实体插入数据库中。你也可以使用 Factory 来使用实体工厂为实体定义一组默认属性,而不必手动指定每个实体的每个属性。

¥When testing you may insert entities in the database before starting a test. Instead of specifying every attribute of every entity by hand, you could also use a Factory to define a set of default attributes for an entity using entity factories.

让我们看一个 作者实体 的示例工厂。

¥Let's have a look at an example factory for an Author entity.

import { Factory } from '@mikro-orm/seeder';
import { faker } from '@faker-js/faker';
import { Author } from './entities/author.entity';

export class AuthorFactory extends Factory<Author> {
model = Author;

definition(): Partial<Author> {
return {
name: faker.person.findName(),
email: faker.internet.email(),
age: faker.random.number(18, 99),
};
}
}

在这里我们扩展了基本 Factory 类,定义了一个 model 属性和一个 definition 方法。model 定义工厂为哪个实体生成实体实例。definition 方法返回使用工厂创建实体时应应用的默认属性值集。

¥Here we extend the base Factory class, define a model property and a definition method. The model defines for which entity the factory generates entity instances. The definition method returns the default set of attribute values that should be applied when creating an entity using the factory.

在此示例中,我们使用一个名为 Faker 的库,它允许你方便地生成各种随机数据以进行测试。

¥In this example, we use a library called Faker, which allows you to conveniently generate various kinds of random data for testing.

自 v6 起,Faker 不再从种子包中重新导出,需要单独安装。

¥Since v6, Faker is no longer re-exported from the seeder package and needs to be installed separately.

使用创建实体工厂

¥Creating entities using factories

定义工厂后,你可以使用它们来生成实体。可以使用此查询创建简单表:

¥Once you defined your factories you can use them to generate entities. Simply import the factory, instantiate it and call the makeOne method.

const author = new AuthorFactory(orm.em).makeOne();

生成多个实体

¥Generate multiple entities

通过调用 make 方法生成多个实体。make 方法的参数是你生成的实体数。

¥Generate multiple entities by calling the make method. The parameter of the make method is the number of entities you generate.

// Generate 5 authors
const authors = new AuthorFactory(orm.em).make(5);

覆盖属性

¥Overriding attributes

如果你想覆盖工厂的某些默认值,可以将对象传递给 make 方法。仅替换指定的属性,而其余属性仍设置为工厂指定的默认值。

¥If you would like to override some of the default values of your factories, you may pass an object to the make method. Only the specified attributes will be replaced while the rest of the attributes remain set to their default values as specified by the factory.

// Make a single author
const author = new AuthorFactory(orm.em).makeOne({
name: 'John Snow',
});

// Make 5 authors
const authors = new AuthorFactory(orm.em).make(5, {
name: 'John Snow',
});

持久化实体

¥Persisting entities

create 方法使用 EntityManager 的 persistAndFlush 方法实例化实体并将其持久化到数据库。

¥The create method instantiates entities and persists them to the database using the persistAndFlush method of the EntityManager.

// Make and persist a single author
const author = await new AuthorFactory(orm.em).createOne();

// Make and persist 5 authors
const authors = await new AuthorFactory(orm.em).create(5);

你可以通过将对象传递给 create 方法来覆盖工厂的默认值。

¥You can override the default values of your factories by passing an object to the create method.

// Make and persist a single author
const author = await new AuthorFactory(orm.em).createOne({
name: 'John Snow',
});

// Make and persist a 5 authors
const authors = await new AuthorFactory(orm.em).create(5, {
name: 'John Snow',
});

工厂关系

¥Factory relationships

为一个实体创建大量数据是很好的,但大多数时候我们希望为多个实体创建数据,并且这些实体之间也有关系。为此,我们可以使用可以链接到工厂的 each 方法。可以使用转换工厂输出实体然后再返回的函数来调用 each 方法。让我们看一些不同关系的例子。

¥It is nice to create large quantities of data for one entity, but most of the time we want to create data for multiple entities and also have relations between these. For this we can use the each method which can be chained on a factory. The each method can be called with a function that transforms output entity from the factory before returning it. Let's look at some examples for the different relations.

多对一和一对一关系

¥ManyToOne and OneToOne relations

const books: Book[] = new BookFactory(orm.em).each(book => {
book.author = new AuthorFactory(orm.em).makeOne();
}).make(5);

一对多和多对多

¥OneToMany and ManyToMany

const books: Book[] = new BookFactory(orm.em).each(book => {
book.owners.set(new OwnerFactory(orm.em).make(5));
}).make(5);

与 CLI

¥Use with CLI

你可以执行 seeder:run MikroORM CLI 命令来为你的数据库播种。默认情况下,seeder:run 命令运行 DatabaseSeeder 类,而该类又可能调用其他种子类。你可以使用密钥 seeder.defaultSeeder 或使用 环境变量 MIKRO_ORM_SEEDER_DEFAULT_SEEDER 配置默认种子。你还可以使用 --class 选项指定种子类:

¥You may execute the seeder:run MikroORM CLI command to seed your database. By default, the seeder:run command runs the DatabaseSeeder class, which may in turn invoke other seed classes. You can configure the default seeder using the key seeder.defaultSeeder or using the environment variable MIKRO_ORM_SEEDER_DEFAULT_SEEDER. You can also use the --class option to specify a seeder class:

npx mikro-orm seeder:run

npx mikro-orm seeder:run --class=BookSeeder

你还可以使用 migrate:freshschema:fresh 命令结合 --seed 选项来播种数据库,这将删除所有表并重新运行所有迁移或根据当前实体生成数据库。此命令对于完全重建数据库很有用:

¥You may also seed your database using the migrate:fresh or schema:fresh command in combination with the --seed option, which will drop all tables and re-run all of your migrations or generate the database based on the current entities. This command is useful for completely re-building your database:

npx mikro-orm migration:fresh --seed    # will drop the database, run all migrations and the DatabaseSeeder class

npx mikro-orm schema:fresh --seed # will recreate the database and run the DatabaseSeeder class

如果你不想运行 DatabaseSeeder 类,你可以指定默认的种子类应该是什么。这可以通过更改 默认设置 来完成。另一个选项是明确定义要运行的种子类。

¥If you do not want to run the DatabaseSeeder class you can either specify what the default seeder class should be. This can be done by changing the default settings. Another option is to explicitly define the seeder class you want to run.

npx mikro-orm migration:fresh --seed TestSeeder       # will drop the database, run all migrations and the TestSeeder class

npx mikro-orm schema:fresh --seed ProjectsSeeder # will recreate the database and run the ProjectsSeeder class

在测试中使用

¥Use in tests

现在我们知道如何创建种子和工厂,但是我们如何在测试中有效地使用它们。我们将展示一个如何使用它的示例。

¥Now we know how to create seeders and factories, but how can we effectively use them in tests. We will show an example how it can be used.

beforeAll(async () => {
// Get seeder from MikroORM
const seeder = orm.getSeeder();

// Refresh the database to start clean (work in mongo too since v5)
await orm.schema.refreshDatabase();

// Seed using a seeder defined by you
await seeder.seed(DatabaseSeeder);
});

test(() => {
// Do tests
});

afterAll(async () => {
// Close connection
await orm.close();
});

在生产中运行种子

¥Running seeder in production

在生产环境中,我们可能想要使用编译的种子文件。我们需要做的就是相应地配置种子路径:

¥In production environment we might want to use compiled seeder files. All we need to do is to configure the seeder path accordingly:

import { MikroORM, Utils } from '@mikro-orm/core';

await MikroORM.init({
seeder: {
path: 'dist/seeders',
pathTs: 'src/seeders',
},
// or alternatively
// seeder: {
// path: Utils.detectTsNode() ? 'src/seeders' : 'dist/seeders',
// },
// ...
});

这应该允许使用 CLI 生成 TS 种子文件(因为在 CLI 中我们可能已启用 TS 支持),同时在生产中使用编译的 JS 文件,其中 ts-node 未注册。

¥This should allow using CLI to generate TS seeder files (as in CLI we probably have TS support enabled), while using compiled JS files in production, where ts-node is not registered.