Transacciones en .net

144

¿Cuáles son las mejores prácticas para realizar transacciones en C # .Net 2.0? ¿Cuáles son las clases que deberían usarse? Cuáles son las trampas a tener en cuenta, etc. Todo lo que se compromete y revierte. Estoy comenzando un proyecto en el que podría necesitar hacer algunas transacciones mientras inserto datos en la base de datos. Cualquier respuesta o enlace para incluso cosas básicas sobre transacciones son bienvenidas.

Malik Daud Ahmad Khokhar
fuente
Aquí hay un buen ejemplo de Transacciones en .NET en proyecto de código para usar como inicio.
Mitchel Sellers,

Respuestas:

271

Hay 2 tipos principales de transacciones; transacciones de conexión y transacciones ambientales. Una transacción de conexión (como SqlTransaction) está vinculada directamente a la conexión db (como SqlConnection), lo que significa que debe seguir pasando la conexión, en algunos casos está bien, pero no permite "crear / usar / liberar" uso, y no permite el trabajo cross-db. Un ejemplo (formateado para espacio):

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

No demasiado desordenado, pero limitado a nuestra conexión "conn". Si queremos llamar a diferentes métodos, ahora tenemos que pasar "conn".

La alternativa es una transacción ambiental; nuevo en .NET 2.0, el objeto TransactionScope (System.Transactions.dll) permite su uso en un rango de operaciones (los proveedores adecuados se alistarán automáticamente en la transacción ambiente). Esto facilita la adaptación en el código existente (no transaccional) y hablar con múltiples proveedores (aunque DTC se involucrará si habla con más de uno).

Por ejemplo:

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

Tenga en cuenta aquí que los dos métodos pueden manejar sus propias conexiones (abrir / usar / cerrar / desechar), sin embargo, se convertirán silenciosamente en parte de la transacción ambiental sin que tengamos que pasar nada.

Si su código falla, se llamará a Dispose () sin Complete (), por lo que se revertirá. Se admite el anidamiento esperado, etc., aunque no puede revertir una transacción interna y completar la transacción externa: si alguien no está contento, la transacción se cancela.

La otra ventaja de TransactionScope es que no está vinculada solo a las bases de datos; cualquier proveedor de transacciones puede usarlo. WCF, por ejemplo. O incluso hay algunos modelos de objetos compatibles con TransactionScope (es decir, clases .NET con capacidad de reversión, tal vez más fácil que un recuerdo, aunque nunca he usado este enfoque).

En definitiva, un objeto muy, muy útil.

Algunas advertencias:

  • En SQL Server 2000, un TransactionScope irá a DTC inmediatamente; esto se soluciona en SQL Server 2005 y versiones posteriores, puede usar el LTM (mucho menos sobrecarga) hasta que hable con 2 fuentes, etc., cuando se eleva a DTC.
  • Hay un problema técnico que significa que es posible que deba ajustar su cadena de conexión
Marc Gravell
fuente
¡CSLA .NET 2.0 admite el objeto TransactionScope!
Binoj Antony
El problema aquí es cuando tiene una transacción en el primer método y este método (encapsulación) no sabe si se llamará desde una transacción principal o no.
Eduardo Molteni
1
@Eduardo: eso no es un problema al usar TransactionScope, lo que lo hace muy atractivo. Tales transacciones anidan, y solo las confirmaciones más externas.
Marc Gravell
Espero que sigas escuchando. Dijiste que hay "algunos modelos de objetos compatibles con TransactionScope". ¿Me puede señalar a algunos de ellos? Gracias.
majkinetor
1
De nuevo Marc, otra excelente explicación. Cuando dice 'se admite el anidamiento esperado', ¿es eso para los bloques de transacciones definidos dentro de los métodos (CallAMethodThatDoesSomeWork () por ejemplo) ellos mismos? ¿O con el alcance de la transacción definido afuera, no es obligatorio?
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
fuente
4

También puede envolver la transacción en su propio procedimiento almacenado y manejarla de esa manera en lugar de realizar transacciones en C #.

Charles Graham
fuente
1

si solo lo necesita para cosas relacionadas con db, algunos OR Mappers (p. ej., NHibernate) admiten transactinos de forma predeterminada.

Joachim Kerschbaumer
fuente
0

También depende de lo que necesites. Para las transacciones SQL básicas, puede intentar realizar transacciones TSQL utilizando BEGIN TRANS y COMMIT TRANS en su código. Esa es la forma más fácil, pero tiene complejidad y hay que tener cuidado de comprometerse correctamente (y revertir).

Usaría 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
}

Cualquier falla lo sacará directamente de la usingtransacción y la transacción siempre se confirmará o revertirá (dependiendo de lo que le diga que haga). El mayor problema que enfrentamos fue asegurarnos de que siempre se comprometiera. El uso asegura que el alcance de la transacción es limitado.

Brody
fuente