Oi Hoje, lancei uma nova versão do ThinkingHome.Migrator - uma ferramenta para migração de versões de um esquema de banco de dados para a plataforma .NET Core.

Pacotes publicados no NuGet , documentação detalhada escrita. Você já pode usar o novo migrador, e eu vou lhe dizer como ele apareceu, por que ele tem a versão número 3.0.0 (embora este seja o primeiro lançamento) e por que é necessário quando há EF Migrations e FluentMigrator .
Como tudo começou
Há 9 anos, em 2009, trabalhei como desenvolvedor ASP.NET. Quando lançamos nosso projeto, uma pessoa especial ficou acordada até tarde e, ao mesmo tempo atualizando os arquivos no servidor, executou scripts SQL com as mãos atualizando os bancos de dados no prod. Pesquisamos uma ferramenta que faria isso automaticamente e encontramos o projeto Migrator.NET .
O migrante propôs uma nova idéia para a época - definir alterações no banco de dados na forma de migrações. Cada migração contém uma pequena parte das alterações e possui um número de versão no qual o banco de dados entrará após a conclusão. O próprio migrante acompanhou as versões e executou as migrações necessárias na ordem necessária. Especialmente interessante foi o fato de o migrante permitir definir alterações reversas para cada migração. Era possível, no início do migrador, definir uma versão menor que a atual e reverter automaticamente o banco de dados para esta versão, executando as migrações necessárias na ordem inversa.
[Migration(1)] public class AddAddressTable : Migration { override public void Up() { Database.AddTable("Address", new Column("id", DbType.Int32, ColumnProperty.PrimaryKey), new Column("street", DbType.String, 50), new Column("city", DbType.String, 50) ); } override public void Down() { Database.RemoveTable("Address"); } }
Houve muitos erros nesse migrador. Ele não sabia como trabalhar com esquemas de banco de dados que não fossem o esquema padrão. Em alguns casos, gerou consultas SQL incorretas e, se você especificar um número de versão inexistente, ele entrará em um loop infinito. Como resultado, meus colegas e eu bifurcamos o projeto e corrigimos bugs no local.
O GitHub.com, com suas solicitações de garfos e pull, não estava lá (o código do migrante estava em code.google.com ). Portanto, não nos preocupamos em garantir que nossas alterações voltassem ao projeto original - apenas serramos nossa cópia e a utilizamos. Com o tempo, reescrevemos a maior parte do projeto, e eu me tornei seu principal mantenedor. Então, publiquei o código do nosso migrador no código do google e escrevi um artigo sobre Habr . Portanto, havia ECM7.Migrator.
Durante o trabalho no migrador, nós o reescrevemos quase completamente. Ao mesmo tempo, eles simplificaram um pouco a API e cobriram tudo com testes automáticos. Pessoalmente, gostei muito de usar o que aconteceu. Ao contrário do migrador original, havia um sentimento de confiabilidade e não havia a sensação de que mágica estranha estava acontecendo.
Como se viu, não só gostei do nosso migrador. Tanto quanto eu sei, foi usado em empresas bastante grandes. Eu sei sobre a ABBYY, BARS Group e concert.ru. Se você digitar a consulta de pesquisa "ecm7 migrator", poderá ver nos artigos de resultados sobre ele, menções no currículo, descrições de uso no trabalho do aluno. Às vezes recebia cartas de estranhos com perguntas ou palavras de gratidão.
Após 2012, o projeto quase não se desenvolveu. Seus recursos atuais cobriam todas as tarefas que eu tinha e não via a necessidade de terminar alguma coisa.
ThinkingHome.Migrator
No ano passado, comecei a trabalhar em um projeto no .NET Core. Lá era necessário possibilitar a conexão de plug-ins, e os plug-ins deveriam poder criar a estrutura de banco de dados necessária para eles mesmos, a fim de armazenar seus dados lá. Essa é apenas uma tarefa, para a qual um migrador é adequado.
Migrações Ef
Como usei o Entity Framework Core para trabalhar com o banco de dados, a primeira coisa que tentei foi o EF Migrations. Infelizmente, quase imediatamente tive que abandonar a ideia de usá-los.
As migrações do Entity Framework arrastam várias dependências para o projeto e, na inicialização, fazem alguma mágica especial. Um passo para a esquerda / um passo para a direita - você encontra restrições. Por exemplo, as migrações do Entity Framework por algum motivo devem necessariamente estar no mesmo assembly que o DbContext
. Isso significa que não será possível armazenar migrações dentro de plugins.
Migrante fluente
Quando ficou claro que as EF Migrations não se encaixavam, procurei uma solução no google e encontrei vários migradores de código aberto. O mais avançado deles, a julgar pelo número de downloads no NuGet e estrelas no GitHub, acabou sendo o FluentMigrator . FM é muito bom! Ele sabe muito e tem uma API muito conveniente. No começo, decidi que era disso que eu precisava, mas depois vários problemas foram descobertos.
O principal problema é que o FluentMigrator não sabe levar em consideração várias seqüências de versão no mesmo banco de dados. Como escrevi acima, eu precisava usar um migrador em um aplicativo modular. É necessário que os módulos (plugins) possam ser instalados e atualizados independentemente um do outro. O FluentMigrator possui numeração de versão de ponta a ponta. Por esse motivo, é impossível executar / reverter a migração de um plug-in do banco de dados sem afetar a estrutura do banco de dados dos plug-ins restantes.
Tentei organizar o comportamento desejado usando tags , mas isso também não é exatamente o que você precisa. O FluentMigrator não armazena informações de tags para migrações concluídas. Além disso, as tags estão vinculadas a migrações, não a assemblies. Isso é muito estranho, já que o ponto de entrada para a migração é exatamente o assembly. Em princípio, provavelmente era possível fazer versões paralelas dessa maneira, mas você precisa escrever um wrapper bastante complicado sobre o migrador.
Porta ECM7.Migrator para .NET Core
No começo, essa opção nem era considerada. Naquela época, a versão atual do .NET Core era 1.1 e sua API era pouco compatível com o .NET Framework, no qual o ECM7.Migrator trabalhava. Eu tinha certeza de que transportá-lo para o .NET Core seria difícil e demorado. Quando não havia opções para "levar o acabado", decidi tentar. A tarefa foi mais fácil do que eu esperava. Surpreendentemente, funcionou quase que imediatamente. Apenas edições menores foram necessárias. Como a lógica do migrador foi coberta por testes, todos os lugares que quebraram foram imediatamente visíveis e eu os consertei rapidamente.
Agora eu tenho adaptadores portados para apenas quatro DBMSs: MS SQL Server, PostgreSQL, MySQL, SQLite. Eu não portava adaptadores para Oracle (já que ainda não há um cliente estável para o .NET Core), MS SQL Server CE (já que funciona apenas no Windows e não tenho lugar para executá-lo) e Firebird (porque não muito popular, porta mais tarde). Em princípio, se você precisar criar fornecedores para esses ou outros DBMSs - isso é bastante simples.
O código fonte do novo migrador está no GitHub . Configurou o lançamento de testes para cada DBMS no Travis CI. Foi criado um utilitário de linha de comando ( .NET Core Global Tool ) que pode ser facilmente instalado a partir do NuGet . A documentação está escrita - tentei muito escrever detalhadamente e, aparentemente, aconteceu. Você pode pegar e usar!
Um pouco sobre o nome ...
O novo migrador não tem compatibilidade com o antigo. Eles trabalham em plataformas diferentes e têm uma API diferente. Portanto, o projeto é publicado com um nome diferente.
O nome foi escolhido pelo projeto ThinkingHome , para o qual eu portava um migrador. Na verdade, o ECM7.Migrator também é nomeado para o projeto em que eu estava trabalhando naquele momento.
Talvez fosse melhor escolher um nome neutro, mas não me ocorreu ter boas opções. Se você souber disso - escreva nos comentários. Não é tarde demais para renomear tudo.
O número da versão indicava 3.0.0, porque o novo migrador é uma continuação lógica do antigo.
Início rápido
Então, vamos tentar usar um migrador.
Todas as alterações no banco de dados são registradas nas classes de código de migração gravadas em uma linguagem de programação (por exemplo, em C #). As classes de Migration
herdadas da classe base Migration
do pacote ThinkingHome.Migrator.Framework . Neles, você precisa substituir os métodos da classe base: Apply
(aplicar as alterações) e Revert
(reverter as alterações). Dentro desses métodos, o desenvolvedor, usando uma API especial, descreve as ações que precisam ser executadas no banco de dados.
Além disso, a classe de migração deve ser marcada com o atributo [Migration]
e indicar a versão na qual o banco de dados entrará depois que essas alterações forem feitas.
Exemplo de migração
using ThinkingHome.Migrator.Framework; [Migration(12)] public class MyTestMigration : Migration { public override void Apply() { // : Database.AddTable("CustomerAddress", new Column("customerId", DbType.Int32, ColumnProperty.PrimaryKey), new Column("addressId", DbType.Int32, ColumnProperty.PrimaryKey)); } public override void Revert() { // : Database.RemoveTable("CustomerAddress"); // , // Revert } }
Como executar
As migrações são compiladas em um arquivo .dll. Depois disso, você pode executar alterações no banco de dados usando o utilitário do console migrate-database
. Para começar, instale-o no NuGet .
dotnet tool install -g thinkinghome.migrator.cli
Execute migrate-database
, especificando o tipo de DBMS, a cadeia de conexão e o caminho necessários para o arquivo .dll com migrações.
migrate-database postgres "host=localhost;port=5432;database=migrations;" /path/to/migrations.dll
Executar através da API
Você pode executar migrações através da API a partir do seu próprio aplicativo. Por exemplo, você pode escrever um aplicativo que, quando iniciado, cria a estrutura de banco de dados desejada.
Para fazer isso, conecte o pacote ThinkingHome.Migrator do NuGet e o pacote ao provedor de transformação do DBMS necessário no seu projeto. Depois disso, crie uma instância da classe ThinkingHome.Migrator.Migrator
e chame seu método Migrate
, passando a versão necessária do banco de dados como parâmetro.
var version = -1; // -1 var provider = "postgres"; var connectionString = "host=localhost;port=5432;database=migrations;"; var assembly = Assembly.LoadFrom("/path/to/migrations.dll"); using (var migrator = new Migrator(provider, connectionString, assembly)) { migrator.Migrate(version); }
A propósito, você pode comparar com o exemplo de lançamento do FluentMigrator.
Conclusão
Eu tentei fazer uma ferramenta simples, sem vícios e magia complexa. Parece ter funcionado muito bem. O projeto não é bruto há muito tempo, tudo é coberto em testes, há documentação detalhada em russo. Se você estiver usando o .NET Core 2.1, tente um novo migrador . Muito provavelmente você vai gostar também.