In this section, we are going to cover Migrations and Seed data features in Entity Framework Core.
In the previous parts of this series, we have created the database model (entity and context classes) and applied different configuration options. Now it is time to transfer this database model to the real database in the SQL server. Our SQL database schema needs to be aligned with our application’s database model and using migrations will help us keep things that way.
EF Core provides a method called Migrate
to execute migration actions for us. All we have to do is to create model classes, and a context class, apply configuration (which we already did) and create and execute migrations with the set of simple commands.
Let’s dive in.
VIDEO: EF Core Code First Migrations in Detail.
What is Migration in EF Core?
Using migrations is a standard way to create and update a database with Entity Framework Core. The migration process has two steps: Creating migration and Applying migration. As we already said, our database schema must be aligned with the database model and every change in a database model needs to be migrated to the database itself.
Those changes might for example be:
- Changes in the properties of the model class
- Configuration changes
- Adding or removing the
DbSet<T>
properties from the context class
From ASP.NET Core 3.0, EF Core tools required for migrations are not preinstalled. Therefore, we have to install the Microsoft.EntityFrameworkCore.Tools
library. If you are following this series from the start, then you already have the Microsoft.EntityFrameworkCore
library installed.
Creating and Applying Migrations in EF Core
To create a migration, we can use Visual Studio’s Package Manager Console window or the command window (Windows command prompt). For the PMC window, the command is:
Add-Migration MigrationName [options]
Or through the dotnet CLI:
dotnet ef migrations add MigrationName [options]
In our application, we are going to use the PMC, so let’s do that by typing:
PM> Add-Migration InitialMigration
After we press the Enter key, our migration will be completed.
Actions that Take Place Behind the Scene
After we execute the Add-Migration
command EF Core does several things behind the scenes to prepare our migration. The first thing it does is inspect our class, associated entity classes (in this case only the Student
class), and any configuration we applied. After that, it creates three different files in the Migrations
folder:
The ApplicationContextModelSnapshot.cs
file holds the model of the database and it is updated every time a new migration is added. The other two files: InitialMigration
and InitialMigration.Designer
are files that contain and describe the newly created migration.
So, if you have followed all the steps from the previous articles, the content of the InitialMigration
file should look like this:
public partial class InitialMigration : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Student", columns: table => new { StudentId = table.Column<Guid>(nullable: false), Name = table.Column<string>(maxLength: 50, nullable: false), Age = table.Column<int>(nullable: true), IsRegularStudent = table.Column<bool>(nullable: false, defaultValue: true) }, constraints: table => { table.PrimaryKey("PK_Student", x => x.StudentId); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "Student"); } }
This file has two methods conveniently named Up()
and Down
. The Up()
method consists of commands that will be executed when we apply this migration. As an opposite action, the Down()
method will execute commands when we remove this migration (in this case it will just drop this created table).
Applying Created Migration
After we have successfully created our migration, we have to apply it for changes to take effect in the database. There are several ways of applying migrations (Using SQL scripts, using Database.Migrate
method, or using command line methods), and as we did with the creation, we are going to use the command line methods approach.
For the Package Manager Console, the command is :
Update-Database [options]
For the command prompt window, the command is:
dotnet ef database update [options]
Since we already decided on PMC, let’s open the PMC window and execute the command:
PM> Update-Database
After we press the Enter key, we are going to see all the different actions EF Core does for us to apply created migration. As a result, we are going to have our Student
table created with all the provided configuration from the previous articles:
There are few more important facts we have to know about EF Core’s migrations. If we inspect our database, we are going to find another created table: _EFMigrationsHistory
. EF Core uses this table to track all the applied migrations. So, this means that if we create another migration in our code and apply it, EF Core will apply only the newly created migration.
But how does EF Core know what migration needs to be applied?
Well, it stores a unique Id in the _EFMigrationsHistory
, which is a unique name of the migration file created with the migration, and never executes files with the same name again:
Each migration is applied within an SQL transaction, which means that whole migration either succeeds or fails. If we have multiple migrations to apply, then they will be applied in the exact order they are created.
Adding a Custom Code in a Migration File
We have already explained the purpose of the Up()
and Down()
methods in our InitialMigration
file. But all the code in those methods is generated by EF Core. If needed, we can add our custom code, too. We can use the MigrationBuilder
parameter to access the broad range of methods that can help us in the process. One of those methods is the Sql
method that we can use to add the custom code we like.
So, let’s open the InitialMigration
class and modify it by adding our custom code:
public partial class InitialMigration : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Student", columns: table => new { StudentId = table.Column<Guid>(nullable: false), Name = table.Column<string>(maxLength: 50, nullable: false), Age = table.Column<int>(nullable: true), IsRegularStudent = table.Column<bool>(nullable: false, defaultValue: true) }, constraints: table => { table.PrimaryKey("PK_Student", x => x.StudentId); }); migrationBuilder.Sql(@"CREATE PROCEDURE MyCustomProcedure AS SELECT * FROM Student"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "Student"); migrationBuilder.Sql(@"DROP PROCEDURE MyCustomProcedure"); } }
We should make sure to have the SQL method in the Down()
method to execute the opposite actions if we decide to remove our migration.
Now, we can delete our database (just to simulate the initial state in the SQL server) and only apply our migration again (we don’t need to create it, it is already created).
Creating Migration if Entities and Dbcontext Files are in a Separate Project
Right now, our model and context classes are in the main project together with the migration files. But in many real-life projects, models and context classes are in a separate project (repository pattern might be one of the reasons for example). For such projects, executing migrations couldn’t be possible with the setup we have in our project.
Let’s try to demonstrate what we mean.
The first thing to do is to create another .NET Core Class Library project and name it Entities
and install Microsoft.EntityFrameworkCore
and Microsoft.EntityFrameworkCore.Relational packages
via NuGet Package Manager or PMC window:
PM> Install-Package Microsoft.EntityFrameworkCore PM>Install-Package Microsoft.EntityFrameworkCore.Relational
Then we need to add the reference to the Entities
project in our main project.
After that, let’s copy the ApplicationContext
and the Student
classes, paste them in the Entities
project and remove the Entities
folder from the main project. Our structure should look like this:
As soon as we do that, we need to change the namespace in the ApplicationContext
and Student
classes from EFCoreApp.Entities
to just Entities
. Furthermore, we have to do the same thing for the using directives in the Startup
class and in all three migration files.
Having done all that, our project should build successfully.
Adding a New Migration
Now we can try to add another migration by typing:
PM> Add-Migration TestMigrationFromSeparateProject
But, as soon as we hit the Enter key, we are going to get an error message which explains that our EFCoreApp project doesn’t match our migrations assembly Entities. This error message is great because it provides us with an explanation of how to solve our problem.
All we have to do is to change our migrations assembly, so let’s do exactly that in the Startup
class:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("sqlConnection"), options => options.MigrationsAssembly("EFCoreApp"))); services.AddControllers(); }
For .NET 6 and above, we have to modify the Program class and use a slightly different code:
builder.Services.AddDbContext<ApplicationContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("sqlConnection"), options => options.MigrationsAssembly("EFCoreApp"))); builder.Services.AddControllers();
Now, we can run the same command again, but this time it will execute successfully. We have successfully created our new migration along with the migration files in the Migrations
folder:
We can see that the TestMigration
file has no code in the Up()
and Down()
methods, and this is normal because we didn’t change anything, but we completed our required task.
Removing a Migration
We’ve learned how to create migrations from a separate project. But as a result of that, we have created an empty migration that does nothing in our database. When we create a migration that we’re not satisfied with, we can easily remove it by typing the Remove-Migration [options]
command in the PMC window. So, let’s do that:
PM> Remove-Migration
After a few seconds our previous migration will be removed:
Excellent, now we can move on.
Seed Data in Entity Framework Core
In most of our projects, we want to have some initial data in the created database. So as soon as we execute our migration files to create and configure the database, we want to populate it with some initial data. This action is called Data Seeding.
We can create the code for the seeding action in the OnModelCreating
method by using the ModelBuilder
, as we did for the Fluent API configuration. So, let’s add a few rows into the Student
table:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Student>() .ToTable("Student"); modelBuilder.Entity<Student>() .Property(s => s.Age) .IsRequired(false); modelBuilder.Entity<Student>() .Property(s => s.IsRegularStudent) .HasDefaultValue(true); modelBuilder.Entity<Student>() .HasData( new Student { Id = Guid.NewGuid(), Name = "John Doe", Age = 30 }, new Student { Id = Guid.NewGuid(), Name = "Jane Doe", Age = 25 } ); }
So, we are using the HasData
method to inform EF Core about the data it has to seed. The rest of the code is self-explanatory because we are just adding the required data. We are not using the IsRegularStudent
property because we created a configuration for that property to have a default value.
Now we can create a new migration:
PM> Add-Migration SeedInitialData
And apply it:
PM> Update-Database
We can check out our table to inspect the result:
A Better Way for Applying Configuration and Data Seed
We can place all of the configuration code inside the OnModelCreating
method, and that will work as it supposed to. As we can see, our OnModelCreating
method is readable and easy to maintain. But, what if we had a larger project with more classes and more data to seed? Our method would become hard to read and maintain.
EF Core provides a better way for creating a Fluent API configuration by using the IEntityTypeConfiguration<T>
interface. By using it, we can divide the configuration for each entity into its own separate configuration class.
So, let’s see how to do that.
In the Entities
project, we are going to create a new folder Configuration
and inside a new class StudentConfiguration
:
public class StudentConfiguration : IEntityTypeConfiguration<Student> { public void Configure(EntityTypeBuilder<Student> builder) { throw new NotImplementedException(); } }
Of course, we don’t want to throw an exception (it is a default code after VS implements an interface), so, let’s modify this method:
public void Configure(EntityTypeBuilder<Student> builder) { builder.ToTable("Student"); builder.Property(s => s.Age) .IsRequired(false); builder.Property(s => s.IsRegularStudent) .HasDefaultValue(true); builder.HasData ( new Student { Id = Guid.NewGuid(), Name = "John Doe", Age = 30 }, new Student { Id = Guid.NewGuid(), Name = "Jane Doe", Age = 25 }, new Student { Id = Guid.NewGuid(), Name = "Mike Miles", Age = 28 } ); }
This code is a little bit different from the old OnModelCreating
code because we don’t have to use .Entity<Student>
part anymore. That’s because our builder object is already of type EntityTypeBuilder<Student>
. We have added an additional object to insert, just to have something to create a migration for.
All we have to do is to modify the OnModelCreating
method:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new StudentConfiguration()); }
And that is all.
We can now add a new migration and apply it:
PM> Add-Migration AdditionalRowInserted
PM> Update-Database
Setup the Initial Migration as Soon as Applications Starts
For every created migration, we had to apply its changes manually. And this is quite okay. But when we deploy our application, it would be nice to have initial data at that moment in the database.
What would be even nicer is that we don’t have to do that manually, but to start all the required migrations and seed all the required data as soon as the application starts.
Of course, besides being useful on deployment, it helps when sharing or developing our application with other people. We would like them to start the app and execute all the migrations before the app configures.
Well, we are going to show you how to do exactly that.
Creating an Extension Method
Let’s create a new class MigrationManager
in the Entities
project. It is going to be a static class because we are going to create an extension method to start all the migrations at the application’s startup:
public static class MigrationManager { }
Let’s continue with the .NET 5 explanation first, and then we will explain the implementation for .NET6 and above.
Now, since we are working on top of the Entities
class library project, we have to install Microsoft.ASPNetCore.Hosting.Abstractions library (we need this for the IHost type we are going to use in our extension method) and add the MigrateDatabase
extension method to this class:
public static class MigrationManager { public static IHost MigrateDatabase(this IHost host) { using (var scope = host.Services.CreateScope()) { using (var appContext = scope.ServiceProvider.GetRequiredService<ApplicationContext>()) { try { appContext.Database.Migrate(); } catch (Exception ex) { //Log errors or do anything you think it's needed throw; } } } return host; } }
We are using the IHost
type because this allows us to chain this method in the Program.cs
file and of course, as you can see, we need it for the main logic.
In .NET 6 and above, we have a different implementation:
public static class MigrationManager { public static WebApplication MigrateDatabase(this WebApplication webApp) { using (var scope = webApp.Services.CreateScope()) { using (var appContext = scope.ServiceProvider.GetRequiredService<ApplicationContext>()) { try { appContext.Database.Migrate(); } catch (Exception ex) { //Log errors or do anything you think it's needed throw; } } } return webApp; } }
So, we are creating a service scope and using it with the ServiceProvider
to obtain an instance of the ApplicationContext
class. In the first article, we discussed properties contained in the DbContext
class, and now, we are using one of them (Database) to call the Migrate
method for migration execution.
Applying the MigrateDatabase method
The next step is to call this method in the Program.cs
class:
public static void Main(string[] args) { CreateWebHostBuilder(args) .Build() .MigrateDatabase() .Run(); }
For .NET 6, we have to modify the same class, just a bit differently:
app.MigrateDatabase(); app.Run();
Finally let’s remove the Student and _EFMigrationsHistory tables from the database and remove the stored procedure in the Programmability folder, to simulate an empty database (or just drop your database). Then, we can start our application. We are going to see logs in a console window stating that migrations are executing. After the migrations have finished their work, we can check the database to confirm that all the tables and a procedure have been created again.
Reverting and Scripting Migrations
In one of the previous sections, we learned how to remove migration if we haven’t applied it. But in the case we have, we can’t remove it just like that, we need to revert it to the specific migration.
So, to show how migration reverting works, we are going to add another row in the StudentConfiguration
class, create, apply migration and then revert it back to the previous values.
Let’s first add another row to seed:
new Student { Id = Guid.NewGuid(), Name = "TEST Name", Age = 100 }
Then let’s create:
PM> Add-Migration RevertTestMigration
and apply migration:
PM> Update-Database
We can see in the database that a new row has been added. But as we are not satisfied with this migration (hypothetically), let’s revert it:
PM> Update-Database AdditionalRowInserted
The AdditionalRowInserted
migration was the previous one, and if we check our database now, we are going to see that it was reverted to the specific migration values.
Finally, if we want to make a SQL script of all our migrations, we can do that by typing:
PM> Script-Migration
This command will create a script file for us.
Conclusion
Excellent, we have learned a lot of different information about data migration and how to use it in various situations within EF Core.
So, to summarize, we have covered:
- How to create and apply migrations
- The way to add custom code in our migrations
- Using model and context classes from a different project for our migrations
- How to seed data and set up an initial seed as soon as the project starts
- The way to remove, revert migrations, and create script files
In the next article, we are going to learn more about the configuration of relationships in EF core.