How to Achieve Zero-Downtime Sitecore Deployments (EF Automatic Migrations) – Part III

Share Button

There are different reasons to encourage developers to use Entity Framework (EF) Code First, the most important reasons to me are the ability to version control the database schema and the database migration feature which creates/updates the database schema from the code-base model.

Check this link for more information about the EF Code First.

If the migration feature is used, your application will check if the database has the latest schema – typically on app start up. The schema then might get updated to accommodate the new changes such as adding new table or fields to the database.

Assume that you’ve got a Sitecore multi-instance  environment that uses a custom database managed by EF Code First, e.g. an error logging database. The custom database is accessible from every Sitecore instance in the environment. Also, assume that the environment is setups and you want to add a new field to the database to log extra information such as the machine name.

In entity framework, you’ve got two options to implement your migrations. Manual Migration or Automatic Migration; the former allows you to specify the migration steps and will give you the ability to customize the migration process as well. The latter works like magic, where EF automatically detects the schema changes and does the migration if needed. Both methods will store the migration information in the __MigrationHistory table.

This article will focus more on the automatic migrations as it’s more challenging to use in a multi-instance environment. The typical configuration class for EF migrations looks like:

public sealed class Configuration : DbMigrationsConfiguration<MyLoggingDbContext>
{
 public Configuration()
 {
     AutomaticMigrationsEnabled = true;
     //AutomaticMigrationDataLossAllowed = true;
 }
 protected override void Seed(YPODbContext context)
 {
 }
}

and your application start should look like:

public override void Application_Start()
{
  base.Application_Start();
  System.Data.Entity.Database.SetInitializer(new IntegrationDbCustomInitializer());
  GetDbContext().Database.Initialize(true);
}

And the IntegrationDbCustomInitializer is:

public class IntegrationDbCustomInitializer : MigrateDatabaseToLatestVersion<MyLoggingDbContext,Configuration>
{
   public IntegrationDbCustomInitializer()
   {
   } 
}

Where the AutomaticMigrationsEnabled is set to true and the AutomaticMigrationDataLossAllowed is set to false by default so EF won’t drop any tables or columns from the database to avoid causing data loss.

So, how EF Code First can be used in a multi-instance environment?

If we are following the same deployment procedure described in Part I, the code will be deployed to the CM server before the CD servers. Once the code is deployed to the CM server, the schema of the custom database will get updated and the new columns/tables will be added. At this point, the CD servers – still having the old database model – may try to validate the schema of the database, at which point the new columns in the database will conflict with the CDs code-base model. As the AutomaticMigrationDataLossEnabled flag is set to false, the CD servers will show the yellow screen of death and will throw the AutomaticDataLossException.

So, someone may think setting the AutomaticMigrationDataLossEnabled to true will fix the problem. Well, this is a horrible hack, it will fix this problem but will cause a lot of other problems. Setting this flag to true will cause data loss as the name of the flag suggests. Also, in a multi-instance environment, multiple servers with different code-base models will keep updating the schema of the database to match their models. Till the deployment is complete, the servers will keep racing to update the schema of the database in an uncontrolled way.

The solution to this problem can be achieved by controlling when EF can (1) check and (2) apply the migration. In order to do that, the code in the global asax file will need to be changed to:

public override void Application_Start()
{
  base.Application_Start();
  System.Data.Entity.Database.SetInitializer<MyLoggingDbContext>(null);
}

The code above tells the entity framework to access the database without using the MigrateDatabaseToLatestVersion migration.

Also, you will need to create a custom admin page or a Sitecore powershell commandlet to apply the migration whenever needed by running the following code:

  System.Data.Entity.Database.SetInitializer(new IntegrationDbCustomInitializer());
  GetDbContext().Database.Initialize(true);

So, the code deployment scenario should be as follows:

  1. Deploy the code to the CM server.
  2. Take the first CD out of the load balancer.
  3. Deploy the code to the CD server.
  4. Run the EF migration code.
  5. Bring the first CD server online.
  6. Repeat steps 2-5 to the second CD server.

Please note that the above steps may need tweaking based on different factors such as the changes in the model (adding, removing or renaming entities), using manual migrations with custom migration code, and the number of CD servers in the environment.

 

 

 

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *