SaveChanges e controle transacional no EF Core

Rafael P. Miranda
Juntos Somos Mais
Published in
4 min readMar 12, 2021

O método SaveChanges é responsável por salvar todas as alterações no banco de dados quando se está trabalhando com o Entity Framework Core. Ao executar esse método, por padrão, estamos executando as operações dentro de uma transação. A documentação diz:

Por padrão, se o provedor de banco de dados oferecer suporte a transações, todas as alterações em uma única chamada para SaveChanges serão aplicadas em uma transação. Se qualquer uma das alterações falhar, a transação é revertida e nenhuma das alterações será aplicada ao banco de dados.

Será tratado aqui o funcionamento do método SaveChanges analisando os logs do EF Core e as consultas geradas. O foco da análise será o controle da transação. A versão da biblioteca nos testes é a 5 e o banco de dados utilizado na análise é o SQL Server da Microsoft. Os resultados encontrados podem ser diferentes em outras versões e/ou utilizando outro bancos de dados. O código criado para os testes pode ser visto aqui.

Configurando o DbContext

O contexto do banco de dados deve ser configurado para obter as informações necessárias para o entendimento do EF Core.

O método LogTo, usado na configuração, é a nova maneira de acessar os logs do EF Core. A funcionalidade Simple Logging foi apresentada na versão 5 do ORM e permite direcionar as entradas de log para o tipo de saída desejada. No exemplo, os dados são direcionados para janela de Debug do Visual Studio usando o método Debug.Writeline. Nas versões antigas é possível obter os logs usando o método UseLoggerFactory.

Adicionando dados com o SaveChanges

Para verificar o funcionamento do SaveChanges foi escrito um teste simples adicionando dois objetos no banco de dados. No banco serão armazenadas duas linhas, uma em cada tabela.

É possível ver no log gerado que os primeiros passos do EF Core na execução do método SaveChanges é a verificação das mudanças das entidades do contexto. Logo em seguida, a conexão com o banco é aberta.

As entradas de log seguintes são relacionadas à transação. No primeiro momento, a transação é iniciada com o nível de isolamento não especificado. A próxima mensagem mostra que a transação foi inicializada com o nível ReadCommitted, que é o padrão do SQL Server.

Em seguida são executados dois comandos INSERT, um para cada entidade. Como o Id das entidades são gerados pelo banco de dados, pode-se ver que, para cada INSERT, também foi realizado um SELECT para que o EF Core obtenha o Id da entidade inserida. Alguns logs de criação dos comandos e detecção de alterações de chave estrangeira foram omitidos.

Por último, é feito o commit da transação.

Controlando a transação

Para a maioria dos casos, o uso do SaveChanges é o suficiente para garantir a consistência dos dados. Porém, quando necessário, é possível controlar as transações manualmente.

O segundo teste utiliza o mesmo exemplo do primeiro. Dessa vez o método SaveChanges está sendo executado dentro de uma transação iniciada com método BeginTransaction.

As primeiras entradas do log são de abertura da transação. Como o primeiro teste, a transação é iniciada com o nível de isolamento não especificado e criada com o nível ReadCommited.

Ao executar o método SaveChanges, o EF Core cria um savepoint da transação ao invés de abrir uma transação aninhada. Os logs de detecção de alterações nas entidades foram omitidos.

Em seguida se realiza a execução dos comandos da mesma forma do primeiro teste.

Por fim, ao executar o método transaction.Commit(), as alterações são confirmadas.

Diferença entre transações aninhadas e savepoints no SQL Server

No SQL Server, o uso de transações aninhadas tem um comportamento que pode confundir os desenvolvedores. O ROLLBACK reverte todas as transações abertas da sessão. Não importa se existam transações aninhadas, o ROLLBACK vai desfazer todas as operações realizadas desde o primeiro BEGIN TRANSACTION.

Já o SAVE TRANSACTION coloca uma tag no ponto desejado da transação de forma a ser possível executar o ROLLBACK e reverter as mudanças até esse ponto. É possível criar vários savepoints em uma mesma transação. É importante lembrar que o SAVE TRANSACTION não abre uma nova transação.

Diferença entre transações aninhadas e save

Savepoints no EF Core

O EF Core também permite a criação manual de savepoints usando o método CreateSavepoint.

Versões anteriores

A funcionalidade de savepoints foi incluída no EF Core 5. Nas versões 3.1 e 2.1 o EF Core, além de não permitir a criação de savepoints de forma manual, também não executa um SAVE TRANSACTION durante a execução do método SaveChanges. Ou seja, caso já exista uma transação aberta, como no segundo teste, o SaveChanges apenas executa os comandos de alteração da base de dados.

Conclusão

O método SaveChanges facilita a vida do desenvolvedor ao executar os comandos de alteração no banco de dados dentro de uma transação. Para os outros casos, em que se faz a abertura de uma transação manualmente, o SaveChanges apenas cria um savepoint dentro da transação.

Referências

  1. Simple Logging — https://docs.microsoft.com/pt-br/ef/core/logging-events-diagnostics/simple-logging
  2. Usando transações — https://docs.microsoft.com/pt-br/ef/core/saving/transactions
  3. Guia de Controle de Versão de Linha e Bloqueio de Transações — https://docs.microsoft.com/pt-br/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-ver15
  4. Nesting transactions and SAVE TRANSACTION command — https://dba-presents.com/index.php/databases/sql-server/43-nesting-transactions-and-save-transaction-command
  5. SAVE TRANSACTION — https://docs.microsoft.com/pt-br/sql/t-sql/language-elements/save-transaction-transact-sql?view=sql-server-ver15
  6. https://github.com/dotnet/efcore/blob/release/5.0/src/EFCore.Relational/Update/Internal/BatchExecutor.cs#L89
  7. https://github.com/dotnet/efcore/blob/release/2.1/src/EFCore.Relational/Update/Internal/BatchExecutor.cs#L70

--

--