Migrations
Migrations workflow
When working on a feature and we want to add/modify/delete an entity, we should NEVER delete or modify existing migrations. You need to add a new migration and update your database:
dotnet ef migrations add <NEW-migration-name>
dotnet ef database update
We do migrations per table. So if we need to add one field in three table for the same feature, we make three migrations. It makes it much easier to execute and resolve problems if there are any.
If along the way on your branch you have to add/modify/delete an entity and your new migration has already been created, run these commands:
dotnet ef database update <PREVIOUS-migration-name>
dotnet ef migrations remove
dotnet ef migrations add <NEW-migration-name>
dotnet ef database update
⚠️ Adding foreign keys can cause problems. Please respect EF Core conventions and be careful when you change migration files manually.
⚠️ When working with Migrations in team environments, pay extra attention to the model snapshot file and resolve merge conflicts.
Create base migration
When we arrive to a point with a stable version of the database and we want to reduce the number of migrations, we can merge them together to create a base migration.
That way, we can keep track of the versions of the database by stable versions.
Follow these steps :
- First check if current migrations have issues
Create Database with no tables. And run this command :
dotnet ef database update
If this command gives you an issue, that means that we have migration issue.
If no errors, it means that we are success in migration.
If we get success, follow the below steps.
Delete the small migrations you want to merge. Delete also the DatabaseContextModelSnapshot.cs
- Run this command :
dotnet ef migrations add Init
This command will create a migration
If you want to keep an existing database, make sure it is a valid database (the one you made
dotnet ef database update
to validate), go in the _efmigrationistory table and delete all the migrations there. Then add the name of the init migration you just created. It looks someting like this :20240402215242_Init
. Then, EF will know the migration is already done and will not try to do it again. The futur migration will be added after.Then run this command :
dotnet ef database update
Using these steps, we will have one migration that combines all the previous small migrations.
Rename field
You need to use migrations to rename a field without the action being considered as a if you delete the field and added a new one. Otherwise, the data in the colum will be lost. </br> If you rename the field using the refractor, EntityFramework should be able to detect the rename and do it properly in the next migration. </br> Before aplying the migration, make sure this code if affected to the renamed field in the migration or add it if EF did not detect it.
In Up() function :
migrationBuilder.RenameColumn(
name: "OldName",
table: "StripeCustomers",
newName: "NewName");
In Down() function
migrationBuilder.RenameColumn(
name: "NewName",
table: "StripeCustomers",
newName: "OldName");
Change migration files manually
Migration Code
Migrations contains 2 functions : up
and down
The up function is runned when we call the update command
Example that adds a column to the table user:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "CreatedTimestamp",
table: "Users",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedTimestamp",
table: "Users");
}
Migrate old data to new changes
⚠️While EF Core generally creates accurate migrations, you should always review the code and make sure it corresponds to the desired change; in some cases, it is even necessary to do so.
Example with **Column renames**
When we rename a column in a Model, EF will understand taht we removed the column and after that we created a new one. So the data will be lost.
migrationBuilder.DropColumn(
name: "Name",
table: "Customers");
migrationBuilder.AddColumn<string>(
name: "FullName",
table: "Customers",
nullable: true);
This code needs to be manually changed to a rename:
migrationBuilder.RenameColumn(
name: "Name",
table: "Customers",
newName: "FullName");
The data will then be transferred to the renamed column.
Alter mandatory foreign key that contains values
It will happen that sometimes, we have to update (alter) a fields or a foreign key that contains values and therefor, cannot be directly updated.
For example, we have the product and SubscriptionPlan table. Product is mandatory in SubscriptionPlan. So, a non-nullable foreign key (required). The delete Behavior of this relation is cascade but we want to change it to Restrinct.
If we simply change it and run the migration, it will never work because the migration will try to delete the foreign key.
In development, we could simply drop the data and re-add it after the migration but this solution is not valid for production.
Here's what to do :
We rename the relation column to a temporary name, then we create the new relation column with the right foreign key rules (in this case restrinct). Then we take the values from the previous column that is renamed and put them in the new column.
So, here are the steps :
- Do your change it the code
- Generate the migration so the DatabaseContextModelSnapshot.cs and the .designer has the right version
- Change the migration script :
Instead of juste droping the foreign key (and index) and adding the new one,
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_subscription_plan_product_ProductID",
table: "subscription_plan");
migrationBuilder.DropIndex(
name: "IX_subscription_plan_ProductID",
table: "subscription_plan");
migrationBuilder.RenameColumn(
name: "ProductID",
table: "subscription_plan",
newName: "tempProductId");
migrationBuilder.AddColumn<int>(
name: "ProductID",
table: "subscription_plan",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql("UPDATE subscription_plan SET ProductID = tempProductId;");
migrationBuilder.AddForeignKey(
name: "FK_subscription_plan_product_ProductID",
table: "subscription_plan",
column: "ProductID",
principalTable: "product",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
migrationBuilder.CreateIndex(
name: "IX_subscription_plan_ProductID",
table: "subscription_plan",
column: "ProductID");
migrationBuilder.DropColumn(
name: "tempProductId",
table: "subscription_plan");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_subscription_plan_product_ProductID",
table: "subscription_plan");
migrationBuilder.DropIndex(
name: "IX_subscription_plan_ProductID",
table: "subscription_plan");
migrationBuilder.RenameColumn(
name: "ProductID",
table: "subscription_plan",
newName: "tempProductId");
migrationBuilder.AddColumn<int>(
name: "ProductID",
table: "subscription_plan",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql("UPDATE subscription_plan SET ProductID = tempProductId;");
migrationBuilder.AddForeignKey(
name: "FK_subscription_plan_product_ProductID",
table: "subscription_plan",
column: "ProductID",
principalTable: "product",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
migrationBuilder.CreateIndex(
name: "IX_subscription_plan_ProductID",
table: "subscription_plan",
column: "ProductID");
migrationBuilder.DropColumn(
name: "tempProductId",
table: "subscription_plan");
}