O .NET Framework já fornece nativamente suporte para a criação de contextos transacionais através da classe TransactionScope. Além da possibilidade de alistar automaticamente recursos como base de dados, message queue, etc., é possível customizar e alistar também recursos voláteis, onde podemos customizar e proteger algum código que queremos que seja efetivado quando ocorrer o commit, ou desfeito se o rollback ocorrer.
Para essa customização, basta implementarmos a interface IEnlistmentNotification, que fornece os métodos específicos e permite ao recurso interceptar cada um dos momentos definidos pelo protocolo 2PC (two phase commit): prepare, commit e rollback. Uma implementação simples desta interface é exibida abaixo. É importante notar que dentro do construtor já avaliamos se a classe está sendo instanciada dentro de um ambiente transacionado, e se sim, já alistamos o recurso para ser notificado quando os respectivos eventos ocorrer.
public class Teste1 : IEnlistmentNotification
{
public Teste1()
{
if (Transaction.Current != null)
Transaction.Current.EnlistVolatile(
this, EnlistmentOptions.EnlistDuringPrepareRequired);
}
public void Commit(Enlistment enlistment) { }
public void InDoubt(Enlistment enlistment) { }
public void Prepare(PreparingEnlistment preparingEnlistment) { }
public void Rollback(Enlistment enlistment) { }
}
Os métodos são autoexplicativos. Mas vamos focar no método Prepare. Este método é disparado durante a primeira fase do protocolo 2PC, onde o gerenciador interrogando o recurso se pode ou não realizar o commit. É importante falarmos sobre o disparo de exceções aqui. É importante envolvermos todo o código que está dentro deste método em um bloco try/catch, pois se houver uma exceção não tratada aqui, isso fará com que o .NET aborte os métodos de rollbacks de outros recursos que estejam também alistados. Imagine que o método Rollback acima esteja implementado da seguinte forma:
public void Prepare(PreparingEnlistment preparingEnlistment)
{
throw new Exception("Erro no Teste1.");
}
Para provar que o problema de fato ocorre, vamos criar uma segunda classe que implemente esta mesma interface, e em seu método Rollback, simplesmente vamos escrever uma mensagem na tela. Conside o código abaixo, que instancia as duas classes (ambas implementam a interface em discussão aqui), e quando o método Prepare é chamado sobre o objeto teste1 disparando a exceção não tratada, o .NET aborta o resto e, consequentemente, o método Rollback do objeto teste2 não é disparado.
using (var transaction = new TransactionScope())
{
var teste1 = new Teste1();
var teste2 = new Teste2();
transaction.Complete();
}
O comportamento que esperamos não é este, ou seja, temos também que garantir que o Rollback seja disparado com sucesso nos demais objetos que estão a seguir e fazem parte do mesmo contexto transacional. Para resolvermos isso, basta proteger o código do método Prepare em um bloco try/catch, e se algum problema ocorrer, basta chamar o método ForceRollback, indicando ao runtime do .NET que a transação deve ser desfeita. E ainda, se quiser prover informações adicionais sobre o problema ocorrido, basta capturar a exceção e informando-a para um overload deste mesmo método, que passa adiante o problema (via InnerException), protegendo toda stack trace e entregando a mensagem real do problema. Sendo assim, a implementação final é:
public void Prepare(PreparingEnlistment preparingEnlistment)
{
try
{
throw new Exception("Erro no Teste1");
preparingEnlistment.Prepared();
}
catch (Exception ex)
{
preparingEnlistment.ForceRollback(ex);
}
}










