Na primeira parte falei um pouco dos problemas envolvidos no controle de versionamento de banco de dados. Agora esta na hora de apresentar alguma solução, certo? :)
Apresentando Migrator.NET
Para resolver a maioria (senão todos) os problemas de versionamento de banco temos uma ferramenta maravilhosa chamada Migrator.NET. Essa ferramenta é um port para .NET do original em Rails. A idéia por trás dela é bastante simples: consiste em diversos objetos (classes) versionados (da mesma maneira que código normal) contendo as instruções que devem ser executadas para gerar e/ou voltar o schema do banco de dados. Cada classe corresponde a uma instrução de alteração e contém um método Up (adicionar/avançar) e um método Down (retroceder/fallback) equivalente. Vejamos como é facil no exemplo abaixo:
#region Usings
using System.Data;
using Migrator.Framework;
#endregion
namespace NetScrum.Migrations.Tables
{
[Migration(20090209211900)]
public class CreateTableProject : Migration
{
public override void Down()
{
Database.RemoveTable("netscrum_project");
}
public override void Up()
{
Database.AddTable("netscrum_project",
new Column("id", DbType.Int32, ColumnProperty.Identity)
, new Column("name", DbType.AnsiString, 100, ColumnProperty.NotNull)
, new Column("description", DbType.AnsiString, 300, ColumnProperty.Null)
);
Database.AddPrimaryKey("pk_project", "netscrum_project", "id");
}
}
}
Viram? Basta referenciar a DLL Migrator.Framework.dll, adicionar a diretiva using criar sua classe herdando de Migration e dar override nos métodos Up e Down.
Gerenciando as versões
Para gerenciar as versões e garantir que os migrations sejam executados na ordem correta, você deve utilizar o atributo Migration(long version) decorando a classe. Você deve preenche-lo com um long contendo o timestamp no formato AAMMDDhhmmss (ano/mes/dia/hora/minuto/segundo) em que seu migration foi gerado. Ou seja, um alter table da tabela projeto, por exemplo, deve conter um valor mais novo (maior) que o create table correspondente para que o migration de create seja executado antes do alter.
Configurando o build
Criar e gerenciar migrations é muito fácil. No entanto, as configurações dele são um pouquinho chatas para quem não está familizarizado com MSBuild ou NAnt. Neste artigo vamos usar o MSBuild que já é nativo e eu gosto mais simplesmente por estar familiarizado.
Junto com o código fonte compilado do Migrator.NET vem também o assembly Migrator.MSBuild.dll que é onde está a o target especifico que o MSBuild precisa para executar os migrations. Este target será usado no arquivo de build (explicado mais abaixo) e deve ser configurado como o exemplo abaixo apontando para o caminho onde esta o assembly. Arquivos Migrator.Targets:
$(MigratorTasksPath)\Migrator.MSBuild.dll
A propriedade $(MigratorTasksPath) define onde está o assembly Migrator.MSBuild.dll. O valor desta propriedade pode ser hard-coded ou então preenchida no arquivo de build conforme veremos.
Por padrão, todo projeto (arquivo.csproj no caso de C#) é um arquivo de MSBuild. É possível editá-lo para incluir as configurações necessárias ao Migrations, mas cedo ou tarde é perigoso que Visual Studio o recrie ou o modifique a seu bel prazer e ai perderiamos as customizações. Portanto, prefiro criar um outro arquivo de build exclusivo para os migrations e chama-lo quando eu bem entender. Segue abaixo o arquivo NetScrum.Migrations.build:
$(MSBuildProjectDirectory)
Este arquivo define uma série de paramêtros:
MigratorTasksPath - Caminho utilizado para apontar onde esta o target (mencionado acima). Neste caso, é preenchido pela propriedade $(MSBuildProjectDirectory) que aponta para onde o arquivo build que está sendo chamado está localizado.
DatabaseVersion - Será passado via paramêtro na hora da chamada do Migrator.NET. Caso não seja informado, o valor default é -1 que indica para a última versão.
Provider = Banco de dados de destino. Neste caso estamos usando SqlServer, mas Migrator.NET funciona para outros bancos também.
Connectionstring - Óbvio, né? :)
To - Recebe o valor do parametro DatabaseVersion indicando a versão de destino do migrations.
Migrations - Caminho onde se encontra o assembly compilado contendo as migrations.
Existem alguns outros paramêtros que permitem uma configuração um pouco diferenciada, como por exemplo, ao invés de fornecer o assembly compilado apenas informar o caminho onde estão as classes para que o próprio Migrator.NET faça a compilação.
Uma vez que tudo esteja configurado, basta ir até o command prompt, navegar até a pasta onde o arquivo.build e chamar o MSBuild passando os paramêtros conforme o exemplo abaixo:
%windir%\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe NetScrum.Migrations.build /t:Migrate /p:DatabaseVersion=-1
Os paramêtros são:
NetScrum.Migrations.build - Nome do arquivo de build que contém as configurações do Migrator.NET
/t:Migrate - Target que deve ser executado. Neste caso o target de migrations :)
/p:DatabaseVersion=-1 - Versão do banco para qual desejo ir. Caso queira ir para última versão é só informar -1. Caso queira zerar o banco (executar Down de todas as migrations) é só informar 0. No caso de ir para uma versão específica é só informar o timestamp desejado.
É isso! Configurar a primeira vez é um pouco chato. Acertar os caminhos dos arquivo requer algumas tentativas para quem não está familizarizado com o MSBuild, mas nada muito traumático. Nos exemplos, todos os arquivos necessários (menos o assembly contendo as migrations) estavam na mesma pasta (tools) para facilitar.
Exemplo
Quem quiser ver o exemplo funcionando, basta baixar a última versão do .NET Scrum. Ele contém as pastas e arquivos configurados direitinho pra funcionar.
Importante
A versão utilizada para este artigo foi a 0.8 que baixei tem uns 45 dias. Na época, apesar de compilada não estava funcionando então tive que efetuar umas pequenas correções do trunk. Não testei a versão 0.8 disponível hoje, portanto você pode baixar aqui a versão 0.8 compilada e corrigida por mim.
Referências
Até alguns dias atrás eu não tinha encontrado nenhum outro artigo a respeito deste assunto em português. Acredito que ainda não exista e esse seja o primeiro. Além das instruções básicas de banco que foram apresentadas no migration do exemplo ainda é possível criar PKs, FKs, executar Sql's open text entre outros procedimentos não descritos aqui.
Vocês podem encontrar informações a respeito desses procedimentos no site do projeto ou tirar dúvidas e perguntar aqui mesmo no blog.
Enjoy!