Entity Framework Wonderland: Migrations and Seeding

This post is a practical dive into Entity Framework 6 migrations and seeding in an ASP.NET MVC 5 web application.

Initial Setup

Suppose we have the following model:

    public class Entity1
    {
        public int Id { get; set; }
        public DateTime Created { get; set; }
        public string Value { get; set; }
    }

    public class MyContext : DbContext
    {
        public MyContext() : base("MyDb")
        {
        }

        public virtual DbSet<Entity1> Entity1 { get; set; }
    }

Add a new MVC 5 controller with views, using Entity Framework  and call it Entity1Controller.

Now, if you run the web application and go to http://localhost:xxxxx/Entity1, the database should be automagically created.

Making a Model Change

Suppose I add another string property (Value2) to Entity1 and hit F5 to run the web application again, I get an exception:

An exception of type ‘System.InvalidOperationException’ occurred in EntityFramework.dll but was not handled in user code

Additional information: The model backing the ‘MyContext’ context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).

OK, let’s fix that. Open Package Manager Console and type

Enable-Migrations

This will add two files to the Migrations folder:

  • <date and time>_IniticalCreate.cs
  • Configuration.cs

Configuration looks like this:

    internal sealed class Configuration : DbMigrationsConfiguration<Models.MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
            ContextKey = "EFMigration1.Models.MyContext";
        }

        protected override void Seed(Models.MyContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
            //  to avoid creating duplicate seed data. E.g.
            //
            //    context.People.AddOrUpdate(
            //      p => p.FullName,
            //      new Person { FullName = "Andrew Peters" },
            //      new Person { FullName = "Brice Lambson" },
            //      new Person { FullName = "Rowan Miller" }
            //    );
            //
        }
    }

Let’s add a seed:

            context.Entity1.Add(new Models.Entity1
            {
                Created = DateTime.Now,
                Value = "foo",
                Value2 = "bar"
            });

Now hit F5 again. The same exception again! Well, looking at the initial migration, it is understandable, because it doesn’t contain the newly added property. Let’s create a new migration:

Add-Migration Value2

Hit F5 again. Boom! The same exception again. A simple solution now would be to use the Update-Database command. If we do that, the Value2 column is added and the seed data is inserted. Now F5 works.

This is fine for a development environment, but for testing and production it is IMHO not suitable. We have two options:

  1. Configure the application to migrate automatically as outlined in Code First Migrations on MSDN.
  2. Use Migrate.exe

Configuration

Migrate to Latest Version

Enabling automatic migration involves one line of code:

Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());

In this case, I choose to Create a new class EfConfig in the App_Start folder, and call it from Global.asax.cs. This will run migrations if necessary, and seed every time.

Note: Don’t confuse this with the AutomaticMigrationsEnabled property, which will effectively disable versioning of your database.

Create Database If Not Exists

As an aside note, the default initializer is CreateDatabaseIfNotExists, which means that the database is created if it does not exists, but not upgraded when the model changes. You can also set this explicitly, but there is no configuration parameter. So the question is: How do you seed in this case? The solution is to create a custom initializer that derives from CreateDatabaseIfNotExists and move the seeding to that:

    internal class MyInitializer : CreateDatabaseIfNotExists<Models.MyContext>
    {
        protected override void Seed(Models.MyContext context)
        {
            context.Entity1.Add(new Models.Entity1
            {
                Created = DateTime.Now,
                Value = "Initializer seed",
                Value2 = "baz"
            });
        }
    }

Then, use this initializer instead:

Database.SetInitializer(new MyInitializer());

Since migration is only done once, seed is only called once.

Note however that if you do Update-Database, it is the Seed method in Configuration that is used!

Use Migrate.exe

If you want more control of the migration process, you could manually, or as part of the deployment process, call Migrate.exe, which is located in the tools subfolder of the Entity Framework package:

pushd
copy ..\packages\EntityFramework.6.1.3\tools\migrate.exe bin
cd bin
migrate.exe EFMigration1.dll /startupConfigurationFile=..\Web.config /verbose
del migrate.exe
popd

This will run migrations if necessary, and seed.

Advertisements