I am currently developing version 2 of an application that has an existing code base of version 1 users, but I also need to roll out the new version to new users (i.e. clean installs). Version 1 uses Entity Framework 4.1. Version 2 uses Entity Framework 5.
My problem is that I need to be able to upgrade existing version 1 users to version 2 and migrate their database to the new version’s schema. The new schema adds a number of new tables, leaves a couple of existing tables untouched and drops other tables.
I have discovered on my travels that EF4.1 does not contain a _MigrationHistory table. So I’ve gone back to my original version 1 code, upgraded it to EF5 and run an initial migration as follows:
Add-Migration Initial -IgnoreChanges
This works nicely to get an original version 1 (EF4.1) database ready for migrations (i.e. add the _MigrationHistory table). I then port this over to my version 2 code and run:
Update-Database
which creates the _MigrationHistory table and then I run:
Add-Migration Version2
which generates a nice migration of the database up to version 2.
This works perfectly if the database already exists at either the version 1 schema or the version 2 schema (no migrations are applied in the latter case) as I am using the following initialiser:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Configuration>());
However, if the database does not exist, the Initial migration is successfully run, but the Version2 migration fails, as some of the migration steps drop tables, indexes and foreign keys which obviously don’t exist in an empty database.
So I’m completely at a loss — how do I migrate existing version 1 users to version 2 (and preserve their data), but also support new installs that need to create the database entirely from scratch?
I’ve seen lots of ideas about creating and running SQL scripts to migrate, but I really need this to be done automatically on startup of my app. End-users should really only need to run an MSI file to be able to upgrade.
UPDATE: Here’s the solution as suggested below by w.brian (sorry I can’t up-vote yet):
string connectionString = ConfigurationManager.ConnectionStrings["DbContext"].ConnectionString;
if (Database.Exists(connectionString))
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DbContext, Migrations.Configuration>());
}
else
{
Database.SetInitializer(new DefaultDataInitialiser());
}
UPDATE2: The above appears to work the first time through a new create. However, the next run passes the Database.Exists() check and attempts to migrate the database. However, as there is only a single entry in the _MigrationHistory table (generated by the new/clean install) and the migration has two entries (one for the EF4.1 migration; one for the new schema), the migration logic starts from the beginning and attempts to apply each migration.
This fails as it attempts to create tables that are already present in the database — it created these as part of the clean install!
So the crux of it is, this approach messes up the migration logic.
UPDATE3: So I think I’ve figured out how to create the initial database and allow for subsequent runs to only apply migrations. From above, if the database already exists, continue with the migration code.
If the database doesn’t already exist, use the DefaultDataInitialiser and in the Seed() method, perform the following:
context.Database.ExecuteSqlCommand(@"DELETE FROM [__MigrationHistory]");
byte[] version1Model = ConvertHexStringToByteArray("1F8B...");
context.Database.ExecuteSqlCommand(@"INSERT INTO [__MigrationHistory]
([MigrationId]
,[Model]
,[ProductVersion])
VALUES
('201302082120145_Initial'
,{0}
,'5.0.0.net40')", new object[] { version1Model });
byte[] version2Model = ConvertHexStringToByteArray("1F8B...");
context.Database.ExecuteSqlCommand(@"INSERT INTO [__MigrationHistory]
([MigrationId]
,[Model]
,[ProductVersion])
VALUES
('201302082200350_Version-2'
,{0}
,'5.0.0.net40')", version2Model);
This gets the new database “fully migrated” from the migration logic’s point of view. The next time the application is run, the database exists and the MigrateDatabaseToLatestVersion magic runs, sees that the database is at the latest version (or in the future upgrades to the next migration) and is happy.
Unfortunately, every new migration needs to be added into this Seed() method in the future for when the database is created from scratch.
And where did I get the hex string used for the model? I copied it from SQL Server Management Studio from a migrated version of the database.
I would only use the
MigrateDatabaseToLatestVersioninitializer if upgrading, and not when it is a new installation. This will require you to query SQL Server directly to see if the database exists, but I don’t think this should be too difficult since you have access to the connection string.