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
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) .ToList();
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
Targetare 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
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 :)