
Controlling the flow of migrations in EF Core
Altering EF Core migrations execution order
EF Core has migrations as a way of restoring the database structure inherited from Entity Framework and for the most of the cases all you have to do to setup your database structure changes when deploying your new application version is to call the Migrate method from your DbContext.Database property.
It is not a perfect solution but as I mention, it works great for majority of the cases and takes of the complexity of sorting out order for execution of pending changes so you can focus on business logic of your application.
For this article I will be using some code snippets from the Demo/Sample project written for project https://www.nuget.org/packages/EntityFrameworkCore.SqlServer.Seeding. If you want to check out the whole solution the repository in GitHub is https://github.com/dejanstojanovic/sql-server-script-seeding
Your application migration invocation will probably look something like this
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ... using (var serviceScope = app.ApplicationServices .GetRequiredService<IServiceScopeFactory>() .CreateScope()) { using (var context = serviceScope.ServiceProvider.GetService<EmployeesDatabaseContext>()) { context.Database.Migrate(); } } app.SeedFromScripts(); ... }
For most of the cases this just works fine because all the migrations, if created the proper way from the command line are added to the project in order and EF Core takes care of their execution internally so you do not have to interfere.
For some corner cases, you may want to execute something in the middle of the migrations list that are pending for execution and this is what this article is about.
Taking control of migrations execution
For the nuget package EntityFrameworkCore.SqlServer.Seeding project, I initially used similar approach as in EF Core common scenario and that is just to invoke seeding once all migrations are finished.
This way application will ensure that whole infrastructure is in place before the seeding takes place. This makes sense for most of the scenarios and if your application does not need any custom flow alterations this is the way to go.
To keep the pipeline clean, all this is abstracted into an extension method and later just invoked from Configure method in Startup.cs.
public static void MigrateEmployeesData(this IApplicationBuilder app, IConfiguration configuration) { using (var serviceScope = app.ApplicationServices .GetRequiredService<IServiceScopeFactory>() .CreateScope()) { using (var context = serviceScope.ServiceProvider.GetService<EmployeesDatabaseContext>()) { context.Database.Migrate(); } } app.SeedFromScripts(); }
The problems take place when you need to run migrations and seedings interchangeably. For this scenario the code snipped from above won't do because the execution order of EF Core migrations and seeding logic is abstracted into their own single method calls. Now I can always alter my own code for seeding nuget package and release an updated version but for EF Core I need to access the migration service.
Luckily, by design, EF Core internally uses injected instance of interface Microsoft.EntityFrameworkCore.Migrations.IMigrator implementation which takes care of migrations execution. Instead of just calling DbContext.Database.Migrate method, by combining method call DbContext.Database.GetPendingMigrations with methods from IMigrator, we can manually call migrations one by one.
public static void MigrateAndSeedEmployeesData(this IApplicationBuilder app, IConfiguration configuration) { using (var serviceScope = app.ApplicationServices .GetRequiredService<IServiceScopeFactory>() .CreateScope()) { using (var context = serviceScope.ServiceProvider.GetService<EmployeesDatabaseContext>()) { var migrator = context.Database.GetService<IMigrator>(); var migrations = context.Database.GetPendingMigrations(); var seeder = serviceScope.ServiceProvider.GetService<ISeeder>(); var seeds = seeder.GetPendingScripts(); var commands = migrations.Concat(seeds).OrderBy(c => c).ToList(); if (commands != null && commands.Any()) { foreach (var command in commands) { if (command.EndsWith(".sql")) seeder.ExecuteScript(command); else migrator.Migrate(command); } } } } }
Both migrations and seeding scripts prefixes are date and time migration/seeding file is created, so this makes it easy to determine the execution order and I can easily call migration/seeding in mixed flow.
References
- IMigrator Interface
- SQL Server script seeding for Entity Framework Core
- EntityFrameworkCore.SqlServer.Seeding
Disclaimer
Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.
Comments for this article