When working with a fast-evolving project changes happen rapidly not only to the project’s code but also to the database schema. It happens especially when working on a micro-service from the start when its purpose shifts.
How EF Core 5 migrations work
In Entity Framework Core 5 migrations we are adding a migration as a delta changes between our DbContext
class and existing [DbContext name]ModelSnapshot
. When generating new migration a CLI Tool will generate only the differences between those two and put them in two methods: Up
and Down
. In the first one, there will be a change to apply the migration and in the second one, to remove the migration.
After the migration is applied, its name is noted in the __EFMigrationsHistory
table.
Merging multiple migrations
Let’s say that after changing a schema multiple times in the early stages our project is now stable. We have a bunch of migrations that could be merged into one, that would create a model once, without many small updates.
When we can remove everything
The easiest way to merge all migrations would be removing everything! What I mean is:
- remove Migrations directory with all migrations
- clear
__EFMigrationHistory
table - remove all tables and other database objects that were added via migrations
- create new migration with all changes
This is a drastic way of merging migrations because we will lose all the data. However, it’s super simple and it might work in some cases.
When we need to preserve data
This means that we cannot remove all already created database objects, but we can merge migration files in our code. Let’s see how that can be done.
- Delete all migration scripts from Migrations folder
- Add a new migration with command
dotnet ef migrations add MergedMigration
- Copy the entire file and clear both
Up
andDown
methods - Update the database and apply
MergedMigration
migration with commanddotnet ef database update
- After that, replace the content of the
MergedMigration
file with earlier generated code
As a result, you will have only one migration file. In my example the __EFMigrationHistory
table looks like this.
And now in the Visual Studio, I can see only on migration.
That contains changes from all my previous migrations merged together.
It worked!
Tip! You can also name you merged migration as the first one, that was already applied so that you wouldn’t have to update the database and add it to the __EFMigrationHistory
table.
It will not work in every case
We can easily merge migrations when they are applied only to the database that we control. I addition, it will not work for environments that don’t have all merged migrations applied. The process is easy, but there are certain things that need to be taken into account.
On the other hand, do we need to keep all migrations, even when we know that we won’t run them ever again? I don’t think so. At this point, it would be great to merge old migrations but leave the newest ones. It could be accomplished with a very similar process.
- Revert last N migrations locally, one by one, and move them somewhere
- Check out the project in the place matching the migrations
- Merge all existing migrations
- Check out the project in the newest state
- Add saved newest migrations, one by one
In this case, we will keep the newest migrations and create a big initial migration, that would be consistent with your project. If there will be a case, where all migrations would need to be applied to a database, the database schema wouldn’t be broken.
Summary
Merging migrations in Entity Framework Core 5 is possible and I can say it’s surprisingly easy. However, it involves an automatic generation process and you need to check if merged migration does exactly the same thing as all migrations applied one by one. Moreover, there is more than one way to merge migrations and you need to choose the one that is best for you.
All code posted here is available at my GitHub, so you can download it and play with it. Take a look also at this post on how to run it: PrimeHotel – how to run this project.
Thanks for reading and hopefully see you again 🙂
That approach seems to lose raw SQL statements like `migrationBuilder.Sql()` used within `Up()` or `Down()`. Nevertheless thanks for sharing!
We found that hasn’t been a problem when it loses the raw SQL statements when typically they’re used to reformat existing data and the existing data that wouldn’t exist in a fresh system.
Great article, appreciate .. i just followed it and works like a charm!
Thanks, I’m glad it helped you 🙂