EF Core + Oracle: como tornar as migrações idempotentes



Normalmente, a estrutura EF Core é usada em conjunto com o MS SQL, outro produto da Microsoft. No entanto, isso não é um dogma. Por exemplo, na CUSTIS, escrevemos a lógica de negócios em C # e usamos o Oracle para gerenciar os bancos de dados. O EF Core possui um maravilhoso mecanismo de migração, mas, no nosso caso, eles não são idempotentes. O fato é que o Oracle e vários outros bancos de dados, como o MySQL, não suportam DDL transacional . Isso significa que, se a migração cair em algum lugar no meio, ela não poderá ser revertida ou revertida. Como implementar migrações idempotentes para o EF Core sem o MS SQL?

fundo


Nossa empresa possui uma ferramenta bastante poderosa para instalar patches em um banco de dados Oracle, que usamos em vários projetos. Foi escrito quando não havia Liquibase, migrações EF e outras ferramentas abertas. O Patcher permite que você trabalhe com centenas de bancos de dados, rastreie o histórico de instalação, visualize logs, armazene segredos e muito mais. Os scripts para alterar o banco de dados são gravados na forma de macros SQL ou m4. Com a ajuda deles, você pode, entre outras coisas, modificar a estrutura: criar tabelas, colunas e outros objetos. Além disso, as macros m4 são idempotentes. Isso significa que, se você tentar criar, por exemplo, a tabela, o script não cairá, mas verá que ele já existe e ignora a criação.

Suponha que um script para instalar um patch consista em duas operações:

  1. Crie a tabela A.
  2. Criando a Tabela B.

Se o script falhar após a primeira operação, a tabela A. permanecerá no Oracle.A reaplicação do patch funcionará corretamente: o script verificará se A já existe e, portanto, prosseguirá imediatamente para a segunda operação.

Além das vantagens, o patcher ainda tem uma desvantagem - a ferramenta está fechada e é usada apenas no CUSTIS. Os desenvolvedores precisam aprender a trabalhar com ele e, fora da empresa, essa experiência não é muito valiosa. Além disso, o patcher não suporta o modo de operação Code First, portanto, todos os scripts para alterar a estrutura do banco de dados devem ser gravados manualmente.

Queríamos experimentar algum mecanismo pronto para instalar patches e escolher a migração. No final de 2019, outro projeto foi lançado para o cliente, no qual decidimos testar uma nova abordagem. O principal problema desse mecanismo foi a não-idepotência das migrações.

Problema


No MS SQL, uma cadeia de instruções DDL ou migração é executada como uma única transação composta. Em caso de interrupção, a operação é completamente cancelada. O DDL do Oracle não é transacional, portanto, uma queda na migração levará a um estado inconsistente do banco de dados.

Vamos retornar ao patch, que consiste em duas operações: criando as tabelas A e B. Se o migrador cair após o primeiro, o Oracle permanecerá na tabela A. A reinicialização não funcionará - o operador CREATE TABLEnão gostará que A já exista. Ele também falhará ao reverter a migração: o EF Core grava na tabela do sistema que a migração foi concluída, apenas no final do processo. Do ponto de vista do EF Core, se a migração ainda não foi concluída, não há nada para reverter.

Decisão


A busca por uma solução pronta para Oracle na Internet não produziu resultados. Tudo o que encontrei são artigos sobre como escreverinstalar patches ao trabalhar com a EF. Um pouco mais tarde, no StackOverflow, tive a idéia de criar meu IMigrationsSqlGenerator . Essa interface é responsável por gerar o código SQL que processa as operações EF.

O pacote Oracle.EntityFrameworkCore incluía OracleMigrationsSqlGenerator, implementa IMigrationsSqlGenerator. Por exemplo, se você deseja adicionar uma coluna, o seguinte código será gerado:

ALTER TABLE MY_TABLE ADD (MY_COLUMN DATE)

Em seguida, o código é passado para outras classes para execução no banco de dados.

Para começar, tentei substituir um par de operações OracleMigrationsSqlGenerator. A tarefa acabou sendo bastante viável e comecei a escrever um migrante idempotente. Foi assim que surgiu o CUSTIS.OracleIdempotentSqlGenerator .

Antes da operação EF, nosso migrante verifica se foi executado anteriormente. Por exemplo, uma coluna é adicionada assim:

DECLARE
    i NUMBER;
BEGIN
    SELECT COUNT(*) INTO i
    FROM user_tab_columns
    WHERE table_name = UPPER('MY_TABLE') AND column_name = UPPER('MY_COLUMN');
    IF I != 1 THEN
        EXECUTE IMMEDIATE 'ALTER TABLE MY_TABLE ADD (MY_COLUMN DATE)';  
    END IF;       
END;

Usando


O uso do pacote é muito simples - você só precisa substituí-lo IMigrationsSqlGeneratorno contexto certo:

public class MyDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.ReplaceService<IMigrationsSqlGenerator, IdempotentSqlGenerator>();
    }
}

As migrações são formadas e definidas pelas ferramentas padrão do EF Core :

dotnet ef migrations add v1.0.1
dotnet ef database update

A abordagem geral estabelecida em CUSTIS.OracleIdempotentSqlGenerator pode ser implementada em geradores criados para MySQL, MariaDB, Teradata, AmazonAurora e outros bancos de dados nos quais o DDL não é transacional.

Referências


O pacote está disponível no NuGet
Sources no GitHub

All Articles