Save Big on Cyber Monday! Up to 40% Off
ends in   {{days}}
Days
{{timeFormat.hours}}
:
{{timeFormat.minutes}}
:
{{timeFormat.seconds}}

EF Core Migrations

Working with data models requires updating them regularly to reflect changes in application functionality. The database schema must stay in sync with the application while adding or removing features. Entity Framework Core (EF Core) provides the Migrations feature that lets you update the database schema without losing existing data.

This article explores EF Core Migrations, their key features, and usage.

Migrations in EF Core

Changes in a database model must be reflected in the database tables to keep them synchronized and ensure the application functions correctly. Entity Framework Core (EF Core) provides the Migrations tool to automatically track and apply such changes.

EF Core compares the current model with the previous one, identifies the differences, and generates migration files. These migrations can then be applied to the database. EF Core also maintains a history table that tracks which migrations have been applied, making it easy to manage schema changes over time. Therefore, EF Core Migrations eliminate the need to manually update database schemas, reducing the risk of errors and saving time.

Migrations are mainly used in the Code-First approach, where the database schema is created and maintained based on C# model classes. In contrast, the Database-First approach that relies on an existing database schema is not well suited for EF Core Migrations.

Why do I need migrations?

EF Core Migrations offers an efficient way to manage database changes, especially in team environments where multiple developers may modify the same schema. Key benefits include:

  • Schema evolution: As the application grows, the database schema must adapt. EF Core Migrations helps track and apply these changes over time.
  • Versioned management: Migrations treat database changes as versioned updates, making it easier to manage and revert modifications when needed.
  • Automation: EF Core automates the generation and application of schema changes, reducing the need for manual SQL coding.
  • Consistency: Migrations ensure that all database instances remain in sync, minimizing the risk of discrepancies and errors.
  • Team collaboration: Each developer can work with a local database that reflects the latest schema updates, improving coordination and reducing conflicts.

Now, let's explore how to create and configure EF Core migrations in a practical scenario. We've set up a test project for this walkthrough that uses PostgreSQL as the data source. To connect to the database, we'll use dotConnect for PostgreSQL, a robust data provider with full EF Core support that enables direct access to PostgreSQL databases during application development.

Install dotConnect for PostgreSQL

After creating a test project in Visual Studio, we can install dotConnect for PostgreSQL with EF Core support using the Package Manager Console of Visual Studio or .NET CLI. Run the following command in your preferred utility:

Package Manager Console:

Install-Package Devart.Data.PostgreSql.EFCore

.NET CLI:

dotnet add package Devart.Data.PostgreSql.EFCore

When successfully installed, this data provider enables us to connect directly to our PostgreSQL database without additional client libraries or the need to master new technologies.

DbContext and DbSet

Before we proceed to the next step of adding a migration, let us explore the two essential concepts – DbContext and DbSet. These concepts are necessary for us, as we have to configure DbContext in our project.

  • DbContext manages the database connections, tracks changes to your entities, and executes queries. It also eliminates the need to write complex SQL queries manually, as it generates them automatically.
  • DbSet represents a collection of entities in the database (a virtual database table). We need to define it for each entity type (e.g., Customer, Order, Product) to query and manipulate data for that entity.

This tutorial works with two tables, Customers and Orders. Let us define the Customers and Orders entities.

public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}
 

public class Order
{
    public int OrderId { get; set; }
    public int CustomerId { get; set; }
    public int ProductId { get; set; }
    public int Date { get; set; }
    public Customer Customer { get; set; }
}

We also need a DbContext implementation. Here we define the ecommercedbModel class:

using Microsoft.EntityFrameworkCore;

public class ecommercedbModel : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Order> Orders { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UsePostgreSql("Host=localhost;Database=ecommercedb;Username=postgres;Password=mypassword;");
    }
}
Important!
The names of entities in DbSet are case sensitive. They must be identical in both models and DbSet.

The next step is creating an EF Core migration.

Add or create an EF Core migration

After setting up the DbContext and registering our entities using DbSet or making changes to them, we can add a migration using the Add-Migration command.

Package Manager Console:

add-migration MigrationName

CLI:

dotnet ef migrations add MigrationName

Make sure to provide the actual migration name in the command. It is recommended that migrations be named like Git commit messages for better organization.

When you create a migration, EF Core generates a new Migrations folder in your project. This folder contains two files: one that defines the migration itself and another called the snapshot file.

Migrations folder with generated files

The snapshot file captures the current state of the database schema and serves as a reference for EF Core to determine whether new migrations are required. EF Core compares the current model with the one from the previous migration (if available) and generates a migration file containing Up and Down methods.

The snapshot file with the information

The file name includes a timestamp followed by the migration name you provided.

  • The Up method defines the C# code needed to apply changes to the database schema based on updates made to the model since the last migration.
  • The Down method contains the logic to undo those changes, returning the database to its previous state.

EF Core also creates or updates a ModelSnapshot file, which represents the latest state of the model. This snapshot helps EF Core detect future changes. The class name matches the migration name you specified.

The model snapshot file contents

Store migrations in a custom directory

Some scenarios may require storing migration files in custom folders, not the default Migrations folder. To achieve this, you need to specify the path in the command for creating that migration.

Package Manager Console:

Add-Migration MigrationName -OutputDir "Custom/Folder/Path"

CLI:

dotnet ef migrations add MigrationName --output-dir Custom/Folder/Path

In this command, MigrationName is the name of your migration, while the --output-dir (-OutputDir in PMC) parameter specifies the path to the folder to store the migration files.

Setting the custom folder for migrations

Customize the migration code

Sometimes, you may need to customize the generated migration code before applying it. You should edit the Up or Down method in the corresponding migration file to do this. Let us consider some common use cases that require editing the migration code.

Rename a column using EF Core migrations

For example, you want to rename the Email column in the Customer table to EmailAddress. By default, EF Core might generate code that drops the existing column and creates a new one with the new name, and this approach can lead to data loss.

To avoid this, modify the migrationBuilder code manually. Instead of allowing EF Core to drop and recreate the column, explicitly rename it using the RenameColumn method:

migrationBuilder.RenameColumn(
    name: "OldColumnName",
    table: "YourTableName",
    newName: "NewColumnName"
);

Open the generated migration file in the Migrations folder. To rename without losing data, replace the generated code in the Up() method with this:

migrationBuilder.RenameColumn(
	name: "Email",
	table: "Customers",
	newName: "EmailAddress");

And in the Down() method, reverse the rename:

migrationBuilder.RenameColumn(
	name: "EmailAddress",
	table: "Customers",
	newName: "Email");

In this approach, RenameColumn() instructs EF Core to rename the column at the database level without dropping and recreating it, while the Down() method helps roll back the rename if needed.

Rename a column by editing the migration code

Run the migration update command to apply changes to your database:

Update-Database

Update the database with the migration

Another option is executing the raw SQL query in the command line tool:

migrationBuilder.Sql("UPDATE customers SET CreatedAt = NOW()");

Also, it is possible to rename a column by modifying the entire model. This method is optional but can be useful in some scenarios:

Rename the property in your model class:

public class Customer
{
	// Old:
	// public string Email { get; set; }

	// New:
	public string EmailAddress { get; set; }

	// Other properties...
}

Then, generate a new migration that will contain the rename operation:

Add-Migration RenameEmailToEmailAddress

Use a new migration to rename a column

Combine two columns into a single column

In some cases, you may need to perform operations that EF Core cannot generate automatically, such as merging two columns into one. For example, you might want to combine the FirstName and LastName columns into a single FullName column.

EF Core generates code that drops the FirstName and LastName fields by default and creates a new FullName field. However, this approach results in data loss for both original columns.

To avoid this, you can write raw SQL in your migration file and manually control the sequence of actions. Since migrations are applied sequentially, each building on the previous one, your custom code should:

  • 1. Add a new nullable FullName column.
  • 2. Populate it using existing FirstName and LastName data.
  • 3. Drop the original FirstName and LastName columns.

Open the newly generated migration class in the Migrations folder. Replace or add code in the Up() method:

protected override void Up(MigrationBuilder migrationBuilder) {
  // Add FullName column first
  migrationBuilder.AddColumn<string>(
    name: "FullName",
    table: "Customers",
    nullable: true);

  // Run SQL to populate FullName from FirstName and LastName (columns must exist here)
  migrationBuilder.Sql("UPDATE \"Customers\" SET \"FullName\" = \"FirstName\" || ' ' || \"LastName\";");

  // Now drop the old columns
  migrationBuilder.DropColumn(name: "FirstName", table: "Customers");
  migrationBuilder.DropColumn(name: "LastName", table: "Customers");
}

In the Down() method, reverse the changes, as in the previous scenario:

protected override void Down(MigrationBuilder migrationBuilder) {
  // Recreate FirstName and LastName columns
  migrationBuilder.AddColumn<string>(
    name: "FirstName",
    table: "Customers",
    nullable: true);

  migrationBuilder.AddColumn<string>(
    name: "LastName",
    table: "Customers",
    nullable: true);

  // Optional: split FullName back into FirstName and LastName
  // (Add SQL here if needed)

  // Drop FullName column
  migrationBuilder.DropColumn(
    name: "FullName",
    table: "Customers");
}

Merge columns into a single column

Run the below command in PMC to update your database schema:

Update-Database

View the merging results

The migrationBuilder.Sql() runs a raw SQL command concatenating FirstName and LastName with a space. It adds FullName, filling it and safely removing FirstName and LastName. This ensures data integrity during the process of restructuring your database schema.

Another approach suggests editing the model class where you can add a new property and remove the old ones:

public class Customer
{
	public int Id { get; set; }
	// Remove FirstName and LastName
	public string FullName { get; set; }
	public string Email { get; set; }
	// ... other properties
}
Note
You don't have to change your model before the migration; updating it helps keep your code in sync.

After that, in the Package Manager Console or CLI terminal, create a new migration that will perform the database schema changes:

Add-Migration CombineNameColumnsIntoFullName

Detect unapplied model changes before migration

EF Core can check whether any model changes have occurred since the last migration without committing to a new migration.

Execute the following command in CLI to see if the code is synchronized with the current migration:

dotnet ef migrations add CheckChanges

Check for unapplied changes before the migration

EF Core will create a new migration file in the specified output folder (TempMigrations) if there are changes. You can inspect this file to see the expected changes.

You can also modify the command to disable the building of the migration:

dotnet ef migrations add CheckChanges --no-build --output-dir TempMigrations

Make sure to delete the TempMigrations folder afterward to avoid cluttering your project.

Revert/Roll back an EF Core migration

You can roll back your database to a specific migration by specifying the target migration in the update command. The target migration marks the point to which the database should be restored, and all changes introduced by later migrations will be undone.

Package Manager Console:

update-database TargetMigration

CLI:

dotnet ef database update TargetMigration

This command removes the entries for any migrations applied after the target migration from the EFMigrationsHistory table in the database. However, it does not delete those migrations from the Migrations folder or modify the ModelSnapshot file.

Roll back the migration

Remove an EF Core migration

If you created a migration by mistake or no longer need it, you can delete it using the following command:

Package Manager Console:

remove-migration

CLI:

dotnet ef migrations remove

This command deletes the most recent migration, including its generated class file, and reverts the ModelSnapshot file to the state of the previous migration. If there are no pending migrations, it will throw an exception. If the migration has already been applied to the database, you must roll it back before removing it.

Remove a migration

Important!
Always use the command to remove a migration instead of manually deleting the migration file. Manually deleting files can cause the model snapshot and migration history to become inconsistent, leading to issues in future migrations.

Suppose you must remove an older migration (not the latest one). In that case, you must first delete all newer migrations, update the model accordingly, and then generate a new migration that reflects the correct changes.

Create SQL scripts from migrations

You may need to deploy one or multiple migrations against another database. The simplest option is executing a dedicated SQL script against the target database. You can generate that script from the migration using the below command:

Package Manager Console:

Script-Migration -From InitialCreate -To RenameEmailToEmailAddress -Output migration.sql

CLI:

dotnet ef migrations script -From InitialCreate -To RenameEmailToEmailAddress -Output migration.sql

Create an SQL script from the specific migration

By default, this script includes all migrations, but you can specify the necessary range to include using the -to and -from options.

Complete list of EF Core migration commands

Below are the EF Core migration commands available for the CLI and the Package Manager Console, depending on your preferences.

Package Manager Console

Command Description
add-migration [name] Creates a new migration with the specific migration name
remove-migration Removes the latest migration
update-database Updates the database to the latest migration
update-database [name] Updates the database to a specific migration name point
get-migrations Lists all available migrations
script-migration Generates an SQL script for all migrations
drop-database Drops the database

Command Line

Command Description
dotnet ef migrations add [name] Creates a new migration with the specific migration name
dotnet ef migrations remove Removes the latest migration
dotnet ef database update Updates the database to the latest migration
dotnet ef database update [name] Updates the database to a specific migration name point
dotnet ef migrations list Lists all available migrations
dotnet ef migrations script Generates a SQL script for all migrations
dotnet ef migrations has-pending-model-changes Checks if there are any model changes since the last migration
dotnet ef database drop Drops the database

Multi-stage database migrations

One of the most common scenarios for using migrations is handling multiple database environments (such as development and production). You need to test and stage the schema changes properly before reaching production. Multi-stage migrations help organize and control this process. Let us review this process step by step.

Use separate connection strings for each environment

Configure your DbContext to use different databases depending on environment or configuration as shown below:

public class AppDbContext : DbContext
{
	public DbSet<Customer> Customers { get; set; }
	public DbSet<Order> Orders { get; set; }

	private readonly string _connectionString;

	public AppDbContext(string connectionString)
	{
    	_connectionString = connectionString;
	}

	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
    	optionsBuilder.UsePostgreSql(_connectionString);
	}
}

For example, we use the following connection strings for the development and production environments:

Development:

"Host=localhost;Database=CommerceDB_dev;User Id=postgres;Password=yourpassword;"

Production:

"Host=prodserver;Database=CommerceDB;User Id=produser;Password=prodpassword;"

Maintain separate migration branches or naming

To manage migrations accurately across environments, you have two options:

1. Use different migration folders or namespaces for dev and production:

  • - Migrations/Dev for development
  • - Migrations/Prod for production

2. Keep migrations in one folder but branch migration names to indicate the environment.

  • - 20250520_Dev_AddNewTable
  • - 20250525_Prod_AddNewTable

This helps us avoid accidentally applying a development-only migration to production.

Create development migrations first

Start with migrations that target the development database:

dotnet ef migrations add AddNewFeature_Dev -o Migrations/Dev
dotnet ef database update --connection "Host=localhost;Database=CommerceDB_dev;User Id=postgres;Password=yourpassword;"

Test your changes thoroughly in CommerceDB_dev.

Create production migration after testing

Once the development migration is stable, you can re-create the migration for production with the same or similar schema changes:

dotnet ef migrations add AddNewFeature_Prod -o Migrations/Prod
dotnet ef database update --connection "Host=prodserver;Database=CommerceDB;User Id=produser;Password=prodpassword;"

Alternatively, if your migrations are identical, you can merge the dev migrations into production after testing and apply them to the production database.

Apply migrations programmatically per environment

At the app startup or deployment, apply migrations using the correct connection string:

var connectionString = Environment.GetEnvironmentVariable("CONNECTION_STRING");
using var context = new AppDbContext(connectionString);
context.Database.Migrate();

Configure your deployment pipeline or environment variables accordingly.

Use migration bundles or scripts for production

For more controlled production deployment, generate a migration bundle or SQL script after testing:

dotnet ef migrations bundle --output prod_migrations.exe

Or

dotnet ef migrations script -o prod_migration.sql

Deploy and execute the bundle or script on production outside the application.

Best practices for multi-stage migrations

  • Back up production databases before applying migrations
  • Test all migrations on dev/staging before production
  • Keep migration history clean and linear per environment
  • Use feature branches in source control for schema changes
  • Automate migrations as part of CI/CD pipelines with environment-specific configs

Conclusion

Migrations in EF Core are a vital feature that helps manage database schema changes over time. They ensure consistency between your application's code and the underlying database, improve team collaboration, and enable safe schema updates. This article explored EF Core migrations in detail, including how to create, customize, revert, and delete migrations. We hope this guide helps you master this valuable feature of EF Core.

Connect to data effortlessly in .NET

Streamline your .NET projects with feature-rich ADO.NET providers