Getting started with Entity Framework Code First Migrations
Steve Moss
A first-time walkthrough upgrading Entity Framework 4.2 to 4.3.1 and using Code First database migrations.
Why this post?
The ADO.NET Team Blog has a walkthrough on how to use migrations. This is a good start, but I found when doing it for myself, the post didn't cover everything that I had to do to make it work. So I've documented my experience here.
The main difference to the ADO.NET blog walkthrough is that this is for an upgrade from an existing EF4.2 model, rather than starting from scratch in EF4.3. I've found one breaking-change so far (in the naming convention used for keys in the database) and some additional steps that are needed to enable migrations for the first time.
What are Migrations?
While you are developing a domain model using Entity Framework Code First, you can automatically generate the database-schema from your domain model's code (hence the name).
Things get more difficult if you have multiple developers working on the project, each with their own local database. Similarly if the project has gone live and development is still continuing, it is not that easy to deploy the changes you've made to the live system.
This is where migrations come in. As you modify the Domain Model, migrations can be used to record a series of steps which can then be applied by other developers or to the live system. This includes pre-populating data, adding indexes, as well as running SQL commands for anything not directly supported by migrations at the moment.
Creating a Test Database
As an aside, Code First makes testing with a real database straightforward.
In the example below I set the initializer to DropCreateDatabaseAlways
so that as soon as the DbContext
is instantiated, the database will be dropped, recreated with the latest schema and then populated with known seed-data:
[TestFixture]
public class ContextInitializeTest
{
[TestFixtureSetUp]
public void Setup()
{
Database.SetInitializer(new CustomContextInitializer());
}
[Test]
public void Trigger_Context_Initialize()
{
var context = new TestContext("Entities");
var customers = context.Customers.ToList();
Assert.That(customers.Count(), Is.GreaterThan(0));
}
This initializer will drop the database, whether anything structural needs to change or not:
public class CustomContextInitializer :
DropCreateDatabaseAlways<CustomContext>
{
protected override void Seed(CustomContext context)
{
var seedData = new EntitySeedData();
context.Customers.Add(seedData.Customer1);
context.Customers.Add(seedData.Customer2);
base.Seed(context);
}
}
Starting Point
My starting point was an existing Entity Framework 4.2 Code First project, which I wanted to upgrade to 4.3.1 and start using migrations on.
I had previously used NuGet to install Entity Framework, and will be using Package Manage Console to run the commands for migrations.
Upgrade EF from 4.2 to 4.3
This looked easy. I'd originally installed Entity Framework 4.2 with NuGet. I just opened NuGet again, found EF4.3.1 and clicked install. The install process added 4.3, then tidied up by removing 4.2 from the installed packages. Later, I found a breaking-change which needs fixing before going any further:
Key-names breaking change
I only hit this problem when I came to delete some existing entities, using migrations. The issue is that the naming convention for SQL Server table Keys has changed from EF4.2 to EF4.3 when using code-first to generate the database:
This causes a problem, as migrations assumes the EF4.3.1 convention has been used and it generates update statements using the new naming convention. The existing key is then not found, and you receive an error such as:
Applying explicit migration: 201205011250118_RemoveQuestions. ALTER TABLE [Questions] DROP CONSTRAINT [FK_Questions_QuestionTypes_QuestionTypeId] System.Data.SqlClient.SqlException (0x80131904): 'FK_Questions_QuestionTypes_QuestionTypeId' is not a constraint. Could not drop constraint. See previous errors.
My workaround was simply to re-initialise my database, using the approach described in "What are Migrations?", above. This dropped & regenerated the database, using the new Key naming conventions.
If anyone has a solution that can be applied to an existing database, in production, please do add a comment at the end with your approach.
Enabling Migrations
The first command you need to run in Package Manager Console is "Enable-Migrations".
I had to resolve three problems before this would work:
Set the default project
The first problem was easy to fix. The Package Manager Console has a drop-down for the Default Project, and I had it set to my WebUI project, rather than the DomainModel project containing my Context. If you forget, you'll get the warning below:
Add a Default Constructor
Running Enable-Migrations again then failed for me with the error message:
The target context 'DomainModel.EntityFramework.CustomContext' is not constructible. Add a default constructor or provide an implementation of IDbContextFactory.
The problem here is that migrations tries to use the default (parameter-less) constructor to create an instance of the Context, but my Context did not have a default constructor. The reason being that I use Castle Windsor for dependency injection, and was using the IoC container to pass the connection string in, when it instantiates my Context.
I didn't want to spend time looking into the IDbContextFactory option, so to get things working I created a default constructor in my Context, and hard-coded the name of the connection string when calling the base DbContext:
public class CustomContext : DbContext
{
public CustomContext() :base("name=Entities") {}
Specify which Context to migrate
So I tried again. New error message this time:
PM> Enable-Migrations More than one class deriving from DbContext found in the current project. Edit the generated Configuration class to specify the context to enable migrations for.
The message was quite accurate. I do have more than one class deriving from DbContext in the project. In my case this is because I have a sub-class of my Context that adds tracing functionality (using the "Entity Framework Tracing Provider" downloadable from NuGet).
Running the command above adds a new "Migrations" directory to the project and a Configuration file:
Migrations\Configuration.cs
So, doing what the error message says, I opened the Configuration file and where marked with "TODO" I replaced the placeholder text with the name of my Context class:
internal sealed class Configuration :
DbMigrationsConfiguration</* TODO: put your Code First context type name here */>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(/* TODO: put your Code First context type name here */ context)
{
With that change migrations now knows which Context we're working with and we're ready to continue. I've not looked into what you do if you do have two independent Contexts in one project, that both need migrating, but my first thought would be to refactor the code so that each Context is in its own project.
Initialising Migrations
Preparing the initial migration code file
The next step is to initialise migrations so it knows what the model is now, and can can keep track of changes you made that you will later want to migrate. The command & its result was:
PM> Add-Migration Initial 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 201204271336508_Initial' again.
This adds a new file to the Migrations directory ("Add-Migration 201204271336508_Initial" in my case). The date is added to the name by migrations, so that it can keep track of the order in which multiple migrations might need to be applied.
Because my model & database already existed, this file just contains empty methods:
public partial class Initial : DbMigration
{
public override void Up()
{}
public override void Down()
{}
}
In subsequent migrations "Up" will contain the code to migrate a database to your new model and "Down" will contain the code to undo the change and revert back to the previous version.
Preparing the __MigrationHistory Table
Migrations keeps track of which code-files have been applied to the database using a System Table added to the database called __MigrationHistory. As I was upgrading from EF4.2 to 4.3, this table didn't exist. To fix this, you run another command (the -verbose parameter is optional):
PM> Update-Database -verbose Using NuGet project 'DomainModel'. Using StartUp project 'WebUI'. Target database is: 'Entities' (DataSource: .\SQLEXPRESS2008R2, Provider: System.Data.SqlClient, Origin: Configuration). Applying explicit migrations: [201204271336508_Initial]. Applying explicit migration: 201204271336508_Initial. ....
We're now ready to go and can start using Migrations!
Where's the connection string?
One nice feature I noticed is that you don't have to specify the connection string in the config file of the project that contains the Context, if it is already specified in the config file of a different StartUp project. In my case, my "DomainModel" project contains the Context, but the connection strings are specified in web.config of my "WebUI" project.
By setting my StartUp project to "WebUI" (view the Properties for your Visual Studio Solution, and set the StartUp project there) it seems to happily get the connection string from that project.
You can see this when the Update-Database command was run (above), in the line:
Using StartUp project 'WebUI'.
Migrating a Domain Model
Finally, we're ready to do an actual migration. And this part actually seems to work really well. I'll just give a quick intro here, as the main purpose of the post was to describe how to get things set up.
Make a change to the Domain Model
To test things out, I added a new field to an existing table:
public bool MigrationTest { get; set; }
Scaffolding the change into Code
Back to the Package Manager Console for another command, which detects what has changed. You need to give the migration a name, so I used "MigrationTest":
PM> Add-Migration MigrationTest Scaffolding migration 'MigrationTest'.
This prepares a code-file that encapsulates the change:
public partial class MigrationTest : DbMigration
{
public override void Up()
{
AddColumn("Tests", "MigrationTest", c => c.Boolean(nullable: false));
}
public override void Down()
{
DropColumn("Tests", "MigrationTest");
}
}
The name of the file includes the date/time, in my case:
201204271344007_MigrationTest.cs
Applying the change to the Development database
Now we run Update-Database again:
PM> Update-Database
But this time, migrations notices the new code-file we've just created and runs the "Up" method within it, to apply the changes to the database.
Applying the change to another Developer's database
Simples. Just check the migration code-files into source-control. The other developer checks them out and applies them to their own development version of the database using Package Manager Console.
Undoing our changes
Adding the field above was just a test, but undoing the change is a breeze. Just tell migrations to update the database to a specific, earlier, revision. In this case, I'm reverting back to the initial state:
PM> Update-Database -TargetMigration:201204271336508_Initial
This time the "Down" commands are run in the code-files to undo the changes made.
Applying the changes to a Production database
This is really where we've been trying to get to. We can now export a SQL script that includes everything that has changed from our initial database by running the command:
PM> Update-Database -Script -SourceMigration:$InitialDatabase -TargetMigration:"MigrationTest"
The SQL script is shown in a new tab in Visual Studio.
Or if you just want a script of the changes for one migration, you can use:
PM> Update-Database -Script -TargetMigration:"MigrationTest"
Summary
The main purpose of this post was to document my experience of setting up Code-First migrations, with hopefully some useful pointers about a few problems you might encounter along the way. The main difference to the ADO.NET blog walkthrough I provided a link to, is that this is for an upgrade, rather than starting from scratch in EF4.3.
First impressions are good. There is of course much more you can do to configure the migrations, which I've not even touched on here.
Comments
I came across the same key-name problem when using an EF4.2-created production database and later upgraded to EF4.3.<br /><br />Thanks to this blog post: http://davidbrycehoward.com/archive/2011/01/naming-and-renaming-database-constraints/ I was able to rename the constraint before dropping it.<br /><br />To do this, just add "Sql("EXEC sp_rename 'old_name', 'new_name'");" before the "DropForeignKey()" line. And don't forget to add the reverted version in the "Down()" method.<br /><br />Another solution would be to drop/add the FK using the "Sql()" method.
AlbanHi Alban<br />Useful tip - thanks for posting it. Since writing the post I've also started using the Sql() construct for data-motion during database migrations and find it works well.
Steve MossComments are closed