Transações em .net

144

Quais são as melhores práticas para fazer transações no C # .Net 2.0. Quais são as classes que devem ser usadas? Quais são as armadilhas a serem observadas, etc. Todas essas coisas de confirmação e reversão. Estou apenas começando um projeto em que talvez seja necessário fazer algumas transações enquanto insere dados no banco de dados. Quaisquer respostas ou links para itens básicos sobre transações são bem-vindos.

Malik Daud Ahmad Khokhar
fonte
Aqui está um bom exemplo de Transações no .NET no codeproject para usar como início.
Mitchel Sellers

Respostas:

271

Existem 2 tipos principais de transações; transações de conexão e transações ambientais. Uma transação de conexão (como SqlTransaction) está vinculada diretamente à conexão db (como SqlConnection), o que significa que você deve continuar transmitindo a conexão - OK em alguns casos, mas não permite "criar / usar / liberar" uso e não permite trabalho entre db. Um exemplo (formatado para espaço):

using (IDbTransaction tran = conn.BeginTransaction()) {
    try {
        // your code
        tran.Commit();
    }  catch {
        tran.Rollback();
        throw;
    }
}

Não é muito confuso, mas limitado à nossa conexão "conn". Se queremos chamar métodos diferentes, agora precisamos passar o "conn".

A alternativa é uma transação ambiental; novo no .NET 2.0, o objeto TransactionScope (System.Transactions.dll) permite o uso em várias operações (provedores adequados se inscreverão automaticamente na transação do ambiente). Isso facilita a adaptação retroativa ao código existente (não transacional) e a conversa com vários fornecedores (embora o DTC se envolva se você falar com mais de um).

Por exemplo:

using(TransactionScope tran = new TransactionScope()) {
    CallAMethodThatDoesSomeWork();
    CallAMethodThatDoesSomeMoreWork();
    tran.Complete();
}

Observe aqui que os dois métodos podem lidar com suas próprias conexões (abrir / usar / fechar / descartar), mas silenciosamente se tornarão parte da transação do ambiente sem que tenhamos que passar nada.

Se os erros do seu código, Dispose () serão chamados sem Complete (), serão revertidos. O aninhamento esperado etc é suportado, embora você não possa reverter uma transação interna e ainda concluir a transação externa: se alguém estiver infeliz, a transação será abortada.

A outra vantagem do TransactionScope é que ele não está vinculado apenas aos bancos de dados; qualquer provedor que reconhece transações pode usá-lo. WCF, por exemplo. Ou ainda existem alguns modelos de objetos compatíveis com TransactionScope (por exemplo, classes .NET com capacidade de reversão - talvez mais fáceis do que uma lembrança, embora eu nunca tenha usado essa abordagem).

Em suma, um objeto muito, muito útil.

Algumas advertências:

  • No SQL Server 2000, um TransactionScope irá para o DTC imediatamente; isso é corrigido no SQL Server 2005 e acima, ele pode usar o LTM (muito menos sobrecarga) até você falar com 2 fontes, etc., quando for elevado ao DTC.
  • Há uma falha que significa que você pode precisar ajustar sua cadeia de conexão
Marc Gravell
fonte
O CSLA .NET 2.0 suporta o objeto TransactionScope!
Binoj Antony 17/03/09
O problema aqui é quando você tem uma transação no primeiro método e esse método (encapsulamento) não sabe se será chamado de uma transação pai ou não.
Eduardo Molteni
1
@ Eduardo - isso não é um problema ao usar o TransactionScope, tornando-o muito atraente. Essas transações são aninhadas e apenas as mais externas são confirmadas.
Marc Gravell
Espero que você ainda esteja ouvindo. Você disse que existem "alguns modelos de objetos compatíveis com TransactionScope". Você pode me apontar para alguns deles? THX.
majkinetor
1
Novamente Marc, outra excelente explicação. Quando você diz que 'o aninhamento esperado é suportado' é o de blocos de transação definidos nos métodos (por exemplo, CallAMethodThatDoesSomeWork ())? Ou com o escopo de transações definido fora, não é necessário?
Phil Cooper
11
protected void Button1_Click(object sender, EventArgs e)
   {


       using (SqlConnection connection1 = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\Database.mdf;Integrated Security=True;User Instance=True"))
       {
           connection1.Open();

           // Start a local transaction.
           SqlTransaction sqlTran = connection1.BeginTransaction();

           // Enlist a command in the current transaction.
           SqlCommand command = connection1.CreateCommand();
           command.Transaction = sqlTran;

           try
           {
               // Execute two separate commands.
               command.CommandText =
                "insert into [doctor](drname,drspecialization,drday) values ('a','b','c')";
               command.ExecuteNonQuery();
               command.CommandText =
                "insert into [doctor](drname,drspecialization,drday) values ('x','y','z')";
               command.ExecuteNonQuery();

               // Commit the transaction.
               sqlTran.Commit();
               Label3.Text = "Both records were written to database.";
           }
           catch (Exception ex)
           {
               // Handle the exception if the transaction fails to commit.
               Label4.Text = ex.Message;


               try
               {
                   // Attempt to roll back the transaction.
                   sqlTran.Rollback();
               }
               catch (Exception exRollback)
               {
                   // Throws an InvalidOperationException if the connection 
                   // is closed or the transaction has already been rolled 
                   // back on the server.
                   Label5.Text = exRollback.Message;

               }
           }
       }


   }
Ali Gholizadeh
fonte
4

Você também pode agrupar a transação em seu próprio procedimento armazenado e manipulá-la dessa maneira, em vez de fazer transações no próprio C #.

Charles Graham
fonte
1

se você precisar apenas de itens relacionados ao banco de dados, alguns OR Mappers (por exemplo, NHibernate) suportam transactinos prontos para uso por padrão.

Joachim Kerschbaumer
fonte
0

Também depende do que você precisa. Para transações SQL básicas, você pode tentar fazer transações TSQL usando BEGIN TRANS e COMMIT TRANS no seu código. Essa é a maneira mais fácil, mas ela tem complexidade e você deve ter o cuidado de confirmar corretamente (e reverter).

Eu usaria algo como

SQLTransaction trans = null;
using(trans = new SqlTransaction)
{
    ...
    Do SQL stuff here passing my trans into my various SQL executers
    ...
    trans.Commit  // May not be quite right
}

Qualquer falha fará com que você saia da usingtransação e a transação será sempre confirmada ou revertida (dependendo do que você pedir). O maior problema que enfrentamos foi garantir que ele sempre se comprometesse. O uso garante que o escopo da transação seja limitado.

Brody
fonte