This was one of these frustrating days where you’re trying to get something done but it just doesn’t seem to work. In this case I was trying to get a custom Entity Framework 6 migration working. There is an excellent guide by Rowan Miller that explains exactly how it’s done but, well, it doesn’t work… His guide is from February 2013 so maybe stuff has changed since then, I don’t know. All other howtos I found have the same problem.

The TLDR version of my story is: don’t create a DbMigration yourself but let Visual Studio create it for you with the Add-Migration Powershell cmdlet. When you create it yourself, the Update-Database cmdlet doesn’t see your migration so it will never be executed. By simply using Add-Migration you can add an empty migration and update it as needed.

For anyone still reading this, I wanted to create a custom migration that creates a new schema in a SQL Azure database. Pretty simple stuff, it should run the statement: create schema <schema_name>. You can follow Rowan’s guide except where he creates the DbMigration implementation (the class with the Up and Down methods). According to Rowan, after registering our custom SqlServerMigrationSqlGenerator it should be as simple as calling the Update-Database cmdlet and your migration is executed. However, my brand new migration didn’t run: No pending explicit migrations.

In my next few attempts I tried specifying the name of my migration explicitly: Update-Database -TargetMigration:CreateSecuritySchema. Whatever name I tried, the error was always the same:

System.Data.Entity.Migrations.Infrastructure.MigrationsException: The specified target migration 'CreateSecuritySchema' does not exist. Ensure that target migration refers to an existing migration id.
   at System.Data.Entity.Migrations.DbMigrator.GetMigrationId(String migration)
   at System.Data.Entity.Migrations.DbMigrator.UpdateInternal(String targetMigration)
   at System.Data.Entity.Migrations.DbMigrator.<>c__DisplayClassc.b__b()
   at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   at System.Data.Entity.Migrations.Infrastructure.MigratorBase.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
   at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
   at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.Run()
   at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
   at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
   at System.Data.Entity.Migrations.Design.ToolingFacade.Run(BaseRunner runner)
   at System.Data.Entity.Migrations.Design.ToolingFacade.Update(String targetMigration, Boolean force)
   at System.Data.Entity.Migrations.UpdateDatabaseCommand.<>c__DisplayClass2.b__0()
   at System.Data.Entity.Migrations.MigrationsDomainCommand.Execute(Action command)

Checking the EF6 source code it appears that reflection is used to determine which migrations are available. The class MigrationAssembly maintains a list of migrations:

_migrations =
  (from t in migrationsAssembly.GetAccessibleTypes()
   where t.IsSubclassOf(typeof(DbMigration))
      && typeof(IMigrationMetadata).IsAssignableFrom(t)
      && t.GetPublicConstructor() != null
      && !t.IsAbstract()
      && !t.IsGenericType()
      && t.Namespace == migrationsNamespace
   select (IMigrationMetadata)Activator.CreateInstance(t))
     .Where(mm => !string.IsNullOrWhiteSpace(mm.Id) && mm.Id.IsValidMigrationId())
     .OrderBy(mm => mm.Id)

A few things of interest:

  • Your migration must be a subclass of DbMigration, makes sense.
  • Your migration must implement IMigrationMetadata. I had never seen or heard of this interface before. It defines three string getters: Id, Source and Target. Id makes sense, Source and Target are respectively the state of the model before/after this migration is run.
  • The migration class must exist in a specific namespace.

I you check migrations that are generated by the Add-Migration cmdlet, the IMigrationMetadata.Source is always null, the IMigrationMetadata.Target is a large base64-encoded string. Decoding it reveals that it is a binary file. Searching around reveals that the binary file is a compressed version of the data model EDMX file. So the Target is a snapshot of a particular state of your data model.

Instead of figuring out how exactly you implement IMigrationMetadata and specifically the Target property, I decided to let Add-Migration generate the necessary (empty) migration code for me and simply add my create schema migration to this empty migration. This worked without issues of course 🙂

Ronald Wildenberg

Author Ronald Wildenberg

Coming from an Artificial Intelligence background, turned developer after graduating. Interested in the tiny programming language details that make your life simpler but also in high-level designs that solve business problems in the most efficient way. And everything in between of course.

More posts by Ronald Wildenberg
6 July 2015

Leave a Reply