🛑 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:
// 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();
}
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.
- Créer un scope
using var scope = serviceProvider.CreateScope();
- Dans ce scope, demander le service
MigrationRunner
pour exécuter des migrationsvar migrationRunner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
- 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.
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.
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()
.