As transações são essenciais para garantir a consistência e integridade dos dados em operações que envolvem múltiplos comandos no banco de dados. No .NET, o Dapper oferece uma maneira simples e eficiente de gerenciar transações com a interface “IDbTransaction”. Neste artigo, vamos explorar como implementar transações com Dapper.
Já discutimos anteriormente como trabalhar com transações utilizando o Entity Framework em outro artigo, mas agora vamos focar na abordagem com o Dapper, que é uma alternativa mais leve e performática para acessar dados diretamente no banco.
As transações são um conjunto de operações executadas como uma única unidade de trabalho. Elas garantem que todas as alterações no banco de dados sejam concluídas com sucesso ou, em caso de falha, revertidas, mantendo a integridade dos dados.
As transações seguem os princípios ACID:
Atomicidade: ou todas as operações ocorrem, ou nenhuma ocorre.
Consistência: o banco de dados sempre vai de um estado válido para outro.
Isolamento: transações concorrentes não interferem umas nas outras.
Durabilidade: alterações confirmadas são permanentes.
As transações são essenciais para evitar dados inconsistentes ou falhas de integridade, especialmente em sistemas críticos. Elas garantem que, mesmo diante de falhas, o banco de dados permaneça em um estado confiável.
Em sistemas reais, muitas operações exigem que várias ações no banco de dados sejam realizadas juntas. Se uma dessas ações falhar, não faz sentido que as outras permaneçam. Sem transações, o banco pode ficar em um estado inconsistente, o que é perigoso especialmente em sistemas financeiros, de estoque ou de controle de pedidos.
Imagine um sistema de vendas. Ao registrar um novo pedido, duas ações são feitas: inserir os dados do pedido no banco de dados e atualizar o estoque, reduzindo a quantidade de itens disponíveis.
Agora veja o seguinte cenário sem o uso de transações:
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
// Passo 1: Inserir o pedido
connection.Execute("INSERT INTO Pedido (...) VALUES (...)");
// Passo 2: Deduzir o item do estoque
connection.Execute("UPDATE Produto SET Quantidade = Quantidade - 1 WHERE
Id = @id", new { id = 1 });
}
Caso ocorra alguma falha na segunda operação, como um erro de item inexistente ou estoque insuficiente, o pedido terá sido registrado sem que o estoque tenha sido atualizado. O banco de dados agora está inconsistente: o pedido existe, mas o produto não foi removido do estoque.
Para contornar essa situação, podemos utilizar a interface “IDbTransaction” do Dapper, que permite iniciar, controlar e finalizar transações de forma eficiente.
Primeiro, precisamos abrir uma conexão com o banco de dados. No Dapper, isso é feito utilizando um objeto “IDbConnection”, que pode ser configurado com a string de conexão adequada.
using (IDbConnection dbConnection = new SqlConnection(connectionString))
{
dbConnection.Open();
using (var transaction = dbConnection.BeginTransaction())
{
// Operações dentro da transação
}
}
Neste código, a conexão é criada dentro de um bloco “using”, o que significa que a instância será descartada automaticamente ao final do bloco. Após isso, criamos uma instância de “IDbConnection” e repassamos a string de conexão para o banco de dados. Com isso, podemos abrir a conexão com o banco usando o método “Open()”.
Após abrir a conexão com o banco de dados, iniciamos a transação. Para isso, usamos o método “BeginTransaction”, que cria e retorna um objeto “IDbTransaction”. Esse objeto será responsável por controlar o início e o fim da transação.
Agora que a transação está ativa, podemos executar comandos SQL dentro dessa transação usando Dapper. Para garantir que a operação faça parte da transação, devemos passar o objeto transaction como parâmetro para os métodos de execução, como “Execute” ou “Query”.
// Primeira operação: Inserir um pedido
string queryInsertPedido = "INSERT INTO Pedidos (ClienteId, DataPedido) VALUES (@ClienteId, @DataPedido)";
dbConnection.Execute(queryInsertPedido, new { ClienteId = 1, DataPedido = DateTime.Now }, transaction: transaction);
// Segunda operação: Atualizar o estoque de um produto
string queryUpdateEstoque = "UPDATE Produtos SET Estoque = Estoque - @Quantidade WHERE Id = @ProdutoId";
dbConnection.Execute(queryUpdateEstoque, new { Quantidade = 1, ProdutoId = 758 }, transaction: transaction);
Aqui, inserimos um novo pedido na tabela “Pedidos” e atualizamos o estoque de um produto na tabela “Produtos”. Essa operação subtrai 1 unidade do estoque de um produto específico. Essas duas ações devem acontecer com sucesso para que as alterações sejam salvas. Caso contrário, as modificações serão revertidas.
Após a execução dos comandos, o próximo passo é decidir se as alterações devem ser confirmadas ou revertidas. Caso tudo ocorra sem erros, utilizamos “Commit()” para persistir as alterações. Se algo der errado, chamamos “Rollback()” para desfazer as operações realizadas até aquele momento.
try
{
//Código implementado acima.
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
Neste exemplo, se houver uma falha será lançada uma exceção e a transação é revertida com “Rollback()”, garantindo que o banco de dados não seja alterado de forma inconsistente.
Ao trabalhar com transações, algumas boas práticas devem ser seguidas:
Manter transações curtas: transações longas podem causar bloqueios no banco de dados, impactando o desempenho. Evite manter transações abertas por tempo prolongado.
Sempre usar using: usar a instrução using garante que os objetos de conexão e transação sejam descartados corretamente após o uso, evitando vazamentos de recursos. Sem o “using” seria necessário fechar a transação manualmente com o “await connection.CloseAsync();”
Capturar exceções específicas: ao capturar exceções, evite usar blocos catch genéricos. Isso ajuda a diagnosticar problemas mais rapidamente.
Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui
Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui
As transações são essenciais para garantir a integridade dos dados em sistemas que exigem operações atômicas. Ao utilizar Dapper, temos a vantagem de um controle direto sobre a transação, o que torna o processo eficiente e flexível. Seguir boas práticas e usar transações corretamente é fundamental para o sucesso de qualquer aplicação que interaja com bancos de dados, seja em Dapper ou em outros frameworks como o Entity Framework que já tratamos aqui.