It's well known around the office that I have a love/hate relationship with Entity Framework. Entity Framework is an ORM that provides many features aimed to make your life as a developer much easier.
One of these features that you can take advantage of as a developer is Code First Migrations. Code First Migrations enable you to easily keep your domain classes and database schema in sync. Although Entity Framework works behind the scenes to make this process easy through a certain degree of automation, when things go wrong, the automation can feel like it is working against you.
In this post, we will explore how Code First Migrations work to demystify the automation and help you understand what's going on behind the scenes.
Before digging into some issues you may run into when using Code First Migrations, let's take a quick step back and review what happens when you add a migration to your solution. When you run the Add-Migration cmdlet, you will get a new file added to your solution matching what you named your migration.
Here's what is happening behind the scenes:
Note: the timestamp is important because Entity Framework uses it to incrementally (and in an orderly manner) apply them to your database
In addition to the change "script", a hash of the current state of your domain classes is created and stored in a nested resource file
Inside the resource file, you will find two key/value pairs: one for the Default Schema (named DefaultSchema) and another for the domain class hash (named Target).
The Default Schema describes the schema prepended to database objects within your database. The Target key/value pair is used by the internals of Entity Framework, and is stored within the __MigrationHistory
table of your database.
The __MigrationHistory
table is automatically created by Entity Framework and is used to track the state changes that have been applied to your database. At a high-level, when Entity Framework applies your migrations to the database, the __MigrationHistory
table is consulted and compared to your migrations stored in your compiled project, applying the missing migrations.
Now, let's take a closer look at the __MigrationHistory
table, and get to the details of Code First Migrations.
When you run your project or when you run the Update-Database command in the Package Manager Console, Entity Framework does the following things:
__MigrationHistory
table, comparing each record to the migrations in your solution;
__MigrationHistory
table);__MigrationsHistory
table, Entity Framework will not proceed and you will receive and error (in most cases);
Once all migrations have been properly applied then the Seed method will be called (see below).
Remember when Entity Framework applies migrations, they are always applied in order by the timestamp (ascending). If you are working on a project with a team or a project with multiple source branches and are branching and merging, you may run into issues with migrations due to the aforementioned rule on how they are applied. Let's take a look at an example when the timestamp rule can cause some trouble.
Imagine you are working with another developer and you update your domain classes, adding a migration to the solution. You continue working and are ready to check in your changes but like any good developer you get latest code and run the project to make sure you've merged all the code successfully before committing your changes. When you run the Update-Database command, you get the following message:
Unable to update database to match the current model because there are pending changes and automatic migration is disabled. Either write the pending model changes to a code-based migration or enable automatic migration. Set DbMigrationsConfiguration.AutomaticMigrationsEnabled
to true to enable automatic migration.
You can use the Add-Migration command to write the pending model changes to a code-based migration.
Uh oh! This can happen for a variety of reasons. In most cases you and your teammate made changes to the same object, or the changes you are making are dependent upon your teammate's changes. Either way, it's now your responsibility to fix the issue before you check in your changes so that your other teammates don't have the same issue.
One quick and simple way to fix this would be to enable automatic migrations. While this may be a quick solution, you may encounter undesirable side effects on your database models as your domain becomes more complex.
I am a strong believer in developers using the tools at their disposal, but I also believe that developers need to have an understanding as to what the tool is doing. For these reasons, I recommend not using automatic migrations in a production setting, so that you and your team can control the changes being made to your database schema. Perhaps it's a trust issue (or even paranoia), but fully-automated schema changes in production give me the willies.
This short example demonstrates my preferred way to fix Code First Migration error. Let's assume the highlighted migration RemoveName is your teammates migration that we want to ensure is applied first. When merging code, it is a best practice to run your migrations after any other migrations. This is so you can ensure the changes that any of your teammates have made are retained before any database changes that you have made are applied.
Here is a view of the migrations in my solution after I merge my code. Notice how RemoveName is showing up as the last migration so we will need to move AddLastName to be applied after our teammates' migration.
The Owner
class is a view of the class after I have merged my code. My teammate has removed the name field but I have added it back because I need to use this field.
public class Owner
{
public int Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
This is a view of your migration after the merge.
public partial class AddLastName : DbMigration
{
public override void Up()
{
AddColumn("dbo.Owners", "LastName", c => c.String());
}
public override void Down()
{
DropColumn("dbo.Owners", "LastName");
}
}
This is the migration that my teammate created.
public partial class RemoveName : DbMigration
{
public override void Up()
{
AddColumn("dbo.Owners", "FirstName", c => c.String());
DropColumn("dbo.Owners", "Name");
}
public override void Down()
{
AddColumn("dbo.Owners", "Name", c => c.String());
DropColumn("dbo.Owners", "FirstName");
}
}
Here is a look at the migrations that have been applied to our database thus far:
We can fix this in a few easy steps and ensure that we maintain the migration steps that our teammate applied to our domain.
Rollback both of the migrations by targeting the one prior to the ones that were added (CreateOwner in this case)
PM> Update-Database -TargetMigration CreateOwner
Specify the -Verbose
flag to view the SQL statements being applied to the target database.
Reverting migrations: [201603111253096\_RemoveName, 201603101254121\_AddLastName].
Reverting explicit migration: 201603111253096\_RemoveName.
Reverting explicit migration: 201603101254121\_AddLastName.
PM>
Rename the RemoveName migration and give it a timestamp prior to your migration.
Also update the IMigrationMetadata.Id
proprerty in the designer file to return the updated name (the string that is returned is what will be written to the __MigrationsHistory
table, which needs to match the filename of your Migration)
[GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")]
public sealed partial class RemoveName : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(RemoveName));
string IMigrationMetadata.Id
{
get { return "201603101253096\_RemoveName"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
Update the database to the migration prior to yours.
PM> Update-Database -TargetMigration RemoveName
Specify the -Verbose
flag to view the SQL statements being applied to the target database.
Applying explicit migrations: [201603101253096\_RemoveName].
Applying explicit migration: 201603101253096\_RemoveName.
Update the model state in your migration to match the model state in the project after the merge.
PM> Add-Migration AddLastName -Force
Re-scaffolding migration 'AddLastName'.
PM>
public partial class AddLastName : DbMigration
{
public override void Up()
{
AddColumn("dbo.Owners", "Name", c => c.String());
AddColumn("dbo.Owners", "LastName", c => c.String());
DropColumn("dbo.Owners", "FirstName");
}
public override void Down()
{
AddColumn("dbo.Owners", "FirstName", c => c.String());
DropColumn("dbo.Owners", "LastName");
DropColumn("dbo.Owners", "Name");
}
}
Take notice here that migration has changed slightly from what it was before we merged and fixed our migrations so they would be applied properly.
Update your database!
PM> Update-Database
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Applying explicit migrations: [201603101254121\_AddLastName].
Applying explicit migration: 201603101254121\_AddLastName.
Running Seed method.
So what have we learned about Entity Framework? Hopefully at this point you have a deeper understanding of how code first migrations work. To help your sanity while working with Entity Framework and code first migrations keep in mind the following:
__MigrationHistory
table to keep track of the changes that were applied to your database;__MigrationHistory
table to determine if the database is current;