64 votes

TypeORM comment semer la base de données

Je suis en cours d'exécution mon backend Nde JS en utilisant typeorm ORM.

Venant de Entity Framework, il était très facile de semer le db avec quelques lignes telles que

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

Existe-t-il une approche similaire pour ensemencer la base de données dans TypeOrm? Si ce n'est pas le cas, quelle est la façon 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 instantiate et enregistrer des entités?

59voto

Oleg Meleshko Points 2427

Malheureusement, il n'est pas sorti officiellement solution de TypeORM (au moment de cette réponse a été publiée).

Mais il y a une belle solution de contournement, on peut utiliser:

  1. créer une autre connexion à l'intérieur d' ormconfig.js le fichier et spécifiez un autre dossier "migrations" - en fait, nos graines
  2. générer et exécuter vos graines avec -c <connection name>. Ça y est!

Exemple ormconfig.js:

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

Exemple de 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",
  },
  ...
}

17voto

B12Toaster Points 1261

Pour ceux qui sont à l'aide de TypeORM avec Nest.jsvoici une solution pour réaliser vos semis par programmation, à partir de votre code.

Idée:

  • Nous avons créer un "semis module" contenant un "semis middleware", qui est responsable de la conduite de l'ensemencement et de veiller à ce que tous les semis se fait avant toute demande de réponse.
  • Pour toute demande qui arrive, le semis, middleware l'intercepte et la remet jusqu'à ce qu'il est confirmé que le semis est fait.
  • Si la db a été semée, le "seeding middleware" transmet la demande à la prochaine middleware.
  • Pour accélérer les choses, le "seeding middleware" conserve un "semis complète" drapeau de l'état de la mémoire pour éviter toute nouvelle db-contrôle après le semis a eu lieu.

Mise en œuvre:

Pour que cela fonctionne, d'abord créer un module qui enregistre un middleware qui est à l'écoute de 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();
  }
}

Voici enfin l'entité que nous utilisons pour enregistrer dans notre db qu'un ensemencement d'un certain type n'a eu lieu. Assurez-vous de vous 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 alternative ensemencement de la solution à l'aide du cycle de vie des événements:

avec Nest.js vous pouvez également mettre en œuvre l' OnApplicationBootstrap interface (voir les événements de cycle de vie) au lieu d'aller pour un middleware de base de la solution pour gérer vos semis. L' onApplicationBootstrap méthode "appelé une fois que l'application est complètement démarré et est bootstrap". Cette approche, cependant, contrairement à un middleware-solution, ne vous permettra pas de graines de votre base de données dans un environnement multi-locataire de l'environnement où db-des schémas pour les différents locataires seront créés lors de l'exécution et de semis doit être effectué plusieurs fois au moment de l'exécution pour les différents locataires, après ils sont créés.

16voto

Eliran Malka Points 6744

Je serais ravi de voir une telle fonctionnalité ainsi (et nous ne sommes pas les seuls), mais à l' instant, il n'y a pas de dispositif officiel 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 (de sorte qu'il précède tous les autres scripts de migration que vous pourriez avoir) et ont la graine de données renseignée là.

@bitwit a créé un extrait de code qui peut être utile pour vous; c'est une fonction qui lit les données à partir de fichiers yaml, que vous pouvez incorporer dans la graine script de migration.

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

3voto

wizzfizz94 Points 292

Ressemble à un module est en cours de construction pour ce faire, typeorm-semis. Lors de l'utilisation d'une migration initiale pour l'amorçage fonctionne également son pas très utile pour les tests où un fraîchement ensemencées DB peut être nécessaire pour les tests à passer. Une fois que vous commencez à créer davantage de migrations vous ne pouvez pas laisser tomber, de synchronisation et d'exécuter des migrations sans erreurs. Ceci peut être résolu par être en mesure d'exécuter migration:run pour un seul fichier de migration, mais avec la CLI vous ne pouvez pas. Ma solution a été d'un poids léger script qui accède à la QueryRunner objet à travers un typeorm connexion:

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

Ensuite, exécutez node ./dist/path/to/testSeed.js

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X