EF Core - Usando transações
Hoje vamos recordar os conceitos sobre transações no EF Core. |
Vamos iniciar
apresentando qual o comportamento padrão das transações no EF Core.
Transações - Comportamento padrão
O
comportamento padrão das transações no EF Core é o seguinte:
Desta forma, esse recurso dos bancos de dados SQL nos poupa de muitas dores de cabeça. Não precisamos pensar se os bancos de dados permanecem em um estado inconsistente, porque as transações do banco de dados podem fazer o trabalho para nós.
Consideremos o seguinte código:
using
var
context = new
ShoppingContext(); context.Items.Add( new Item{ ProdutoId = produtoId, Quantidade = quantidade }); var estoque = context.Estoque.FirstOrDefault(s => s.ProdutoId == produtoId);estoque.Quantidade -= quantidade; context.SaveChanges(); |
Como estamos adicionando um Item e, no mesmo escopo, reduzindo a quantidade de estoque, a chamada a SaveChanges aplicará as duas alterações dentro de uma transação. Podemos garantir que o banco de dados permanecerá em um estado consistente.
Criando transações com o EF Core
E se você quiser ter mais controle sobre as transações ao trabalhar com o EF
Core ?
Você pode criar manualmente uma transação acessando o facade Database disponível
em uma instância de DbContext e chamando BeginTransaction.
Aqui está um exemplo em que temos várias chamadas para
SaveChanges.
No cenário padrão, ambas as chamadas seriam executadas em suas próprias transações. Isso deixa a possibilidade da segunda chamada para SaveChanges falhar e deixar o banco de dados em um estado inconsistente.
using
var
context = new
ShoppingContext(); using var transaction = context.Database.BeginTransaction();try { context.Items.Add(new Item { ProdutoId = produtoId, Quantidade = quantidade }); context.SaveChanges(); var estoque = context.Estoque.FirstOrDefault(s => s.ProdutoId == produtoId); estoque.Quantidade -= quantidade; context.SaveChanges(); // Ao comitar as alterações elas serão aplicadas ao banco de dados // A transação fará um auto-rollback quando ela for liberada // se qualquer comando falhar transaction.Commit(); catch (Exception) { transaction.Rollback(); } |
Invocamos BeginTransaction para iniciar manualmente uma nova transação de banco de dados. Isso criará uma nova transação e a retornará, para que possamos confirmar a transação quando quisermos concluir a operação.
Você também deseja adicionar um bloco try-catch ao seu código, para que possa reverter a transação se houver alguma exceção.
A seguir temos o exemplo de código mostrando o uso de uma transação explícita no EF Core:
using (var context = new MeuContexto())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
// Realize as operações que deseja realizar dentro da transação
context.Pessoas.Add(new Pessoa { Nome = "João" });
context.Pessoas.Add(new Pessoa { Nome = "Maria" });
context.SaveChanges();
// Se chegou até aqui sem erros, comite a transação
transaction.Commit();
}
catch (Exception ex)
{
// Se ocorrer um erro, desfaça a transação
transaction.Rollback();
Console.WriteLine($"Erro: {ex.Message}");
}
}
}
|
Neste exemplo, estamos criando uma nova instância do contexto de banco de dados do EF Core (MeuContexto), e em seguida iniciando uma nova transação explicitamente utilizando o método BeginTransaction() do objeto Database do contexto.
Dentro do bloco try da transação, estamos realizando algumas operações no banco de dados (adicionando duas pessoas ao banco de dados), e em seguida salvando as mudanças usando o método SaveChanges().
Se todas as operações dentro do bloco try forem bem sucedidas, a transação é comitada usando o método Commit() do objeto DbContextTransaction.
No entanto, se ocorrer um erro durante a execução das operações, a transação é desfeita usando o método Rollback() do objeto DbContextTransaction. Além disso, o erro é capturado e uma mensagem de erro é exibida na tela.
Usando transações existentes com o EF Core
Criar uma transação usando o EF Core DbContext não é a única opção. Você pode
criar uma instância SqlTransaction e passá-la para o EF Core, para que as
alterações aplicadas com o EF Core possam ser confirmadas dentro da mesma
transação.
Assim, para criar
uma instância de SqlTransaction e passá-la para o
EF Core, você pode usar o método BeginTransaction()
da classe SqlConnection e, em seguida, passar o objeto SqlTransaction resultante
para o método UseTransaction() do objeto DbContext.
Continuando com o exemplo anterior a seguir temos um exemplo para este cenário:
using (var connection = new SqlConnection("connectionString"))
{
await connection.OpenAsync();
using (var transaction = connection.BeginTransaction())
{
try
{
using (var context = new MeuContexto())
{
context.Database.UseTransaction(transaction);
// Realize as operações que deseja realizar dentro da transação
context.Pessoas.Add(new Pessoa { Nome = "João" });
context.Pessoas.Add(new Pessoa { Nome = "Maria" });
await context.SaveChangesAsync();
}
// Se chegou até aqui sem erros, comite a transação
transaction.Commit();
}
catch (Exception ex)
{
// Se ocorrer um erro, desfaça a transação
transaction.Rollback();
Console.WriteLine($"Erro: {ex.Message}");
}
}
}
|
Neste exemplo, estamos criando uma nova conexão com o banco de dados e abrindo-a usando o método OpenAsync(), e a seguir estamos iniciando uma nova transação usando o método BeginTransaction() da conexão.
Dentro do bloco try da transação, estamos criando uma nova instância do contexto de banco de dados do EF Core (MeuContexto) e, em seguida, chamando o método UseTransaction() do objeto Database do contexto, passando o objeto SqlTransaction criado anteriormente.
Em seguida, realizamos algumas operações no banco de dados usando o contexto do EF Core, adicionando duas pessoas ao banco de dados e salvando as alterações usando o método SaveChangesAsync().
Se todas as operações dentro do bloco try forem bem sucedidas, a transação é comitada usando o método Commit() do objeto SqlTransaction.
No entanto, se ocorrer um erro durante a execução das operações, a transação é desfeita usando o método Rollback() do objeto SqlTransaction. Além disso, o erro é capturado e uma mensagem de erro é exibida na tela.
Desta forma, vimos
que o EF Core tem um excelente suporte para transações sendo muito fácil de
usar.
Você tem três opções disponíveis:
1- Confie no comportamento de transação padrão;
2- Criar uma nova transação;
3- Usar uma transação existente;
Na maioria das vezes, você deseja confiar no comportamento padrão e não precisa
pensar nisso.
E estamos conversados...
Referências:
NET - Unit of Work - Padrão Unidade de ...