EF Core - Múltiplos Requests p/ um DataBase em uma Transação
Neste artigo vamos estudar como tratar múltiplos requests para um banco de dados em uma única transação usando o EF Core. |
Ao trabalhar com
Banco de Dados, seja por meio de APIs ou de outras formas, quando usamos o
Entity Framework Core você sabe o que ocorre nos bastidores ?
Por exemplo, se estivermos realizando várias operações de banco de dados em uma única solicitação, isso criará uma conexão de banco de dados e confirmará as alterações separadamente,e com isso teremos a ocorrência de muitas viagens de ida e volta ao banco de dados para essa solicitação. Acrescente agora que isso pode corromper os dados se algo der errado.
Isso não parece ser um bom cenário, mas qual poderia ser uma abordagem mais robusta ?
Imagine abrir uma transação e fazer toda a operação do banco de dados dentro dela e, se todas elas forem concluídas, basta confirmar a transação.
Por meio dessa
abordagem, a integridade dos dados pode ser alcançada e também, se alguma
operação falhar, toda a transação pode ser revertida.
Parece uma boa ideia, certo ? Então, vamos ver como podemos conseguir isso.
Para ilustrar vamos criar uma aplicação console referenciando o EF Core criar e configurar a classe de contexto e habilitar a exibição dos comandos SQL gerados pelo EF Core na execução dos comandos.
No projeto Console chamado EFCore_MultipleDBs vamos criar uma pasta Models onde vamos criar as seguintes classes das entidades:
1- Funcionario
public class Funcionario
{
[Key]
public int FuncionarioId { get; set; }
public string? Nome { get; set; }
Departamento? Departamento { get; set; }
public int DepartamentoId { get; set; }
}
|
2- Departamento
public class Departamento
{
[Key]
public int DepartamentoId { get; set; }
public string? NomeDepartamento { get; set; }
public ICollection<Funcionario>? Funcionarios { get; set; }
}
|
Definimos as entidades Funcionario e Departamento e o relacionamento um-para-muitos entre as entidades usando as convenções do EF Core.
A seguir vamos criar a classe AppDbContext :
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.LogTo(Console.WriteLine);
optionsBuilder.UseSqlServer("Data Source=.\\SQLEXPRESS;Initial Catalog=TesteDB2;Integrated Security=True");
}
public AppDbContext() : base()
{ }
public DbSet<Funcionario>? Funcionarios { get; set; }
public DbSet<Departamento>? Departamentos { get; set; }
}
|
Agora podemos aplicar o Migrations e gerar o banco e as tabelas usando os comandos:
Vamos definir no arquivo Program o seguinte código :
Console.WriteLine("\nPressione algo para iniciar...");
Console.ReadKey();
using (var context = new AppDbContext())
{
context.Departamentos.Add(new Departamento() { NomeDepartamento = "Contabilidade" });
context.SaveChanges();
context.Funcionarios.Add(new Funcionario() { Nome = "David", DepartamentoId = 1 });
context.SaveChanges();
}
Console.WriteLine("Processamento Concluido...");
Console.ReadKey();
|
Neste código vamos analisar oque ocorre quando emitimos o comando SaveChanges mais de uma vez na mesma transação.
Teremos o Log de Console gerado que podemos analisar (não vou publicar pois é muito extenso).
Se dermos uma olhada no trecho de código acima e nos logs de console correspondentes, podemos ver que SaveChanges() é chamado 2 vezes e em ambas as vezes o EF Core abriu conexões de banco de dados separadas e confirmou as alterações.
Isso significa que o EF Core está criando duas transações separadas para essas duas operações de inserção. Esta é uma abordagem dispendiosa e queremos evitá-la.
Agora, para obter
vários SaveChanges() em uma única transação,
podemos usar um recurso chamado BeginTransaction().
A instrução DbContext.Database.BeginTransaction();
cria uma nova transação para o banco de dados subjacente e nos permite confirmar
ou reverter as alterações feitas no banco de dados usando várias chamadas de
método SaveChanges.
Assim,, vamos reescrever o método escrito anteriormente usando
BeginTransaction() :
Console.WriteLine("\nPressione algo para iniciar...");
Console.ReadKey();
using (var context = new AppDbContext())
{
using (IDbContextTransaction transaction = context.Database.BeginTransaction())
{
try
{
context.Departamentos.Add(new Departamento() { NomeDepartamento = "RH" });
context.SaveChanges();
context.Funcionarios.Add(new Funcionario() { Nome = "Maria", DepartamentoId = 2 });
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine(ex.Message);
}
}
}
Console.ReadKey();
|
No trecho de
código acima, estamos criando uma transação por
BeginTransaction(), fazendo todas as operações de banco de dados dentro
dela e finalmente confirmando por Commit().
Se olharmos para o log do console, podemos ver que a conexão do banco de dados é
aberta apenas uma vez e confirmada apenas uma vez, e as duas operações de
inserção são feitas dentro dessa transação.
Voilà, então é isso que queríamos alcançar, lidando com várias solicitações de
banco de dados em uma única transação.
Mas isso é tudo?
Na verdade, não.
E se houver várias transações e qualquer uma delas falhar?
De forma ideal, toda a transação deve ser revertida ou revertida.
Agora, a parte interessante é que também podemos conseguir isso na mesma
abordagem.
Vamos adicionar uma exceção forçada no código acima.
Console.WriteLine("\nPressione algo para iniciar...");
Console.ReadKey();
using (var context = new AppDbContext())
{
using (IDbContextTransaction transaction = context.Database.BeginTransaction())
{
try
{
context.Departamentos.Add(new Departamento() { NomeDepartamento = "Compras" });
context.SaveChanges();
context.Funcionarios.Add(new Funcionario() { Nome = "Paulo", DepartamentoId = 3 });
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine(ex.Message);
}
}
}
Console.ReadKey();
|
Na seção try do
trecho acima, estamos forçando uma exceção e na seção catch temos o método
Rollback() desfaz a operação de forma segura.
Nota: Você pode especificar diferentes níveis de isolamento no método
DbContext.Database.BeginTransaction().
Então, é assim que podemos lidar com várias solicitações de banco de dados em
uma única transação usando o EF Core de maneira eficiente.
E estamos
conversados...
Pegue o projeto aqui : EFCore_MultipleDBs.zip
"Porque nele(Jesus) foram criadas todas as coisas
que há nos céus e na terra, visíveis e invisíveis, sejam tronos, sejam
dominações, sejam principados, sejam potestades. Tudo foi criado por ele e para
ele."
Colossenses 1:16
Referências:
Curso Fundamentos da Programação Orientada a Objetos com VB .NET