Entity Framework 6 – Database.BeginTransaction() e Database.UseTransaction(DbTransaction)

Ao falarmos sobre transações é bastante comum ouvir sobre o acrônimo ACID (Atomicidade, Consistência, Isolamento e Durabilidade). O conceito ACID descreve características necessárias em uma transação, sendo estes conceitos:

Atomicidade: uma transação é uma sequência de operações tratadas como um bloco único e indivisível. Todas as suas operações devem ser executadas com sucesso ou nenhum resultado será refletido na base de dados.

Consistência: a execução de uma transação deve manter a integridade dos dados.

Isolamento: transações paralelas não devem interferir na execução umas das outras. Existem diferentes tipos de isolamentos, algo que veremos a seguir.

Durabilidade: ao executar com sucesso, os efeitos da transação (os dados alterados, inseridos e excluídos) devem estar disponíveis em definitivo.

acid

Anteriormente, antes da versão do Entity Framework 6, era bastante comum o uso do objeto TransactionScope para controle de transações com o Entity Framework. Mas existem cenários no qual esse objeto não é suportado, por exemplo: SQL CE. Neste caso era bastante comum a criação de uma DbTransaction, por meio do acesso ao DbConnection contido na propriedade Connection da classe DataBase. O bloco de código abaixo demonstra essa prática.

using (var context = new DataContext()) {
    using (DbTransaction tran = context.Database.Connection.BeginTransaction()) {
               
    }
}

E abaixo um exemplo de uso do objeto TransactionScope.

using (var context = new DataContext()) {
    using (TransactionScope transaction = new TransactionScope()) {

    }
}

Detalhe importante: vale ressaltar que o método SaveChanges já opera com uma transação. Neste caso, quando invocamos o método SaveChanges, e por algum motivo, uma das entidades não é persistida, então todos as demais entidades também não são persistidas, mantendo a consistência dos dados. Então vale a pena avaliar quantos invoques ao método SaveChanges a sua operação executa, para então definir se o uso de componentes de transação são necessários ou não.

Agora, o Entity Framework 6 possui um controle nativo para suas transação, a classe DbContextTransaction. Podemos fazer uso deste objeto por meio do invoque do método Database.BeginTransaction().

Provavelmente a dúvida que pode surgir é: porque utilizar um novo recurso para controlar minhas transações? A resposta é simples: as demais soluções não são especializadas no comportamento do Entity Framework e o uso do objeto DbContextTransaction.

var process1 = new Process() { Description = "Task 01" };
var process2 = new Process() { Description = "Task 02" };
using (var context = new DataContext(connectionString)) {
    using (DbContextTransaction tran = context.Database.BeginTransaction(IsolationLevel.ReadCommitted)) {
        try {
            context.Database.ExecuteSqlCommand("UPDATE dbo.Process SET Description = 'Too late! Too late!', Status = 1 WHERE Status = 0");
            context.Processes.Add(process1);
            context.Processes.Add(process2);
            var itemToUpdate = context.Processes.First();
            context.SaveChanges();
            tran.Commit();
        }
        catch (Exception) {
            tran.Rollback();
        }
    }
}

O método BeginTransaction possui duas assinaturas. Uma dessas assinaturas não depende de parâmetro algum e a outra depende de uma enumeração que descreva o nível de isolamento das transações. Essa enumeração é a IsolationLevel, e nós podemos utilizar níveis como:

ReadCommitted Os processos de leitura ficam em espera caso existam outras transações de atualização em uma mesma tabela.
ReadUncommitted Não respeita os bloqueios exclusivos das transações permitindo a leitura de dados ainda não commitados. O fato de que dados não commitados possam ser lidos é chamado de "Leitura suja".
Snapshot Respeita os bloqueios das transações, mas os processos de leitura não ficam em espera. Os processos de leitura retornam apenas dados comitados, pois é criada uma versão do dados que possa ser lida enquanto os demais são modificados.

Existem outros níveis de isolamento que podem ser encontrados nessa enumeração. Você pode encontrar mais detalhes sobre os níveis de isolamento disponíveis por meio desse link: http://msdn.microsoft.com/pt-br/library/system.data.isolationlevel(v=vs.90).aspx

keep calm ef

Outro método disponível na classe Database, a partir do Entity Framework 6, é o UseTransaction(DbTransaction). Com o invoque desde método podemos compartilhar uma transação aberta com um bloco de comandos do Entity Framework. O exemplo demonstra o seu uso.

var process1 = new Process() { Description = "Task 01" };
var process2 = new Process() { Description = "Task 02" };
using (var connection = new SqlConnection(connectionString)) {
    connection.Open();
    using (var transaction = connection.BeginTransaction()) {
        try {
            var command = connection.CreateCommand();
            command.Transaction = transaction;
            command.CommandText = "UPDATE Process SET Description = 'Too late! Too late!', Status = 1 WHERE Status = 0";
            command.ExecuteNonQuery();
            using (var context = new DataContext(connection, false)) {
                context.Database.UseTransaction(transaction);
                context.Processes.Add(process1);
                context.Processes.Add(process2);
                context.SaveChanges();
            }
            transaction.Commit();
        }
        catch (Exception) {
            transaction.Rollback();
        }
    }
}

Observe que, dada uma DbConnection aberta, compartilha-se a conexão com o contexto do Entity Framework, e por meio do método UseTransaction(DbTransaction), compartilha-se a transação ativa. Desta forma, os comandos executados pelo Entity Framework são executados, respeitando o ciclo de vida da transação.

Acredito que estes dois novos recursos são bastante úteis e devem agregar em cenários híbridos, no qual encontramos comandos em ADO.Net e operações com o Entity Framework.

Por

MSc. Fernando Henrique Inocêncio Borba Ferreira

Microsoft Most Valuable Professional – Visual C#

Referências:

http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx

http://msdn.microsoft.com/en-us/library/vstudio/bb738523(v=vs.100).aspx

http://stackoverflow.com/questions/10428675/atomic-transactions-in-entity-framework-4-1-code-first-without-stored-procedures

https://entityframework.codeplex.com/wikipage?title=Improved%20Transaction%20Support

http://msdn.microsoft.com/en-us/library/system.data.isolationlevel.aspx

http://www.ici.curitiba.org.br/exibirArtigo.aspx?idf=40

Publicidade

15 comentários sobre “Entity Framework 6 – Database.BeginTransaction() e Database.UseTransaction(DbTransaction)

  1. Olá, Fernando. Gostei bastante dessa novidade. Parabéns por blogar essa feature. 🙂

    Tenho uma duvida (não cheguei de testar no show me the code), mas existe queda de performance ou seria uma best practice usar:

    using (var context = new MyContext()) {
    using (var trans = new TransactionScope()) {
    // code
    }
    }

    OU

    using (var trans = new TransactionScope()) {
    using (var context = new MyContext()) {
    // code
    }
    }

    Abs.,
    Nelson.

    • Olá Nelson,
      Tudo bem?
      Não existe uma queda de performance ou melhor prática para o momento de criação do TransactionScope se comparado ao momento de criação da instância do contexto.

      Obrigado por postar.
      []s!

  2. Fala Fê!

    Muito importante essa nova feature, gostei muito da explicação e exemplos, foi claro e didático.

    Já guardei de referência aqui, obrigado!
    Abraço.

  3. Bacana…Mas Fernando sabe me dizer se todas essas novas funcionalidades que você cita no Entity funcionará em bancos além do Sql server? rs, eu mesmo to me peidando pra fazer funcionar no postgres o entity, vim do nhibernate que todos falam que é maior a curva de aprendizado e blablabla, mas vejo no Entity uma total falta de suporte com outros bancos, não só de conexão, mas também performance, isso me desanima, ainda mais agora que ele virou opensource o vs 2013 com asp.net identity totalmente integrado a ele…etc, mas só fica 100% se for sql server….nos demais? vish, nem rezando :/

    Será que já estão resolvendo isso? já foi resolvido isso? qual versão?

    • Olá Rodrigo! Tudo bem?

      Sim, realmente esse é um ponto importante. O time do EF procura, sempre que possível, gerar comandos SQL q funcionem em diferentes bancos de dados, mas nem sempre todos os recursos funcionam desta maneira, pois alguns são bastante específicos para cada banco de dados. Assim, a MS deixa essa parte por conta dos demais fabricantes, para que eles criem os providers para seus respectivos bancos de dados. Não sei realmente sei esse é o melhor caminho, pois me parece q alguns fabricantes não estão motivados a fazer isso ou não temos desenvolvedores na comunidade motivados a fazer isso.

      Vc realmente tocou em um assunto crítico e base para mtas críticas ao EF. Talvez, após o lançamento da versão 6, nós possamos reunir alguns devs e começar a trabalhar nessa linha, criando novos providers e expandindo o mundo do EF.

      Acho q dependerá apenas de nós, os devs da comunidade.

      Obrigado por postar.
      Anotei seu email, nos falaremos logo.
      []s!

  4. Fala Fernando!

    Gostaria de saber sua opinião, acerca do uso adequado do EF.

    Venho acompanhando a evolução do mesmo desde a versão 4.3 e só agora vejo maturidade para utiliza-lo em um novo projeto, que considero de grande porte.
    Minha preocupação ao usar ORMs foi sempre com performance, e por isso venho pensando estratégias para extrair o máximo de performance possível.

    Minha primeira pergunta é bem direta: Vocês recomendam o uso do EF para um modelo com mais de 500 entidades? (Detalhe: Code First + FluentAPI)

    Se a resposta for sim, penso que não seria nada interessante e nem performático manipular uma classe de contexto mapeando mais de 500 artefatos, e por isso tenho pensando em segmenta-la. A idéia seria criar classes de contexto por módulos do sistema, até porque esses módulos serão projetos separados, onde cada um terá sua camada (lógica) de negócio e dados.
    Tenho pensando nessa possibilidade pelo fato de ser possível a partir da versão 6, como você mencionou no post, fazer o controle da conexão externadamente.
    O que acham dessa idéia?

    Minha terceira pergunta está ligado a estratégia de conexão. O projeto em questão trata-se de uma aplicação desktop (Windows Forms, infelizmente rs).
    Como disse, nas versões anteriores não era possível fazer o controle da conexão do Entity, a cada operação realizado o mesmo tratava de abrir conexão, transação e fechá-las (Me corrija se eu estiver errado). Imagine o cenário onde o banco dados é remoto, ou até mesmo em rede local, o tempo levado para estabelecer uma conexão é bem relevante, em alguns casos muito alto, por isso tenho pensado na hipótese de manter uma conexão aberta durante o tempo de vida da aplicação, e compartilhar essa conexão com os objetos de contexto. O que acha?

    Quero muito ouvir sua opinião.
    Abraço !

    • Olá Danilo,
      Tudo beleza?

      Sim, realmente este é um projeto de grande porte.

      Manipular uma classe de contexto com 500 entidades não é recomendado e nem saudável (hehehe). Um contexto com 500 entidades terá um tempo de inicialização gigantesco, a construção das queries será muito lenta, o desenvolvimento será difícil e a manutenção de um único arquivo dentro de uma equipe será sacal (aja check-in, check-out e lock de arquivos!).

      Dessa forma, não recomendo a criação de um contexto com 500 entidades. Na verdade, um modelo com mais de 40 entidades já deveria ser revisto, pois pode apresentar problemas de performance.

      Eu tenho apoiado uma abordagem chamada DDD Bounded Contexts (http://msdn.microsoft.com/en-us/magazine/jj883952.aspx). Essa abordagem preza que os contextos sejam criados com as entidades necessárias para resolver um problema específico de domínio. Assim, os contextos chegam a conter de 3 a 9 entidades, fazendo o compartilhamento de entidades entre outros contextos. Obs.: Apenas relembrando que uma entidade não precisa estar limitada a um único contexto, ela pode ser utiliza por N contextos ao mesmo tempo.

      Vc comentou que se preocupa com performance, e que deseja atingir o máximo de performance possível. Desta forma, tenha algo em mente: “vc não utilizará apenas EF, o ADO.NET ainda será um amigo muito próximo”. Apesar de todas as melhorias de performance, o EF (assim como qualquer outra ferramenta ORM) nunca será tão rápido quanto o ADO.NET.

      A construção de camadas de acesso a dados é complexa e exige cuidados (assim como a construção de qualquer outra camada de sua aplicação). Quando utilizamos ferramentas ORM temos a tendência de perder performance, pois os comandos SQL gerados automaticamente para consultas não são dos melhores, além de não serem tão performáticos quanto stored procedures. Da mesma forma, quando trabalhamos com ADO.NET somos obrigados a trabalhar com dois paradigmas diferentes: orientados a objetos vs. bancos de dados relacionais. Com ADO.NET somos obrigados a trabalhar com comandos SQL, mapeamento de objetos e transformação de dados relacionais em instâncias de objetos relacionados, além de perda de performance dos desenvolvedores, já que trabalhar com o ADO.NET geralmente demanda mais tempo de desenvolvimento.
      Dessa forma, acredito que o melhor caminho é o do meio: utilizar EF quando possível e ADO.NET quando necessário. EF e ADO.NET podem conviver dentro de um mesmo projeto facilmente. A tomada de decisão para isso exige maturidade e experiência. Quando for necessário inserir, apagar, excluir ou consultar algum registro simples, o EF pode fazer isso tranquilamente. Quando vc precisar inserir um bloco de registros, atualizar diferentes linhas ou executar uma query complexa, o ADO.NET estará lá para fazer o trabalho pesado e da forma mais performática possível.

      Lembre-se: ADO.NET = performance / Ferramentas ORM = facilidade de uso.
      Adotar os dois no mesmo projeto é vantajoso. Eu costumo fazer isso.
      Se vc tem problemas para estabelecer conexões com o banco de dados, então vc terá problemas com ADO.NET, EF, NHibernate ou qualquer outra ferramenta. O EF tem a vantagem de trabalhar com resiliência de conexões (http://msdn.microsoft.com/en-us/data/dn456835), algo que permite o contexto tentar recuperar uma conexão perdida com o banco de dados (essa feature foi criada por causa do SQL Azure, vale a pena estudar isso). Dessa forma, acredito que o seu problema é 80% infraestrutura e 20% desenvolvimento.

      Manter uma conexão aberta não deve ser feito. Uma hora ou outra o próprio SQL vai dropar a conexão por ficar muito tempo ativa. Isso não é saudável para a aplicação e para o banco de dados. E se vc tem problemas para conectar, então também deve ter problemas para manter a conexão. Acredito que é melhor fugir da ideia de manter uma conexão ativa. Talvez o uso de serviços seja o ideal, pois o serviço provavelmente estará mais próximo ao seu servidor SQL e trabalhará de forma mais rápida. Faltará saber os tempos gastos para consumir estes serviços 

      Qualquer coisa é só falar.
      []s!

  5. Nice read, I just passed this onto a friend who was doing some research on that. And he just bought me lunch since I found it for him smile So let me rephrase that Thank you for lunch! Whenever you have an efficient government you have a dictatorship. by Harry S Truman. feddeacfggfd

  6. olá fernando.

    Fiz testes com o ultimo exemplo e fiquei como uma dúvida. Quando SaveChanges é invocado o sql é enviado ao banco de dados, está correto? Não deveria ser quando se invoca commit?

    Utilizei o EF 6.1.3.

    Até…

    • Desculpe fernando, desconsidere o comentário acima, se quiser delete. Eu havia esquecido a lógica de uma transação, as operações são enviadas ao servidor do banco e podem ser canceladas num roolback. Falha minha desculpe.

  7. Quando utilizei ReadUncommitted tentei observar a query junto ao Sql Profile e não vi nenhum configuração ser adicionada a consulta enviada, como é realizada esta configuração junto ao Servidor de banco?

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.