Entity Framework CodeFirst Migrations, launching migrations with upgradeable to newest changes

We are running Entity Framework 5 with Code First and Migrations Included . Starting with database initializer:MigrateToLatestVersion

We have several clients working on the latest stable branch. Even though we are developing new code in our connector / master, sometimes we need to connect to the forked client database fx. to debug any data error.

It can be "dangerous" if someone forgot to switch to the branch the client is running on, because the migration will then update the client database to match whatever code is running.

One solution would be to run a different initializer that does not carry the latest version. But that will mean a lot more work when we roll out new systems to new customers, or someone new joins the team and needs go up and work.

We solved this problem by setting a bool in app.config to set whether the code should be migrated to the latest version, then always false for development, and then convert it when deployed to clients.

This gives us the advantage of still updating the database to the newest version, but not the danger of a developer accidentally connecting to the system with an old version of code and migrations that destroy that database.

So now that we have this, we should definitely check it:

(simplified code)

if(Config.MigrateToLatestVersion || !databaseExists) 
{
  var initializer = new MigrateToLatestVersion<MyContext,MigrationConfiguration>();
  Database.SetInitializer(initializer);
  using(var context = new MyContext())
  {
    context.Database.Initialize(true)
  }
}

      

My question was how to check if the database exists without performing migration, but I found out that you can do this:

Database.SetInitializer<MyContext>(null);
var context = new MyContext();
var databaseExists = context.Database.Exists();

      

But if I only run the initializer MigrateToLatestVersion

when the database does not exist or when I manually run Update-Database from the Package Manager Console.

I have 2 problems, firstly, I don't get an exception anymore if my model is different from the database, and I will like it anyway. second: it doesn't run the seed method located in my MigrationConfiguration which I still want to run.

Any suggestions on how I can get the Migrations Initializer running to get all the benefits, but still there is something to prevent accidentally damaging our production environment by accident?

+3


source to share


1 answer


So the solution I went with was to create a new DatabaseInitializer called MigrateDatabaseToLatestIfLocal

After seeing how the MigrateDatabaseToLatestVersion initializer works , I did something very similar, with the difference in checking if the database exists and running locally or remotely (using config transformations to determine this. The remote system has a datasource converted to a config file.)



public class MigrateDatabaseToLatestIfLocal<TContext, TMigrationsConfiguration> : IDatabaseInitializer<TContext>
        where TContext : DbContext
        where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
    {
        private DbMigrationsConfiguration _config;

        public MigrateDatabaseToLatestIfLocal()
        {
            this._config = (DbMigrationsConfiguration)Activator.CreateInstance<TMigrationsConfiguration>();
        }

        public MigrateDatabaseToLatestIfLocal(string connectionStringName)
        {
          MigrateDatabaseToLatestIfLocal<TContext, TMigrationsConfiguration> databaseToLatestVersion = this;
          var instance = Activator.CreateInstance<TMigrationsConfiguration>();
          instance.TargetDatabase = new DbConnectionInfo(connectionStringName);
            databaseToLatestVersion._config = instance;
        }

        public void InitializeDatabase(TContext context)
        {
            var databaseExists = context.Database.Exists();

            var migrator = new DbMigrator(this._config);
            var pendingMigrations = migrator.GetPendingMigrations().OrderByDescending(s => s);
            var localMigrations = migrator.GetLocalMigrations().OrderByDescending(s => s);
            var dbMigrations = migrator.GetDatabaseMigrations().OrderByDescending(s => s);


            var isRemoteConnection = FunctionToFindOutIfWeAreRemote(); //here we check the config file to see if the datasource is a certain IP, this differentiates locally and remotely because of config tranformation.

            if (isRemoteConnection && databaseExists)
            {
                if (pendingMigrations.Any())
                {
                    throw new MigrationsException("You are not allowed to automatically update the database remotely.")
                }
                if (localMigrations.First() != dbMigrations.First())
                {
                    throw new MigrationsException("Migrations in code and database dont match, please make sure you are running the code supported by the remote system. ");
                }
            }
            else
            {
                //we are local, fine update the db and run seeding.
                //we are remote and the db does not exist, fine update and run seed.
                migrator.Update();
            }
        }
    }

      

This may be a very special case, but for me it provides some safety when running the first code migrations, which will ensure you don't accidentally migrate the live environment by accident

+4


source







All Articles