Por que você não deve usar o Entity Framework com transações

Estrutura de entidade

Este é um .net ORM Mapper Framework da Microsoft para ajudá-lo a se comunicar com seu banco de dados de uma maneira orientada a objetos. Wikipedia

Transação de banco de dados

Uma transação de banco de dados, por definição, deve ser atômica, consistente, isolada e durável. Os profissionais de banco de dados geralmente se referem a essas propriedades de transações de banco de dados usando a sigla ACID . As transações em um ambiente de banco de dados têm dois objetivos principais:

  1. Fornecer unidades de trabalho confiáveis ​​que permitam a recuperação correta de falhas e mantenham um banco de dados consistente mesmo em casos de falha do sistema, quando a execução é interrompida (total ou parcialmente) e muitas operações em um banco de dados permanecem incompletas, com status pouco claro.
  2. Para fornecer isolamento entre programas que acessam um banco de dados simultaneamente. Se esse isolamento não for fornecido, o resultado do programa pode estar errado. Wikipedia

Transações .NET

Uma transação .NET pode ser usada de maneiras diferentes por estruturas diferentes para oferecer suporte a transações. A transação .NET em si não está conectada ao banco de dados de forma alguma. MSDN

Transações .NET e EntityFramework

Se você estiver usando o Entity Framework durante um TransactionScope aberto, EntityFramework abrirá uma nova Transação com o próximo comando que será enviado ao Banco de Dados ( Operação CRUD ).

Considere este bloco de código:

using (var transaction = new System.Transactions.TransactionScope())
{
// DBC = Database Command

// create the database context
var database = new DatabaseContext();

// search for the user with id #1
// DBC: BEGIN TRANSACTION
// DBC: select * from [User] where Id = 1
var userA = database.Users.Find(1);
// DBC: select * from [User] where Id = 2
var userB = database.Users.Find(2);
userA
.Name = "Admin";

// DBC: update User set Name = "Admin" where Id = 1
database
.SaveChanges();

userB
.Age = 28;
// DBC: update User set Age = 28 where Id = 2
database
.SaveChanges();

// DBC: COMMIT TRANSACTION
transaction
.Complete();
}

https://gist.github.com/SeriousM/e6b30db2b21e7e602655#file-bad_example-cs

A chamada envia suas alterações para o banco de dados e as executa, mas elas não são realmente persistidas porque você está no escopo da transação do banco de dados. realmente termina a transação do banco de dados e seus dados são salvos.database.SaveChanges()transaction.Complete()

Esse comportamento é realmente legal e muito útil, certo?

NÃO. Absolutamente não. ponto final.

Por que não usar .NET Transactions junto com EntityFramework

O modo de isolamento padrão é read committede se encaixa perfeitamente em 99% de suas necessidades, por exemplo. ler dados. Quando você deseja salvar as alterações feitas no banco de dados (Criar, Atualizar, Excluir), EntityFramework é inteligente o suficiente para criar uma transação sem seu aviso nos bastidores para encerrar as alterações. Você pode ter certeza que tudo será salvo ou todas as alterações serão descartadas ( Atomicidade ).

Ao usar transações em EntityFramework, você altera esse comportamento e força cada operação CRUD durante um escopo de transação a ser executada em serializablemodo de isolamento, que é o tipo mais alto e de maior bloqueio. Nenhum processo será capaz de acessar as tabelas que você tocou (mesmo lendo) durante sua transação. Isso pode levar a Deadlocks muito rápido e você deseja evitá-los a todo custo!

É assim que o código se parece sem o uso explícito de transações:

// DBC = Database Command

// create the database context
var database = new DatabaseContext();

// search for the user with id #1
// DBC: select * from [User] where Id = 1
var userA = database.Users.Find(1);
// DBC: select * from [User] where Id = 2
var userB = database.Users.Find(2);
userA
.Name = "Admin";
userB
.Age = 28;

// DBC: BEGIN TRANSACTION
// DBC: update User set Name = "Admin" where Id = 1
// DBC: update User set Age = 28 where Id = 2
// DBC: COMMIT TRANSACTION
database
.SaveChanges();

https://gist.github.com/SeriousM/e6b30db2b21e7e602655#file-good_example-cs

Regra geral: salve apenas uma vez por tarefa e não use transações.

Edit: thanks to @seimur – Um thread deve ter acesso a uma instância de DbContext que funciona melhor em aplicativos da web onde cada solicitação atua como um thread. Em aplicativos do Windows, cada comando ou tarefa deve ter um DbContext que não é compartilhado. Se você compartilhar o DbContext entre threads, poderá ter problemas como ter leituras furtivamente em uma transação estrangeira.