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:- Crie a tabela A.
- 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 TABLE
nã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 escrever e instalar 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 IMigrationsSqlGenerator
no 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 NuGetSources no GitHub