Entity Framework Code First Migrations
Migrations (EF 4.3+)
You need to use NuGet's Package Manager Console. Get used to lots of typos.
- Install-Package EntityFramework
- Enable-Migrations [-EnableAutomaticMigrations]
Generates Migrations/Configuration.
If the database was created by EF CF (there's a __MigrationHistory table) you'll get an InitialCreate migration- -ProjectName MyProjectWithContext : You may need to set the project name, or you'll get "No classes deriving from DbContext found" and migrations in the wrong project.
- -ContextTypeName MyContext : If you have more than one context in a project
- Add-Migration name : adds a migration (if automatic migrations, scaffolds it)
If you have errorUnable to generate an explicit migration because the following explicit migrations are pending: [201202100921137_Initial]. Apply the pending explicit migrations before attempting to generate a new explicit migration.
:
- -ProjectName MyProjectWithContext : as above
- -StartUpProjectName:ProjectWithConfig : where the config file is. A UI, console or test project.
- -IgnoreChanges : from 4.3.1.0, generate an empty migration. Useful for a existing database.
- -Context: Specify a content if multi-context
- EF <7: Update-Database : runs a migration
- -Verbose : show Sql
- -TargetMigration:"migrationName" -Force : migrate to named migration
- -Script : script from last migration
- -Script -SourceMigration:$InitialDatabase -TargetMigration:name script from/to.
Use this to generate a script over a number of migrations ("all migrations for version 2") - Update-Database -TargetMigration:$InitialDatabase (or 0) rolls back to the start.
- EF 7+: Apply-Migration
- For scripts, instead use Script-Migration
Migrations via Database.SetInitializer
This generated Migrations.Configuration class is internal sealed- if you have a Database.SetInitializer with a outside the data project, you can change it to public. You don't need to do it if you configure in the app.config.
The MigrateDatabaseToLatestVersion initializer requires the DbContext and Configuration.
Database.SetInitializer(new MigrateDatabaseToLatestVersion<NorthwindContext, Migrations.Configuration>());
You can also configure the initializer in the app.config. (MSDN)
<entityFramework>
<contexts>
<!--<context type="Data.Context.MyContext, Data" disableDatabaseInitialization="true" />-->
<context type="Data.Context.MyContext, Data" disableDatabaseInitialization="false">
<databaseInitializer type="System.Data.Entity.MigrateDatabaseToLatestVersion`2[[Data.Context.MyContext, Data], [Data.Migrations.Configuration, Data]], EntityFramework" />
</context>
</contexts>
</entityFramework>
Automatic Migrations
Once you have an initial migration, it tracks all changes so there are no further explicit migrations. Unless you have an existing database- you'll need to add-migration Initial -IgnoreChanges and update-migration to get the __migrationHistory table initialized. See below!
If you delete columns (=dropping a column) it throws an AutomaticDataLossException. You can suppress this in configuration (but consider it carefully...).
public Configuration()
{
AutomaticMigrationsEnabled = true;
//if you drop columns - consider this carefully...
AutomaticMigrationDataLossAllowed = true;
}
Migration Code
You can't refer to the actual entities (you must follow the naming conventions or map manually). The overuse of lambda expressions is awkward, but you can do lower-level things like naming foreign key constraints and add indexes.
Extensibility is limited. You cannot define new MigrationOperations (DbMigration.AddOperation is private), only anonymous arguments to the existing builder methods.
public override void Up()
{
CreateTable(
"Products",
cb => new
{
Id = cb.Int(nullable: false, identity: true),
ProductName = cb.String(nullable: false, maxLength: 20),
CategoryId = cb.Int(nullable: true)
})
.PrimaryKey(t => t.Id)
.ForeignKey("Categories",
t => t.CategoryId,
name: "FK_Products_Category");
}
Migrations in Code
To run an update in code, use a DbMigrator.
public void RunUpdate(string connectionName)
{
var configuration = new Configuration();
//will use app.config/connectionStrings/add[@name=connectionName]
configuration.TargetDatabase =
new System.Data.Entity.Infrastructure.DbConnectionInfo(connectionName);
var migrator = new DbMigrator(configuration);
migrator.Update();
}
Note that DbMigrator exposes three methods:
- GetLocalMigrations() -reflects what's in the Migrations folder
- GetDatabaseMigrations() - reads what's in the database (__MigrationHistory).
NB: check where it's looking- it may be looking for .\SQLEXPRESS\Mynamespace.MyContext
In the console you may have to set StartUpProjectName; in code set the TargetDatabase - GetPendingMigrations() - the difference between what's in Local but not in Database
It won't let you AddMigration if there are pending migrations ("Apply the pending explicit migrations" error quoted above).
You can control the migration SQL generation. The hook is in Migrations.Configuration:
internal sealed class Configuration : DbMigrationsConfiguration<EntitiesContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
SetSqlGenerator("System.Data.SqlClient", new CustomizedSqlGenerator());
}
The customized SQL generator must derive from MigrationSqlGenerator (or the SqlServer version SqlServerMigrationSqlGenerator) and then override the Generate methods.
Scripting
You can take control of the scripts as long as you use insert the __MigrationHistory row. In the console use Update-Database -Script or script as follows:
public string WriteCreateDatabaseScript(DbContext context)
{
var objectContext = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)context).ObjectContext;
return objectContext.CreateDatabaseScript();
}
public string WriteMigrationScript()
{
var configuration = new Configuration();
var migrator = new DbMigrator(configuration);
var scriptor =
new System.Data.Entity.Migrations.Infrastructure.MigratorScriptingDecorator(migrator);
//source, target migration names
string script = scriptor.ScriptUpdate(null, null);
return script;
}
Code First for an existing database
DbMigrator presumes it has control of the database. Since EF 6.1, you can have multiple contexts per database with the migration ContextKey (you cannot have overlapping tables - it's not true multi-tenant).
- Make sure the OnModelCreating mappings are all correct. Or generate it by using DatabaseSchemaReader's CodeGen
- Enable migrations, create an initial migration and get the script:
PM> Enable-Migrations
Code First Migrations enabled for project Northwind.
PM> Add-Migration Initial -IgnoreChanges
Scaffolding migration 'Initial'.
The Designer Code for this migration file includes a snapshot of your current Code First model. This snapshot is used to calculate the changes to your model when you scaffold the next migration. If you make additional changes to your model that you want to include in this migration, then you can re-scaffold it by running 'Add-Migration 201202100921137_Initial' again.
PM> Update-Database
Applying explicit migrations: [201202100921137_Initial].
Applying explicit migration: 201202100921137_Initial.
To port this to a remote database, use Update-Database -Script which should just contain the __MigrationHistory DDL and INSERT. From here on, Code First thinks it owns the database.
The script may fail: "Conversion failed when converting date and/or time from character string". This is because the migrationHistory insert is in the form '2012-03-03T15:07:35.431Z' and the database is set to compatibility with SqlServer 2000. You have to fix the databse: in SqlServer Management Studio, right click database, Properties-Options-Compatibility Level set to SqlServer 2005 (90) or above.