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