Telerik blogs
entity_framework_header

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.

What Happens When You Add a Migration

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:

  • Entity Framework reviews the models in your solution that you have mapped;
  • Entity Framework checks the existing migrations in your solution and determines what changes have been made since the last migration;
  • A "script" of the changes is built and Entity Framework adds the change "script" to a migration file;
  • The migration file is named with a timestamp and a brief description of the changes;

    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

    ef1

A Closer Look at the Migration 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).

ef2

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.

ef3

The __MigrationHistory Table

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:

  • Looks at the records in the __MigrationHistory table, comparing each record to the migrations in your solution;
    • It will always look at the migrations in the order of the timestamps that are appended to the beginning of the migration names (remember the timestamps we talked about earlier);
    • Any migrations that do not exist in the database (but exist in your code) will be applied to your database in the order of the timestamps;
    • A hash of the current domain classes is created again and compared to hashed domain classes value for the last migration applied to the database (as stored in the __MigrationHistory table);
  • If the computed hash does not match the hash value stored in the __MigrationsHistory table, Entity Framework will not proceed and you will receive and error (in most cases);
    • The exception is, when automatic migrations are enabled, Entity Framework will attempt to update your database however it sees fit;
  • Once all migrations have been properly applied then the Seed method will be called (see below).

    ef4

Be Careful with Migrations in a Team Setting

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.

The Easy Way to Fix Code First Migration Errors

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.

My Preferred Way to Fix Code First Migration Errors

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.

ef5

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:

ef6

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.

    ef7

  • 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.

Conclusion

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:

  • Entity Framework uses the __MigrationHistory table to keep track of the changes that were applied to your database;
  • Entity Framework creates a hash of the project's current model state and compares this against the model state store in the __MigrationHistory table to determine if the database is current;
  • Migrations are always applied in the order of the timestamp (ascending order).

Telerik Blogging Ninja
About the Author

The Telerik Team

 

Comments

Comments are disabled in preview mode.