Implementar una transacción implícita mediante el ámbito de la transacción
La clase TransactionScope proporciona una manera simple de marcar un bloque de código como participar en una transacción, sin exigirle que interactuara con la propia transacción. Un ámbito de la transacción puede seleccionar y administrar automáticamente la transacción ambiente. Debido a su facilidad de uso y eficacia, se recomienda que utilice la clase TransactionScope al desarrollar una aplicación de transacción.
Además, no necesita dar de alta explícitamente los recursos con la transacción. Cualquier administrador de recursos System.Transactions (como SQL Server 2005) puede detectar la existencia de una transacción ambiente creada por el ámbito y automáticamente darse de alta.
Crear un ámbito de la transacción
En el ejemplo siguiente se muestra un uso sencillo de la clase TransactionScope.
' This function takes arguments for 2 connection strings and commands to create a transaction
' involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
' transaction is rolled back. To test this code, you can connect to two different databases
' on the same server by altering the connection string, or to another 3rd party RDBMS
' by altering the code in the connection2 code block.
Public Function CreateTransactionScope( _
ByVal connectString1 As String, ByVal connectString2 As String, _
ByVal commandText1 As String, ByVal commandText2 As String) As Integer
' Initialize the return value to zero and create a StringWriter to display results.
Dim returnValue As Integer = 0
Dim writer As System.IO.StringWriter = New System.IO.StringWriter
Try
' Create the TransactionScope to execute the commands, guaranteeing
' that both commands can commit or roll back as a single unit of work.
Using scope As New TransactionScope()
Using connection1 As New SqlConnection(connectString1)
' Opening the connection automatically enlists it in the
' TransactionScope as a lightweight transaction.
connection1.Open()
' Create the SqlCommand object and execute the first command.
Dim command1 As SqlCommand = New SqlCommand(commandText1, connection1)
returnValue = command1.ExecuteNonQuery()
writer.WriteLine("Rows to be affected by command1: {0}", returnValue)
' If you get here, this means that command1 succeeded. By nesting
' the using block for connection2 inside that of connection1, you
' conserve server and network resources as connection2 is opened
' only when there is a chance that the transaction can commit.
Using connection2 As New SqlConnection(connectString2)
' The transaction is escalated to a full distributed
' transaction when connection2 is opened.
connection2.Open()
' Execute the second command in the second database.
returnValue = 0
Dim command2 As SqlCommand = New SqlCommand(commandText2, connection2)
returnValue = command2.ExecuteNonQuery()
writer.WriteLine("Rows to be affected by command2: {0}", returnValue)
End Using
End Using
' The Complete method commits the transaction. If an exception has been thrown,
' Complete is called and the transaction is rolled back.
scope.Complete()
End Using
Catch ex As TransactionAbortedException
writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message)
Catch ex As ApplicationException
writer.WriteLine("ApplicationException Message: {0}", ex.Message)
End Try
' Display messages.
Console.WriteLine(writer.ToString())
Return returnValue
End Function
El ambiente de transacción ha comenzado una vez que se ha creado un nuevo objeto TransactionScope . Tal y como se ilustra en la muestra del código se recomienda la creación de ambientes 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 asegurarse de que el ámbito se elimina correctamente.
Al crear una instancia TransactionScope, el administrador de transacciones determina en qué transacción va a participar. Una vez determinada, el ámbito siempre participa en esa transacción. La decisión se basa en dos factores: si está presente una transacción de ambiente y el valor del parámetro TransactionScopeOption del constructor. La transacción ambiente es la transacción dentro de la que su código se ejecuta. Puede obtener una referencia a la transacción ambiente llamando a la propiedad estática Current de la clase Transaction. Para obtener más información sobre cómo se utiliza esta enumeración, vea el apartado "Administración de flujo de transacciones con TransactionScopeOption" del tema.
Completar un ámbito de la transacción
Cuando la aplicación termina todo el trabajo que tiene que llevar a cabo en una transacción, debe llamar al método Complete sólo una vez para notificar al administrador de transacciones que la transacción se puede confirmar. Es muy recomendable colocar la llamada Complete como la última instrucción del bloque using.
Si no se puede llamar a este método se anula la transacción, dado que el administrador de transacciones interpreta esto como un error del sistema o es equivalente a cuando se producen excepciones dentro del ámbito de la transacción. Sin embargo, llamar a este método no garantiza que se vaya a confirmar la transacción. Es sólo una manera de informar al administrador de transacciones de su estado. Después de llamar a este métodoComplete ya no podrá obtener acceso a la transacción de ambiente mediante la propiedad Current y, si intenta hacerlo, se producirá una excepción.
Si el objeto TransactionScope creara inicialmente la transacción, el trabajo real de confirmar la transacción por el administrador de transacciones se produce después de la última línea de código en el bloque using. Si no ha creado la transacción, se produce la confirmación cada vez que el propietario del objeto CommittableTransaction llama al método Commit. En ese punto el administrador de transacciones llama a los administradores de recursos y les informa de si se va a confirmar o deshacer la transacción, basándose en si se ha llamado al método Complete en el objeto TransactionScope .
La instrucción using asegura que el método Dispose de TransactionScope se llama aún cuando se produzca una excepción. El método Dispose marca el fin del ámbito de la transacción. Las excepciones que se producen después de llamar a este método quizá no afecten a la transacción. Este método también restaura la transacción de ambiente a su estado previo.
Se inicia TransactionAbortedException si el ámbito crea la transacción, y ésta se anula. Se inicia TransactionIndoubtException si el administrador de transacciones no puede llegar a una decisión de la confirmación. No se produce ninguna excepción si se confirma la transacción.
Deshacer una transacción
Si desea revertir una transacción, no debería llamar al método Complete dentro del ámbito de la transacción. Por ejemplo, puede producir una excepción dentro del ámbito. Se deshará la transacción en la que participa.
Flujo de la transacción de administración utilizando TransactionScopeOption
El ámbito de la transacción puede estar anidado al llamar a un método que utiliza desde dentro TransactionScope un método que utiliza su propio ámbito, como es el caso con el 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 la transacción de nivel superior se conoce como el ámbito de la 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:
Unir la transacción ambiente o crear una nueva si no existe.
Ser un nuevo ámbito de la raíz, eso es, iniciar una nueva transacción y tener esa transacción siendo la nueva transacción ambiente dentro de su propio ámbito.
No tomar parte en absoluto en una transacción. Como resultado, no hay ninguna transacción ambiente.
Si se crean instancias del ámbito con Required, y una transacción ambiente está presente, el ámbito combina dicha transacción. Si, por otro lado, no hay ninguna transacción ambiente, a continuación, el ámbito crea una nueva transacción y se vuelve el ámbito de la raíz. Éste es el valor predeterminado. Cuando se utiliza Required, el código dentro del ámbito no necesita comportarse de manera diferente si es la raíz o simplemente uniendo la transacción ambiente. Debería funcionar idénticamente en ambos casos.
Si se crean instancias del ámbito con RequiresNew, siempre es el ámbito de la raíz. Inicia una nueva transacción y su transacción se vuelve la nueva transacción ambiente dentro del ámbito.
Si se crea del ámbito instancias con Suppress, nunca toma la parte en una transacción, sin tener en cuenta si una transacción ambiente está presente. Un ámbito con instancias creadas siempre tiene null con este valor como su transacción ambiente.
Las opciones anteriores se resumen en la tabla siguiente.
TransactionScopeOption | Transacción ambiente | El ámbito toma parte. |
---|---|---|
Necesario |
No |
Nueva transacción (será la raíz) |
Se requiere nueva |
No |
Nueva transacción (será la raíz) |
Suprimir |
No |
Sin transacción |
Necesario |
Sí |
Transacción ambiente |
Se requiere nueva |
Sí |
Nueva transacción (será la raíz) |
Suprimir |
Sí |
Sin transacción |
Cuando un objeto TransactionScope une una transacción ambiente existente, al eliminar el objeto de ámbito, no se puede finalizar la transacción, a menos que el ámbito anule la transacción. Si un ámbito de la raíz creara la transacción ambiente, sólo cuando el ámbito de la raíz se elimina, se llama Commit en la transacción. Si se crea la transacción manualmente, la transacción finaliza cuando se anula o cuando su creador la confirma.
El ejemplo siguiente muestra un objeto TransactionScope que crea tres objetos de ámbito anidados, cada uno con instancias con un valor TransactionScopeOption diferente.
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 sin cualquier transacción ambiente que crea un nuevo ámbito (scope1
) con Required. El ámbito scope1
es un ámbito de la raíz cuando crea una nueva transacción (Transacción A) y realiza Transacción A la transacción ambiente. Scope1
crea a continuación tres objetos más, cada uno con un valor TransactionScopeOption diferente. Por ejemplo, scope2
se crea con Requiredy hay subsecuentemente una transacción ambiente, que se une la primera transacción creada por scope1
. Observe que scope3
es el ámbito de la raíz de una nueva transacción, y scope4
no tiene ninguna transacción ambiente.
Aunque el valor predeterminado y más comúnmente utilizado de TransactionScopeOption es Required, cada uno de los otros valores tiene su propósito único.
Suppress es útil al desear conservar las operaciones realizadas por la sección de código, y no desea anular la transacción ambiente si se produce un error en las operaciones. Por ejemplo, al desear realizar un registro u operaciones de la auditoría, o al desear publicar los eventos a los suscriptores sin tener en cuenta si su transacción ambiente se confirma o sufre interrupciones. Este valor le permite tener una sección de código no transaccional dentro de un ámbito de la 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 unir la transacción ambiente del ámbito de la raíz, llamar Complete en el ámbito anidado no tiene ningún efecto en el ámbito de la raíz. Sólo si todos los ámbitos del ámbito de la raíz, hasta el último del ámbito anidado, votan para confirmar la transacción, la transacción se confirmará.
Establecer el tiempo de espera de TransactionScope
Algunos de los constructores sobrecargados de TransactionScope aceptan un valor de tipo TimeSpan, que se utiliza para controlar el tiempo de espera de la transacción. Un conjunto de tiempos de espera establecidos a cero significa un tiempo de espera infinito. El tiempo de espera infinito es principalmente útil para depurar, al desear aislar un problema en su lógica comercial caminando a través de su código, y no desear la transacción que depura el tiempo de espera mientras intenta buscar el problema. Sea sumamente cuidadoso utilizando el valor de tiempo de espera infinito en todos los otros casos, porque invalida las medidas de seguridad contra los interbloqueos de la transacción.
Se establece normalmente el tiempo de espera TransactionScope en los valores distintos del valor predeterminado en dos casos. El primero durante el desarrollo, al desear probar la manera en que su aplicación administra las transacciones anuladas. Estableciendo el tiempo de espera en un valor pequeño (como un milisegundo), hace que su transacción produzca un error y observa así su código de control de errores. El segundo caso en el que establece el valor para ser menor que el tiempo de espera predeterminado es al creer que el ámbito está implicado en la contención del recurso, produciendo los interbloqueos. En ese caso, desea anular lo antes posible la transacción y no esperar para que el tiempo de espera predeterminado expire.
Cuando un ámbito combina una transacción ambiente pero especifica un tiempo de espera menor que el de la transacción ambiente, el nuevo tiempo de espera más corto se exige en el objeto TransactionScope y el ámbito debe finalizar dentro de la hora anidada especificada o se anula la transacción automáticamente. Si el tiempo de espera del ámbito anidado es más que eso de la transacción ambiente, no tiene ningún efecto.
Establecer el nivel de aislamiento de TransactionScope
Algunos de los constructores sobrecargados de TransactionScope aceptan una estructura de tipo TransactionOptions para especificar un nivel de aislamiento, además de un valor de tiempo de espera. De forma predeterminada, la transacción se ejecuta con nivel de aislamiento establecido en Serializable. La selección de un nivel de aislamiento distinto de Serializable se utiliza normalmente para los sistemas de lectura-intensivos. Esto requiere un sólido entendimiento de teoría del procesamiento de transacciones y las semántica de la propia transacción, los problemas de concurrencia implicados y las consecuencias para la coherencia del sistema.
Además, no todos los administradores de recursos admiten todos los niveles de aislamiento y pueden elegir tomar parte en la transacción en un nivel más alto que el que se configuró.
Cada nivel de aislamiento además de Serializable es susceptible a la inconsistencia que es el resultado de otras transacciones que tienen acceso a la misma información. La diferencia entre los niveles de aislamiento diferentes es la manera en que se utilizan los bloqueos de la lectura y escritura. Se puede contener un bloqueo sólo cuando la transacción tiene acceso a los datos en el administrador de recursos, o se puede contener hasta que la transacción se confirme o anule. Lo primero es mejor para el rendimiento, lo último para la coherencia. Los dos tipos de bloqueos y los dos tipos de operaciones (lectura/escritura) proporcionan cuatro niveles de aislamiento básicos. Para obtener más información, consulte IsolationLevel.
Al utilizar los objetos TransactionScope anidados, todos los ámbitos anidados se deben configurar para utilizar exactamente el mismo nivel de aislamiento si desean unir la transacción ambiente. Si un objeto TransactionScope anidado intenta unir la transacción ambiente todavía especifica un nivel de aislamiento diferente, se inicia ArgumentException.
Interoperabilidad con COM+
Al crear que una nueva instancia TransactionScope, se puede utilizar la enumeración EnterpriseServicesInteropOption en uno de los constructores para especificar cómo interactuar con COM+. Para obtener más información sobre este tema vea Interoperabilidad con Enterprise Services y transacciones de COM+.
Consulte también
Referencia
Copyright © 2007 Microsoft Corporation. Reservados todos los derechos.