web-dev-qa-db-fra.com

TypeORM comment initialiser la base de données

J'exécute mon Node JS backend en utilisant ORTH typeorm.

Venant d’Entity Framework, il était très facile d’ensemencer la base de données avec quelques lignes telles que

Database.SetInitializer(new DbInitializer()); 

Où la classe DbInitializer contiendrait toutes les informations d'ensemencement.

Existe-t-il une approche similaire pour initialiser la base de données dans TypeOrm? Si non, quelle est la manière recommandée de le faire?

1) Créer une nouvelle migration avec les instructions d’insertion de données? 2) Créer une tâche où vous instanciez et enregistrez des entités?

19
Aaron Ullal

j'aimerais aussi voir une telle fonctionnalité (et nous ne sommes pas seuls ), mais à lamoment , il n'y a pas de fonctionnalité officielle pour l'ensemencement .

en l'absence d'une telle fonctionnalité intégrée, je pense que la meilleure chose à faire serait de créer un script de migration nommé 0-Seed (il précède tous les autres scripts de migration que vous pourriez avoir) et les données de base y sont remplies.

@ bitwit a créé n extrait de code qui peut vous être utile. c'est une fonction qui lit les données à partir de fichiers yaml, que vous pouvez incorporer dans le script de migration de départ.

après quelques recherches, cependant, j’ai trouvé une autre approche intéressante: lier un after_create événement à la table, et initialise les données dans le listener.
Je n’ai pas implémenté cela, donc je ne suis pas sûr que cela puisse être fait directement avec TypeORM.

7
Eliran Malka

Malheureusement, il n’existe pas de solution officiellement publiée par TypeORM (au moment de la publication de cette réponse).

Mais il existe une solution de contournement intéressante que nous pouvons utiliser:

  1. créer une autre connexion à l'intérieur de ormconfig.js file et spécifie un autre dossier pour "migrations" - en fait, nos graines
  2. générer et faire fonctionner vos graines avec -c <connection name>. C'est ça!

Exemple ormconfig.js :

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

Exemple 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",
  },
  ...
}
16
oleh.meleshko

Pour ceux qui utilisent TypeORM avec Nest.js , voici une solution pour effectuer votre amorçage par programmation, depuis votre code.

Idée approximative:

  • Nous créons un "module d'ensemencement" dédié contenant un "middleware d'ensemencement" chargé de mener l'ensemencement et de veiller à ce que tout l'ensemencement soit effectué avant qu'une demande ne soit traitée.
  • Pour toute demande qui arrive, le middleware d'ensemencement l'intercepte et la reporte jusqu'à confirmation de l'ensemencement.
  • Si la base de données a été créée, le "middleware de distribution" transmet la demande au middleware suivant.
  • Pour accélérer les choses, le "middleware de sauvegarde" conserve en mémoire un indicateur "de sauvegarde complète" afin d'éviter toute vérification supplémentaire de la base de données une fois la sauvegarde effectuée.

Mise en oeuvre:

Pour que cela fonctionne, commencez par créer un module qui enregistre un middleware qui écoute toutes les demandes entrantes:

// file: src/seeding/SeedingModule.ts

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

Maintenant, créez le middleware:

// 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();
  }
}

Enfin, voici l’entité que nous utilisons pour enregistrer dans notre base de données qu’un amorçage d’un certain type s’est produit. Assurez-vous de l'enregistrer en tant qu'entité dans votre TypeOrmModule.forRoot appel.

// 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;
  }
}

Une solution d'amorçage alternative utilisant des événements de cycle de vie:

avec Nest.js, vous pouvez également implémenter l'interface OnApplicationBootstrap (voir événements de cycle de vie ) au lieu de rechercher une solution basée sur un middleware pour gérer vos semences. La méthode onApplicationBootstrap sera "appelée une fois l'application lancée et démarrée". Cependant, cette approche, contrairement à une solution middleware, ne vous permettra pas d’amorcer votre base de données dans un environnement multi-locataire où les schémas de base de données pour différents locataires seront créés au moment de l’exécution et l’amorçage doit être effectué plusieurs fois à l’exécution. différents locataires après leur création.

1
B12Toaster