Aller au contenu principal

🛑 Explications MigrationsConfig

Beaucoup de choses ont été effectuées en lien avec la classe MigrationsConfig et méritent d'être décortiquées.

Extension Method

La classe MigrationsConfig que vous avez créée est une Extension Method ou une méthode d'extension.

Vous verrez les notions liées aux extensions plus directement donc votre cours de développement d'applications natives, mais il est tout de même pertinent de comprendre l'essentiel à ce stade.

Une méthode d'extension en C# est une façon d'ajouter à une classe existante des comportements supplémentaires via de nouvelles fonctions.

Par exemple, l'extension suivante permet d'ajouter une fonction supplémentaire au type string afin de compter le nombre de mots qu'elle contient.

namespace ExtensionMethods
{
public static class MyExtensions
{
public static int WordCount(this string str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}

Ensuite, la fonction peut être appelée directement sur string, comme s'il s'agissait d'une fonction native au type.

string s = "Hello Extension Methods";
int i = s.WordCount();

Pour créer une fonction d'extension

Pour créer une fonction d'extension, il faut:

  • Créer une classe static (classe ne pouvant être instanciée en objet)

  • Définir la ou les fonctions comme des fonctions static

  • Ajouter le modificateur this au premier paramètre de la fonction, ainsi que le type à étendre.

    public static int WordCount(this string str)

    Ici, on remarque this string str.

Utilisation des fonctions d'extension dans Program.cs

Program.cs fait abondamment usage des fonctions d'extension. Par exemple:

// Add services to the container.
builder.Services.AddControllersWithViews();

Ensuite, si on regarde la définition de AddControllersWithViews() (F12):

public static IMvcBuilder AddControllersWithViews(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);

var builder = AddControllersWithViewsCore(services);
return new MvcBuilder(builder.Services, builder.PartManager);
}

On remarque l'utilisation de la signature d'une méthode d'extension via this IServiceCollection services.

ServiceCollection

Au final, tout comme AddControllersWithViews, c'est exactement ce que nous faisons pour ajouter le support pour les migrations:

Program.cs
// Ajoute les migrations
builder.Services.AddMigrations(configuration.GetConnectionString("AppDatabaseConnection")!);

La signature de la fonction AddMigrations dans MigrationsConfig correspond bien à une méthode d'extension de IServiceCollection:

public static IServiceCollection AddMigrations(this IServiceCollection services, string connectionString)

IServiceCollection?

ServiceCollection est utilisé pour l'injection de dépendances. N'importe quelle classe que nous voulons rendre disponible à l'application via l'injection de dépendances doit être enregistrée dans le ServiceCollection.

On peut ensuite injecter la dépendance plus tard directement dans un constructeur ou encore aller la chercher manuellement via ServiceProvider.

AddMigrations()

La fonction d'extension AddMigrations() de la classe MigrationsConfig effectue essentiellement la configuration de FluentMigrator, en l'ajoutant à la liste de services:

public static IServiceCollection AddMigrations(this IServiceCollection services, string connectionString)
{
if (services == null)
throw new ArgumentNullException(nameof(services));

// Configure le service FluentMigrator
services.AddFluentMigratorCore()
.ConfigureRunner(c => c
.AddPostgres()
.WithGlobalConnectionString(connectionString)
.ScanIn(Assembly.GetExecutingAssembly()).For.All()
);

// Ajoute le logging en console
services.AddLogging(c => c.AddFluentMigratorConsole());

return services;
}

MigrateUp()

La fonction MigrateUp() appelle la fonction MigrateUp() sur le runner de FluentMigrator, soit le processus responsable de lire et exécuter les migrations.

Cette opération se fait dans un Scope.

public static void MigrateUp(this IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var migrationRunner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
migrationRunner.MigrateUp();
}
info

Un scope permet de contrôler la création des ressources utilisées par .NET. En effet, dans un scope, les ressources demandées sont créées une seule fois et disposées ensuite.

  1. Créer un scope
    using var scope = serviceProvider.CreateScope();
  2. Dans ce scope, demander le service MigrationRunner pour exécuter des migrations
    var migrationRunner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
  3. Exécuter les actions nécessaires et le scope sera automatiquement libéré à la fin de la fonction.

Comment sait-il quelle(s) migration(s) exécuter?

Peut-être l'avez-vous remarqué, mais FluentMigrator a automatiquement créé une table VersionInfo dans votre BD.

Imgur

Cette table contient une entrée pour chaque migration qui a été effectuée. C'est de cette façon que l'outil s'assure de ne pas exécuter deux fois la même migration! C'est aussi pour cette raison que l'on ajoute des numéros de version aux migrations.

astuce

Vider cette table et supprimer les autres tables a le même effet que faire MigrateDown(0). Ou encore, supprimer une table et la rangée associée dans VersionInfo aura comme effet d'exécuter la migration lors du prochain MigrateUp().