Development Blog

 Friday, April 25, 2008
« Good Tools and ReSharper 4 EAP Thoughts | Main | Introducing Machine.Specifications (or M... »

Introduction

We used ActiveRecord migrations at Eleutian for a number of months. Everything was right with the world and we had no major complaints until we started running into a problem with our more complex migration scenarios. Usually, we see two kinds of migrations:

  • Schema - Add/remove/rename columns, manipulate tables, and general schema related changes.
  • Data - Refactoring data into new schemas and major data reorganization or population.

Schema migrations are handled great by most of the migration frameworks out there. One problem we were running into was it's typically easier to write the data migrations in C# directly using our entities and services. As a result we had two different ways of migrating and this made our frequent publishes problematic. We decided to consolidate the two operations.

On the outside there's nothing really new in Machine.Migrations that you haven't seen elsewhere. Most of the changes are internal. Migrations are numbered right now, although we've talked of moving to time-stamping them a la the newer ruby ActiveRecord. What Machine.Migrations does allow you to do is heavily tweak your migrations to fit your project. In our project we have a custom MsBuild task that extends the default implementation to provide access to our DaoFactory and other goodies. If this is something that interests you, then read on. If you're happy with your current migration scenario then you probably don't have a huge need for Machine.Migrations.

A Simple Migration

I'll start with the actual migrations. Here is an example that creates a table:

using System;
using System.Collections.Generic;
using Machine.Migrations;

public class CreateUserTable : SimpleMigration
{
  public override void Up()
  {
    Schema.AddTable("users", new Column[] {
      new Column("Id", typeof(Int32), 4, true, false),
      new Column("Name", typeof (string), 64, false, false),
      new Column("Email", typeof (string), 64, false, false),
      new Column("Login", typeof (string), 64, false, false),
      new Column("Password", typeof (string), 64, false, false),
    });
  }

  public override void Down()
  {
    Schema.DropTable("users");
  }
}

Machine.Migrations does enforce a file naming/class naming restriction so the file for this migration would be named 001_create_user_table.cs. We inherit from SimpleMigration, which is built-in and provides access to Schema manipulations and raw Database queries through those properties respectively. We have a solution and project for our migrations so that we can get IntelliSense while writing them. This can be helpful if you forget the syntax.

When you run Machine.Migrations it will only compile the migrations that it needs to apply. This is very handy if you are doing high-level migrations including your entities because then you don't have to maintain them after they've been applied to your live database. But, this does mean you won't be able to migrate from the beginning to the end because migrations will become outdated. This has been a minor problem for us because we snapshot our live database and run that in development. I do think a better long term solution is required though, in order to make creating a brand new database easier. Perhaps flagging migrations as data migrations and not running them if they don't compile? I am not sure.

There is also a Boo migration factory that you can use if you don't want to write your migrations in C#. Internally, the infrastructure is there if you want to add other languages as well.

Another interesting thing I should note is that each migration is run inside its own transaction, so if you mess up it'll just rollback. This is very nice under SQL Server because schema changes are rolled back as well.

How Do I Start Using It?

If you just want to use Machine.Migrations out of the box (a good first step) it's fairly straightforward:

  1. Copy Machine.Migrations and its dependencies into your project. Be sure to include the Machine.Migrations.targets file which has the default MsBuild task and target.
  2. You can then drop the following XML into your MsBuild file (check all your paths!):
    <Import Project="Libraries\Machine\Machine.Migrations.targets" />
    
    <PropertyGroup>
      <MigrationConnectionString>Data Source=127.0.0.1;Initial Catalog=MyDb;Integrated Security=SSPI</MigrationConnectionString>
    </PropertyGroup>
    
    <Target Name="Migrate" DependsOnTargets="MigrateDatabase">
    </Target>
  3. All that's happening here is we're importing the targets file, defining a connection string to use when migrating, and then making a target that depends on the target we imported.
  4. By default it will look for your migrations in a subdirectory named Migrations.
  5. Then it's just a matter of running MsBuild YourProject.proj /t:Migrate where YourProject.proj is your MsBuild file.

One thing you may notice is that sometimes your migrations will fail to compile because of a missing dependency. In order to ensure your own or third party assemblies are referenced you add them to the MigrationReferences ItemGroup, like so:

<ItemGroup>
  <MigrationReferences Include="System.Xml.dll">
    <InProject>false</InProject>
  </MigrationReferences>
</ItemGroup>

When Machine.Migrations compiles it will include those assemblies in the references list.

What Next?

I'm going to hold off on explaining how to extend the default migration scenario until later because this post is already pretty long. For anyone feeling ambitious it basically amounts to extending SimpleMigration in your own code and inheriting from that. (Be sure to include your assembly in MigrationReferences). We go a step further and wrap the MsBuild task as well, which may or may not be necessary for you.

I can't stress enough that going down this road isn't for everybody. There is an argument for keeping migrations simple and making those complex data migrations something else's concern. For now, this is what we're doing and it has worked nicely. I'm sure there will be lots of questions as well, please feel free to comment and offer up suggestions. As far as I know we're the only people using this code, so I'm sure there are quirks.

by Jacob on Friday, April 25, 2008 12:37:26 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [1]  |  Trackback