Using Code-First Migration With SimpleMembership

In previous posts I have written about customizing and seeding SimpleMembership, the newest membership provider used in ASP.NET MVC 4 Internet applications.  In the example in this previous post we setup a database initializer that can be set to either to a type of DropCreateDatabaseAlways or DropCreateDatabaseIfModelChanges. As the name implies DropCreateDatabaseAlways will drop the database and recreate it every time the initializer is called, which is at application start, and will run the method to seed the database after creation.  DropCreateDatabaseIfModelChanges varies in that it will only recreate the database if the database model changes in your code.  Both of these approaches work great during development and unit testing but you would not want to deploy the solution this way for production.  This is where code-first migration comes in.

Code-first migration allows you to update the database without having to recreate it and therefore loosing the data that is already in the database.  An obvious must have for production systems. To show how we can implement this in SimpleMembership I will start with the decoupled version of SimpleMembership called SimpleSecurity.  You can read about SimpleSecurity and the concept behind it here.  It turns out that adding migration is fairly straight forward. To follow along you can download the project for SimpleSecurity here.  Note that if you get the latest version it will already have migration enable, but I will cover the steps here so that you can do this from scratch if needed.

To enable migrations go to the Package Manager Console and make sure your default project in the console is set to the project that contains your data layer (i.e. DbContext). For SimpleSecurity the project with the data layer is also called SimpleSecurity. Then enter the command "enable-migrations".  This will create a new folder in your data layer project called Migrations and it will contain a new file named Configurations.cs. If Entity Framework can find your database and detect any changes it will also create a CSharp file that contains code for the initial migration.  The Configuration file contains this code.

    internal sealed class Configuration : DbMigrationsConfiguration<SimpleSecurity.Repositories.SecurityContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }

        protected override void Seed(SimpleSecurity.Repositories.SecurityContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
            //  to avoid creating duplicate seed data. E.g.
            //
            //    context.People.AddOrUpdate(
            //      p => p.FullName,
            //      new Person { FullName = "Andrew Peters" },
            //      new Person { FullName = "Brice Lambson" },
            //      new Person { FullName = "Rowan Miller" }
            //    );
            //
        }
    }

This is the new database initializer that will be used for database migration.  When EF creates this the setting for AutomaticMigrationsEnabled is set to false. I changed this to true so we can start with automatic migrations.  The other type of migration is called code-based migrations.  Code-based migration is much more powerful and the likely choice for production system because of the control it provides.  But it is a much larger topic that we will have to cover in later posts.  So for this article we will just cover automatic migrations.

If you recall from the article on Seeding & Customizing ASP.NET MVC SimpleMembership we set which initializer to use in code using the SetInitializer method.

 Database.SetInitializer<userscontext>(new InitSecurityDb());
 UsersContext context = new UsersContext();
 context.Database.Initialize(true);
 if (!WebSecurity.Initialized)
    WebSecurity.InitializeDatabaseConnection("DefaultConnection",
         "UserProfile", "UserId", "UserName", autoCreateTables: true);


We cannot do that with the Configuration class because it is defined as internal sealed. If we wanted to set it programmatically there is no problem with changing to the definition of Configuration to public.  But the better way to do this is to set it in the web.config like this.

  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
    <contexts>
      <!--Use this initializer if you want to use datbase migration-->
      <context type="SimpleSecurity.Repositories.SecurityContext, SimpleSecurity" disableDatabaseInitialization="false">
        <databaseInitializer
          type="System.Data.Entity.MigrateDatabaseToLatestVersion`2[[SimpleSecurity.Repositories.SecurityContext, SimpleSecurity],
               [SimpleSecurity.Migrations.Configuration,  SimpleSecurity]], EntityFramework" />
      </context>
      <!--Use this initializer if you are testing and want to create a new database for each test.-->
      <!--<context disabledatabaseinitialization="false" type="SimpleSecurity.Repositories.SecurityContext, SimpleSecurity">
        <databaseInitializer
          type="SimpleSecurity.DropCreateSecurityDb, SimpleSecurity" />
      </context>-->
    </contexts>
  </entityFramework>


You will notice that I have two initializers set in this configuration with one being commented out.  The one that is not commented is the one for migrations, the one that is commented out is for an initializer that performs a drop-create.  This way I can easily switch between the initializers since I may want to use the drop-create initializer during testing. Now we can easily set our initializers through a configuration change. Just be sure to remember to remove Database.SetInitializer from your code.

Now when you have it configured for automatic migrations any changes to your model will automatically update the database with these changes without loosing your data, with one exception. If you remove a property from the model and there is data in the corresponding column in a table the migration will throw an exception. If you want to allow the removal of a column with data you need to set AutomaticMigrationDataLossAllowed to true in the Configuration class constructor.

Automatic  migration is a powerful feature of Entity Framework.  Look for future posts where we will discuss code-based migrations.


Comments

Popular posts from this blog

Using Claims in ASP.NET Identity

Seeding & Customizing ASP.NET MVC SimpleMembership

Customizing Claims for Authorization in ASP.NET Core 2.0