Personas que lo han encontrado útil: 3 de 7 - Valorar este tema

Implementar una transacción implícita con el ámbito de transacción

La clase TransactionScope proporciona un manera sencilla de marcar un bloque de código como participante en una transacción, sin que el usuario tenga que interactuar con la transacción. Un ámbito de transacción puede seleccionar y administrar la transacción de ambiente de forma automática. Gracias a su fácil uso y a su eficacia, se recomienda utilizar la clase TransactionScope cuando se desarrolle una aplicación de transacción.

Además, no es necesario inscribir recursos de forma explícita con la transacción. Cualquier administrador de recursos de System.Transactions (como SQL Server 2005) puede detectar la existencia de una transacción de ambiente creada por el ámbito e inscribirla de forma automática.

Crear un ámbito de transacción

En el siguiente ejemplo se muestra un uso sencillo de la clase TransactionScope.

using (TransactionScope ts = new TransactionScope())
{
    //Create and open the SQL connection.  The work done on this connection will be a part of the transaction created by the TransactionScope
    SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=northwind");
    SqlCommand myCommand = new SqlCommand();
    myConnection.Open();
    myCommand.Connection = myConnection;

    //Restore database to near it's original condition so sample will work correctly.
    myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)";
    myCommand.ExecuteNonQuery();

    //Insert the first record.
    myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
    myCommand.ExecuteNonQuery();

    //Insert the second record.
    myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
    myCommand.ExecuteNonQuery();

    myConnection.Close();

                    //Call complete on the TransactionScope or not based on input
    ConsoleKeyInfo c;
    while (true)
    {
                            Console.Write("Complete the transaction scope? [Y|N] ");
        c = Console.ReadKey();
        Console.WriteLine();

        if ((c.KeyChar == 'Y') || (c.KeyChar == 'y'))
        {
            // Commit the transaction
            ts.Complete();
            break;
        }
        else if ((c.KeyChar == 'N') || (c.KeyChar == 'n'))
        {
            break;
        }
    }

El ámbito de transacción se inicia después de crear un nuevo objeto TransactionScope. Como se muestra en el ejemplo de código, se recomienda crear ámbitos con una instrucción using. La instrucción using está disponible tanto en C# como en Visual Basic y funciona como un bloque try...finally para garantizar que el ámbito se deseche correctamente.

Cuando se crea una instancia de TransactionScope, el administrador de transacciones determina la transacción en la que se debe participar. Una vez determinada, el ámbito siempre participa en esa transacción. La decisión se basa en dos factores: si hay presente una transacción de ambiente y el valor del parámetro TransactionScopeOption en el constructor. La transacción de ambiente es la transacción en la que se ejecuta el código. Puede obtener una referencia a la transacción de ambiente llamando a la propiedad Current estática de la clase Transaction. Para obtener más información sobre cómo se utiliza este parámetro, vea la sección Administrar el flujo de transacciones con TransactionScopeOption en este tema.

Completar un ámbito de transacción

Cuando la aplicación finalice todo el trabajo que desea llevar a cabo en una transacción, se debe llamar al método Complete sólo una vez para informar al administrador de transacciones que se puede confirmar la transacción. Es muy conveniente realizar la llamada a Complete como última instrucción en el bloque using.

Si no se llama a este método, la transacción se anula, ya que el administrador de transacciones lo interpreta como un error del sistema o como una excepción iniciada dentro del ámbito de transacción. De todos modos, llamar a este método no garantiza que la transacción se confirme. Es simplemente una manera de informar del estado al administrador de transacciones. Después de llamar al método Complete, ya no podrá tener acceso a la transacción de ambiente utilizando la propiedad Current y, si lo intenta, se iniciará una excepción.

Si el objeto TransactionScope ha creado la transacción inicialmente, el trabajo de confirmación de la transacción por parte del administrador de transacciones tiene lugar después de la última línea de código del bloque using. Si no ha creado la transacción, la confirmación tiene lugar cuando el propietario del objeto CommittableTransaction llama a Commit. En ese momento, el administrador de transacciones llama a los administradores de recursos y les informa de que deben confirmar o deshacer la transacción, en función de si se ha llamado al método Complete en el objeto TransactionScope.

La instrucción using garantiza que se llame al método Dispose del objeto TransactionScope aunque se produzca una excepción. El método Dispose marca el final del ámbito de transacción. Puede que las excepciones que tienen lugar después de llamar a ese método no afecten a la transacción. Este método también restaura la transacción de ambiente a su estado anterior.

Se inicia una excepción TransactionAbortedException si el ámbito crea la transacción y la transacción se anula. Se inicia una excepción TransactionIndoubtException si el administrador de transacciones no puede tomar una decisión de confirmación. No se inicia ninguna excepción si se confirma la transacción.

Deshacer una transacción

Si desea deshacer una transacción, no debe llamar al método Complete dentro del ámbito de transacción. Por ejemplo, puede iniciar una excepción en el ámbito. La transacción en la que éste participa se deshará.

Administrar el flujo de transacciones con TransactionScopeOption

El ámbito de transacción se puede anidar llamando a un método que utilice un TransactionScope de otro método que utilice su propio ámbito, como es el caso del método RootMethod en el ejemplo siguiente.

void RootMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
          /* Perform transactional work here */
          SomeMethod();
          scope.Complete();
     }
}

void SomeMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
          /* Perform transactional work here */
          scope.Complete();
     }
}

El ámbito de transacción superior se denomina ámbito raíz.

La clase TransactionScope proporciona varios constructores sobrecargados que aceptan una enumeración del tipo TransactionScopeOption, que define el comportamiento transaccional del ámbito.

Un objeto TransactionScope tiene tres opciones:

  • Unirse a la transacción de ambiente o crear una nueva si no existe ninguna.

  • Convertirse en un nuevo ámbito raíz, es decir, iniciar una nueva transacción que sea la nueva transacción de ambiente dentro de su propio ámbito.

  • No participar en una transacción. Por lo tanto, no existirá ninguna transacción de ambiente.

Si se crea una instancia del ámbito con Required y hay presente una transacción de ambiente, el ámbito se une a esa transacción. Si, en cambio, no existe ninguna transacción de ambiente, el ámbito crea una nueva transacción y se convierte en el ámbito raíz. Éste es el valor predeterminado. Cuando se utiliza Required, el código del ámbito no tiene que comportarse de forma distinta en función de si es el ámbito raíz o si simplemente se une a la transacción de ambiente. Debe comportarse de la misma manera en ambos casos.

Si se crea una instancia del ámbito con RequiresNew, éste se convierte siempre en el ámbito raíz. Inicia una nueva transacción y su transacción se convierte en la nueva transacción de ambiente dentro del ámbito.

Si se crea una instancia del ámbito con Suppress, éste nunca participa en una transacción, independientemente de si hay una transacción de ambiente presente. Para un ámbito del que se haya creado una instancia y que tenga este valor, null es siempre su transacción de ambiente.

Las opciones anteriores se resumen en la tabla siguiente:

TransactionScopeOption Transacción de ambiente El ámbito participa en

Requerida (Required)

No

Nueva transacción (será la raíz)

Se requiere nueva (RequiresNew)

No

Nueva transacción (será la raíz)

Suprimir (Suppress)

No

Sin transacción

Requerida (Required)

Transacción de ambiente

Se requiere nueva (RequiresNew)

Nueva transacción (será la raíz)

Suprimir (Suppress)

Sin transacción

Cuando un objeto TransactionScope se une a una transacción de ambiente existente, puede que, al desechar el objeto de ámbito, no finalice la transacción, a menos que el ámbito la anule. Si un ámbito raíz ha creado la transacción de ambiente, sólo se llama a Commit en la transacción cuando se desecha el ámbito raíz. Si la transacción se ha creado manualmente, la transacción finaliza cuando su creador la anula o la confirma.

En el ejemplo siguiente se muestra un objeto TransactionScope que crea tres objetos de ámbito anidados, cada uno con una instancia con un valor de TransactionScopeOption distinto.

using(TransactionScope scope1 = new TransactionScope()) 
//Default is Required 
{ 
     using(TransactionScope scope2 = new 
      TransactionScope(TransactionScopeOption.Required)) 
     {
     ...
     } 

     using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew)) 
     {
     ...
     } 

     using(TransactionScope scope4 = new 
        TransactionScope(TransactionScopeOption.Suppress)) 
    {
     ...
    } 
}

El ejemplo muestra un bloque de código en el que ninguna transacción de ambiente crea un nuevo ámbito (scope1) con Required. El ámbito scope1 es un ámbito raíz ya que crea una nueva transacción (Transacción A) y convierte la Transacción A en la transacción de ambiente. Scope1 crea, a continuación, tres objetos más, cada uno con un valor de TransactionScopeOption distinto. Por ejemplo, scope2 se crea con Required y, puesto que existe una transacción de ambiente, se une a la primera transacción creada por scope1. Tenga en cuenta que scope3 es el ámbito raíz de una nueva transacción y que scope4 no tiene ninguna transacción de ambiente.

Aunque el valor predeterminado y más utilizado de TransactionScopeOption es Required, cada uno de los demás valores tiene una finalidad propia.

Suppress es útil cuando se desean conservar las operaciones realizadas por la sección de código y no se desea anular la transacción de ambiente si las operaciones presentan errores. Por ejemplo, cuando se desean realizar operaciones de registro o de auditoría o cuando se desean publicar eventos para suscriptores, independientemente de si la transacción de ambiente se confirma o se anula. Este valor permite tener una sección de código no transaccional dentro de un ámbito de transacción, como se muestra en el ejemplo siguiente.

using(TransactionScope scope1 = new TransactionScope())
{
     try
     {
          //Start of non-transactional section 
          using(TransactionScope scope2 = new
             TransactionScope(TransactionScopeOption.Suppress))
          {
               //Do non-transactional work here
          }
          //Restores ambient transaction here
   }
     catch
     {}
   //Rest of scope1
}

Votar dentro de un ámbito anidado

Aunque un ámbito anidado puede unirse a una transacción de ambiente del ámbito raíz, llamar a Complete en el ámbito anidado no tiene ningún efecto en el ámbito raíz. La transacción sólo se confirmará si todos los ámbitos del ámbito raíz, hasta el último ámbito anidado, votan por la confirmación de la transacción.

Establecer el tiempo de espera de TransactionScope

Algunos de los constructores sobrecargados de TransactionScope aceptan un valor del tipo TimeSpan, que se utiliza para controlar el tiempo de espera de la transacción. Un tiempo de espera establecido en cero significa un tiempo de espera infinito. El tiempo de espera infinito es útil principalmente para depuraciones, cuando se desea aislar un problema en la lógica empresarial examinando todo el código y no se desea que la transacción que se depure exceda el tiempo de espera mientras se intenta localizar el problema. Tenga mucho cuidado al utilizar el valor de tiempo de espera infinito en el resto de los casos, porque reemplaza los elementos de seguridad para evitar interbloqueos de transacciones.

El tiempo de espera de TransactionScope se suele establecer en valores distintos del valor predeterminado en dos casos. El primero es durante el desarrollo, cuando se desea probar el modo en que la aplicación controla las transacciones anuladas. Si establece el tiempo de espera en un valor bajo (como un milisegundo), hará que la transacción no se realice correctamente y, por lo tanto, podrá observar el código de control del error. El segundo caso en el que se establece este valor en un valor inferior al tiempo de espera predeterminado es cuando se cree que el ámbito está implicado en una contención de recursos, lo que provoca interbloqueos. En ese caso, se debe anular la transacción lo antes posible y no esperar a que transcurra el tiempo de espera predeterminado.

Cuando un ámbito se une a una transacción de ambiente pero especifica un tiempo de espera inferior al establecido para la transacción de ambiente, ese nuevo tiempo de espera se aplica en el objeto TransactionScope y el ámbito debe finalizar en el plazo de tiempo anidado especificado o la transacción se anulará de forma automática. Si el tiempo de espera del ámbito anidado es superior al de la transacción de ambiente, no tiene ningún efecto.

Establecer el nivel de aislamiento de TransactionScope

Algunos de los constructores sobrecargados de TransactionScope aceptan una estructura del tipo TransactionOptions para especificar un nivel de aislamiento, además de un valor de tiempo de espera. De manera predeterminada, la transacción se ejecuta con el nivel de aislamiento establecido en Serializable. Se suele seleccionar un nivel de aislamiento distinto a Serializable para sistemas con mucha actividad de lectura. Esto requiere una comprensión exhaustiva de la teoría del procesamiento de transacciones, la semántica de la propia transacción, los problemas de concurrencia y las consecuencias para la coherencia del sistema.

Además, no todos los administradores de recursos admiten todos los niveles de aislamiento y pueden optar por formar parte de la transacción a un nivel superior del configurado.

Cada nivel de aislamiento, además de Serializable, es susceptible de sufrir incoherencias derivadas del acceso por parte de otras transacciones a la misma información. La diferencia entre los distintos niveles de aislamiento radica en el modo en que se utilizan los bloqueos de lectura y escritura. Un bloqueo sólo se puede mantener cuando la transacción obtiene acceso a los datos del administrador de recursos o hasta que la transacción se confirma o se anula. Lo primero es mejor para el rendimiento y lo segundo, para la coherencia. Los dos tipos de bloqueos y los dos tipos de operaciones (lectura y escritura) ofrecen cuatro niveles de aislamiento básicos. Vea IsolationLevel para obtener más información.

Cuando se utilizan objetos TransactionScope anidados, todos los ámbitos anidados deben configurarse para utilizar exactamente el mismo nivel de aislamiento si desean unirse a la transacción de ambiente. Si un objeto TransactionScope anidado intenta unirse a la transacción de ambiente, pero especifica un nivel de aislamiento distinto, se inicia una excepción ArgumentException.

Interoperabilidad con COM+

Cuando se crea una nueva instancia de TransactionScope, se puede utilizar la enumeración de EnterpriseServicesInteropOption en uno de los constructores para especificar cómo interactuar con COM+. Para obtener más información al respecto, vea Interoperabilidad con Enterprise Services y transacciones COM+.

Vea también

¿Le ha resultado útil?
(Caracteres restantes: 1500)
Contenido de la comunidad Agregar