Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La TransactionScope clase proporciona una manera sencilla de marcar un bloque de código como participante en una transacción, sin necesidad de interactuar 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 eficiencia, se recomienda usar la TransactionScope clase al desarrollar una aplicación de transacción.
Además, no es necesario incluir recursos explícitamente en 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.
Creación de un ámbito de transacción
En el ejemplo siguiente se muestra un uso sencillo de la TransactionScope clase .
// 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.
static public int CreateTransactionScope(
string connectString1, string connectString2,
string commandText1, string commandText2)
{
// Initialize the return value to zero and create a StringWriter to display results.
int returnValue = 0;
System.IO.StringWriter writer = 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 (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = 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.
SqlCommand command1 = 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 (SqlConnection connection2 = 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;
SqlCommand command2 = new SqlCommand(commandText2, connection2);
returnValue = command2.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
}
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
}
// Display messages.
Console.WriteLine(writer.ToString());
return returnValue;
}
' 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)
End Try
' Display messages.
Console.WriteLine(writer.ToString())
Return returnValue
End Function
El ámbito de transacción se inicia una vez que se crea un nuevo TransactionScope objeto. Como se muestra en el ejemplo de código, se recomienda crear alcances con una instrucción de 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 de TransactionScope, el administrador de transacciones determina en qué transacción participar. Una vez determinado, el ámbito siempre participa en esa transacción. La decisión se basa en dos factores: si existe una transacción ambiental y el valor del TransactionScopeOption
parámetro en el constructor. La transacción ambiental es la transacción dentro de la que se ejecuta el código. Puede obtener una referencia a la transacción ambiente llamando a la propiedad estática Transaction.Current de la clase Transaction. Para obtener más información sobre cómo se usa este parámetro, consulte la sección Administración del flujo de transacciones mediante TransactionScopeOption de este tema.
Completar un ámbito de la transacción
Cuando la aplicación complete todo el trabajo que desea realizar en una transacción, debe llamar al TransactionScope.Complete método solo una vez para informar al administrador de transacciones de que es aceptable confirmar la transacción. Es muy recomendable colocar la llamada Complete como la última instrucción del bloque using
.
No llamar a este método aborta la transacción, ya que el administrador de transacciones interpreta esto como un fallo del sistema o como equivalente a lanzar una excepción dentro del ámbito de la transacción. Sin embargo, llamar a este método no garantiza que se confirme la transacción. Es simplemente una manera de informar al administrador de transacciones de su estado. Después de llamar al Completemétodo, ya no se puede acceder a la transacción ambiente mediante la Currentpropiedad, y al intentar hacerlo, se producirá una excepción.
Si el TransactionScope objeto creó 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 del using
bloque. Si no creó la transacción, la confirmación se produce cuando el propietario del objeto Commit llama a CommittableTransaction. En ese momento, el administrador de transacciones llama a los administradores de recursos y les informa sobre si deben confirmar o revertir, dependiendo de si el método Complete fue llamado en el objeto TransactionScope.
La using
instrucción garantiza que se llame al Dispose método del TransactionScope objeto incluso si se produce una excepción. El Dispose método marca el final del ámbito de transacción. Las excepciones que se producen después de llamar a este método pueden no afectar a la transacción. Este método también restaura la transacción ambiental en su estado anterior.
Se inicia TransactionAbortedException si el ámbito crea la transacción, y ésta se anula. Si el administrador de transacciones no puede llegar a una decisión de confirmación, se genera un TransactionInDoubtException. No se produce ninguna excepción si se confirma la transacción.
Revertir una transacción
Si desea revertir una transacción, no debe llamar al método Complete dentro del ámbito de la transacción. Por ejemplo, puede producir una excepción dentro del ámbito. La transacción en la que participa se revertirá.
Administración del flujo de transacciones mediante 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 transacción más alto se conoce como ámbito raíz.
La TransactionScope clase proporciona varios constructores sobrecargados que aceptan una enumeración del tipo TransactionScopeOption, que define el comportamiento transaccional del ámbito.
Un TransactionScope objeto 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 participar en una transacción en absoluto. 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. Por otro lado, si no hay ninguna transacción ambiental, el ámbito crea una nueva transacción y se convierte en el ámbito raíz. Este 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. Debe funcionar de forma idéntica en ambos casos.
Cuando el ámbito se instancia con RequiresNew, siempre es el ámbito raíz. Inicia una nueva transacción y su transacción se vuelve la nueva transacción ambiente dentro del ámbito.
Si se instancia el ámbito con Suppress, nunca participa en una transacción, independientemente de si hay una transacción ambiente. 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. |
---|---|---|
Obligatorio | No | Nueva transacción (será la raíz) |
Requiere nuevo | No | Nueva transacción (será la raíz) |
Reprimir | No | Sin transacción |
Obligatorio | Sí | Transacción ambiente |
Requiere nuevo | Sí | Nueva transacción (será la raíz) |
Reprimir | 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, solo 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.
En el ejemplo siguiente se muestra un objeto TransactionScope que crea tres objetos de ámbito anidados, cada uno instanciado 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))
{
//...
}
}
En el ejemplo se muestra un bloque de código sin ninguna transacción ambiental que cree 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 esta en la transacción ambiente.
Scope1
a continuación, crea tres objetos más, cada uno con un valor diferente TransactionScopeOption . Por ejemplo, scope2
se crea con Requiredy, dado que hay una transacción ambiental, 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 ambiente.
Aunque el valor predeterminado y más usado de TransactionScopeOption es Required, cada uno de los otros valores tiene su propósito único.
Código no transaccional dentro de un ámbito de transacción
Suppress es útil cuando desea 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, cuando desee realizar operaciones de registro o auditoría, o cuando quiera publicar eventos a los suscriptores, independientemente de si la transacción contextual 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 la transacción ambiental del ámbito raíz, llamar Complete al ámbito anidado no tiene ningún efecto en el ámbito raíz. La transacción solo se llevará a cabo si todos los ámbitos, desde el ámbito raíz hasta el último ámbito anidado, votan para proceder con la transacción. Si no se llama a Complete en un ámbito anidado, el ámbito de la raíz resultará afectado, ya que la transacción ambiente se anulará inmediatamente.
Establecimiento del tiempo de espera de TransactionScope
Algunos de los constructores sobrecargados de TransactionScope aceptan un valor de tipo TimeSpan, que se usa 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 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. Tenga mucho cuidado con el valor de tiempo de espera infinito en todos los demás casos, ya que invalida las medidas de seguridad contra los interbloqueos de transacción.
Normalmente, se establece el TransactionScope tiempo de espera en valores distintos de los predeterminados en dos casos. La primera es durante el desarrollo, cuando desea probar la forma en que la aplicación controla las transacciones anuladas. Al establecer el tiempo de espera en un valor pequeño (por ejemplo, un milisegundo), se produce un error en la transacción y, por tanto, se puede observar el 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 la transacción lo antes posible y no esperar a que expire el tiempo de espera predeterminado.
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.
Establecimiento del nivel de aislamiento 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 el nivel de aislamiento establecido en Serializable. Seleccionar un nivel de aislamiento diferente de Serializable es algo habitual en sistemas con gran carga de lecturas. Esto requiere un conocimiento sólido de la teoría del procesamiento de transacciones y la semántica de la propia transacción, los problemas de simultaneidad 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 optar por participar en la transacción en un nivel superior al configurado.
Cada nivel de aislamiento, excepto Serializable, es susceptible a la inconsistencia resultante de otras transacciones que acceden a la misma información. La diferencia entre los distintos niveles de aislamiento es la forma en que se usan los bloqueos de lectura y escritura. Un bloqueo solo se puede mantener cuando la transacción accede a los datos del administrador de recursos o se puede mantener hasta que la transacción se confirme o anule. El primero es mejor para el rendimiento, el segundo para la coherencia. Los dos tipos de bloqueos y los dos tipos de operaciones (lectura y escritura) proporcionan cuatro niveles de aislamiento básicos. Consulte IsolationLevel para obtener más información.
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 una nueva TransactionScope instancia, puede usar la EnterpriseServicesInteropOption enumeración en uno de los constructores para especificar cómo interactuar con COM+. Para obtener más información sobre esto, consulte Interoperabilidad con Enterprise Services y Transacciones COM+.