Antes de unir os dois assuntos, falarei um pouco de cada um para entendermos melhor a ideia deste artigo.

TransactionScope

Presente no .Net Framework desde a versão 2.0, TransactionScope fica disponível após adicionar referencia para System.Transactions, e fornece uma maneira simples de marcar um bloco de código como participante em uma transação, sem a necessidade de interagir com a transação em si. Um TransactionScope pode gerenciar o ambiente da transação automaticamente, inclusive transações de bancos de dados, como o Sql Server. A sintaxe de utilização é algo como isto:

// C#
using (TransactionScope scope = new TransactionScope())
{
    // Código que participa da transação

    scope.Complete();
} // Commit ou RollBack
' VB.NET
Using scope As New TransactionScope()
    ' Código que participa da transação

    scope.Complete()
End Using ' Commit ou RollBack

Mais detalhes sobre TransactionScope:
Classe TransactionScope
Enumerador TransactionScopeOption


Entity Framework

Acho que aqui não é necessário definir ou conceituar o Entity Framework, e sim, expor os motivos de usar o TransactionScope junto a ele. Se tiver interesse em ler conceitos sobre Entity Framework, clique aqui, aqui ou aqui.

Trabalhar com Entity Framework e um banco de dados de tamanho pequeno é simples, pois criamos o Modelo Objeto-Relacional (MOR) e já podemos sair desenvolvendo.  Quando temos um banco de dados muito grande, se colocarmos tudo em um único modelo, podemos ter diversos problemas, entre eles, desempenho e dificuldade em organização. Em geral, é recomendado ter modelos com até 100 entidades. Passando disso, já devemos começar a pensar em dividir em vários modelos.

NOTA: Utilizando apenas um modelo, todas as atualizações feitas através do Entity Framework já acontecem em uma transação, ou seja, se ocorrer algum erro antes que o método SaveChanges() esteja concluído, o RollBack é garantido. Neste caso, não é necessário usar uma transação explícita.

Aí vem a pergunta: E para controlar transações usando mais de um modelo? O intuído de escrever esse artigo é justamente estudar um pouco mais este assunto.


Transações usando mais de um Modelo Objeto-Relacional

Uma das maneiras de termos a mesma transação para mais de um modelo é usarmos o TransactionScope. A aplicação é simples e muito útil. Melhor do que escrever, é ir direto para o código.

// C#
using System;
using System.Transactions;
using EntityTransactionCS.Model1;
using EntityTransactionCS.Model2;

namespace EntityTransactionCS
{
    class Program
    {
        static void Main(string[] args)
        {
            bool bolSucesso = false;

            // Instanciando o Modelo1
            Modelo1 context1 = new Modelo1();

            // Instanciando o Modelo2
            Modelo2 context2 = new Modelo2();

            try
            {
                // Envolvendo o processo por um TransactionScope
                using (TransactionScope scope = new TransactionScope())
                {
                    // Inserindo Cliente com o Modelo1
                    Model1.CLIENTES cliente = new Model1.CLIENTES()
                    {
                        NM_CLIENTE = "Fernando Ottoboni",
                        NR_IDADE = 28
                    };

                    //context1.AddToCLIENTES(cliente);
                    context1.CLIENTES.AddObject(cliente);

                    // Inserindo Produto com o Modelo2
                    Model2.PRODUTOS produto = new Model2.PRODUTOS()
                    {
                        NM_PRODUTO = "Notebook DELL Inspiron 1564 4G 320GB.",
                        VL_PRECO = 2699
                    };
                    //context2.AddToPRODUTOS(produto);
                    context2.PRODUTOS.AddObject(produto);

                    try
                    {
                        // Salva as alterações realizadas no Contexto
                        context1.SaveChanges();
                        context2.SaveChanges();

                        bolSucesso = true;
                    }
                    catch
                    {
                        bolSucesso = false;
                    }

                    if (bolSucesso)
                    {
                        // Completa a transação (Fará o Commit)
                        scope.Complete();
                    }

                    // Somente depois de passar pelo Complete() que o
                    // TransactionScope fará o Commit.
                    // Caso contrário, o RollBack será executado.

                } // Commit ou RollBack

                if (bolSucesso)
                    Console.WriteLine("Dados gravados.");
                else
                    Console.WriteLine("Problemas ao gravar.");
            }
            finally
            {
                context1.Dispose();
                context2.Dispose();
            }

            Console.ReadKey();
        }
    }
}
' VB.NET
Imports EntityTransactionVB.EntityTransactionVB
Imports EntityTransactionVB.EntityTransactionVB.Model1
Imports EntityTransactionVB.EntityTransactionVB.Model2
Imports System.Transactions

Module Module1

    Sub Main()
        Dim bolSucesso As Boolean = False

        ' Instanciando o Modelo1
        Dim context1 As New Modelo1()

        ' Instanciando o Modelo2
        Dim context2 As New Modelo2()

        Try
            ' Envolvendo o processo por um TransactionScope
            Using scope As New TransactionScope()

                ' Inserindo Cliente com o Modelo1
                Dim cliente As New Model1.CLIENTES() With { _
                    .NM_CLIENTE = "Fernando Ottoboni", _
                    .NR_IDADE = 28 _
                }
                'context1.AddToCLIENTES(cliente)
                context1.CLIENTES.AddObject(cliente)

                ' Inserindo Produto com o Modelo2
                Dim produto As New Model2.PRODUTOS() With {
                    .NM_PRODUTO = "Notebook DELL Inspiron 1564 4G 320GB.", _
                    .VL_PRECO = 2699 _
                }
                'context2.AddToPRODUTOS(produto)
                context2.PRODUTOS.AddObject(produto)

                Try
                    ' Salva as alterações realizadas no Contexto
                    context1.SaveChanges()
                    context2.SaveChanges()

                    bolSucesso = True
                Catch
                    bolSucesso = False
                End Try

                If bolSucesso Then
                    ' Completa a transação (Fará o Commit)
                    scope.Complete()
                End If

                ' Somente depois de passar pelo Complete() que o
                ' TransactionScope fará o Commit.
                ' Caso contrário, o RollBack será executado.

            End Using ' Commit ou RollBack

            If bolSucesso Then
                Console.WriteLine("Dados gravados.")
            Else
                Console.WriteLine("Problemas ao gravar.")
            End If
        Finally
            context1.Dispose()
            context2.Dispose()
        End Try

        Console.ReadKey()
    End Sub

End Module


NOTA 1:Para excluir da transação uma determinada camada do código, utilize TransactionScopeOption.Supress na chamada ao construtor da classe TransactionScope.

// C#
using (TransactionScope scope =
    new TransactionScope(TransactionScopeOption.Suppress))
{
    // Código a ser suprimido
}
' VB.NET
Using scope As _
    New TransactionScope(TransactionScopeOption.Suppress)
    ' Código a ser suprimido
End Using


NOTA 2: TransactionScope pode ser utilizada com vários modelos de acesso a dados, inclusive providers que não são distribuídos pela Microsoft. Veja um exemplo utilizando SqlConnection:

// C#
using (TransactionScope scope = new TransactionScope())
{
    using (SqlConnection connection1 = new SqlConnection(connectString1))
    {
        connection1.Open();

        // Operações com connection1

        using (SqlConnection connection2 = new SqlConnection(connectString2))
        {
            connection2.Open();

            // Operações com connection2
        }
    }

    scope.Complete();
}
' VB.NET
Using scope As New TransactionScope()
    Using connection1 As New SqlConnection(connectString1)
        connection1.Open()

        ' Operações com connection1

        Using connection2 As New SqlConnection(connectString2)
            connection2.Open()

            ' Operações com connection2
        End Using
    End Using

    scope.Complete()
End Using



Conclusão

É de suma importância (e também uma boa prática) controlarmos transações em nossa aplicações. A classe TransactionScope nos fornece as ferramentas idais para isso e, quando aplicada em situações em que temos que quebrar o Modelo Objeto-Relacional e várias partes, facilita muito a nossa vida e dá todo o suporte que precisamos para manipulação de transações.


O download do código fonte pode ser feito aqui.


Obrigado

Anúncios