Как создать базу данных

Я запускаю свой Node JS-сервер, используя ORIBOR типа ORORM.

Исходя из Entity Framework, было очень просто засеять db несколькими строками, такими как

Database.SetInitializer(new DbInitializer()); 

Где класс DbInitializer будет содержать всю информацию о посеве.

Есть ли аналогичный подход для семени базы данных в TypeOrm? Если нет, то каков рекомендуемый способ сделать это?

1) Создать новую миграцию с помощью операторов ввода данных? 2) Создать задачу, в которой вы создаете экземпляры и сохраняете объекты?

Ответ 1

Мне бы тоже хотелось увидеть такую функциональность (и мы не одиноки), но в момент нет официальной функции для заполнения.

в отсутствие такой встроенной функции, я думаю, что следующая лучшая вещь будет состоять в том, чтобы создать сценарий миграции с именем 0-Seed (таким образом, он предшествует любым другим сценариям миграции, которые у вас могут быть) и заполнить начальные данные там.

@bitwit создал фрагмент, который может пригодиться вам; это функция, которая считывает данные из файлов yaml, которые вы можете включить в скрипт начальной миграции.

однако после некоторых исследований я нашел другой интересный подход: связать событие after_create с таблицей и инициализировать данные в слушателе.
я не реализовал это, поэтому я не уверен, что это можно сделать напрямую с помощью TypeORM.

Ответ 2

К сожалению, официально не выпущено решение от 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",
  },
  ...
}

Ответ 3

Для тех, кто использует 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
  // 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();
  }
}

Наконец, вот сущность, которую мы используем, чтобы записать в нашу базу данных, что произошло заполнение определенного типа. Обязательно зарегистрируйте его как объект в вашем вызове 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;
  }
}

Альтернативное решение для заполнения с использованием событий жизненного цикла:

с помощью Nest.js вы также можете реализовать интерфейс OnApplicationBootstrap (см. события жизненного цикла) вместо использования решения на основе промежуточного программного обеспечения для обработки начальных значений. Метод onApplicationBootstrap будет вызван после того, как приложение полностью запустится и будет загружено. Однако этот подход, в отличие от промежуточного программного решения, не позволит вам заполнить вашу базу данных в мультитенантной среде, где во время выполнения будут созданы схемы базы данных для разных арендаторов, и для выполнения заполнения потребуется несколько раз во время выполнения. разные арендаторы после того, как они созданы.