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) .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 threestring
getters:Id
,Source
andTarget
.Id
makes sense,Source
andTarget
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 :)