Integración de System.Transactions con SQL Server
Se aplica a: .NET Framework .NET .NET Standard
En .NET se incluye un marco de transacciones al que se puede acceder a través del espacio de nombres System.Transactions. Este marco expone las transacciones de forma que se integran totalmente en .NET, incluido ADO.NET.
Además de las mejoras de programación, System.Transactions y ADO.NET pueden funcionar conjuntamente para coordinar las optimizaciones al trabajar con transacciones. Una transacción promovible es una transacción ligera (local) que, en caso necesario, se puede promover automáticamente a una transacción completamente distribuida.
El proveedor de datos SqlClient de Microsoft para SQL Server admite transacciones aptas para promoción cuando se trabaja con SQL Server. Las transacciones promovibles no invocan la sobrecarga adicional de las transacciones distribuidas a menos que sea necesario. Las transacciones aptas para promoción son automáticas y no necesitan la intervención del desarrollador.
Creación de transacciones aptas para promoción
El proveedor de datos SqlClient de Microsoft para SQL Server ofrece compatibilidad con transacciones aptas para promoción, que se controlan a través de las clases del espacio de nombres System.Transactions. Las transacciones promocionadas optimizan las transacciones distribuidas ya que aplazan la creación de las mismas hasta que es necesario. Si solo se necesita un administrador de recursos, no tiene lugar ninguna transacción distribuida.
Nota
En un caso que no es de plena confianza, se requiere DistributedTransactionPermission cuando la transacción se promueve al nivel de transacción distribuida.
Escenarios de transacciones aptas para promoción
Normalmente, las transacciones distribuidas consumen muchos recursos del sistema, siendo el encargado de administrarlas Microsoft DTC (Coordinador de transacciones distribuidas), que integra todos los administradores de recursos a los que se tiene acceso en la transacción. Una transacción apta para promoción es una forma especial de una transacción System.Transactions que delega eficazmente el trabajo en una transacción simple de SQL Server. System.Transactions, Microsoft.Data.SqlClient y SQL Server coordinan el trabajo relacionado con la administración de la transacción, y la promueven a una transacción distribuida completa, según sea necesario.
La ventaja de utilizar transacciones promocionadas es que cuando se abre una conexión utilizando una transacción TransactionScope activa, y no hay ninguna otra conexión abierta, la transacción se confirma como una transacción ligera, en lugar de incurrir en la sobrecarga adicional de una transacción completamente distribuida.
Palabras clave de cadena de conexión
La propiedad ConnectionString admite una palabra clave, Enlist
, que indica si Microsoft.Data.SqlClient detectará contextos transaccionales e inscribirá automáticamente la conexión en una transacción distribuida. Si Enlist=true
, la conexión se inscribe automáticamente en el contexto de transacción actual del subproceso de apertura. Si Enlist=false
, la conexión SqlClient
no interactúa con una transacción distribuida. El valor predeterminado de Enlist
es true. Si no se especifica Enlist
en la cadena de conexión, la conexión se da de alta automáticamente en una transacción distribuida si se detecta una al abrirse la conexión.
Las palabras clave Transaction Binding
en una cadena de conexión SqlConnection controlan la asociación de la conexión con una transacción System.Transactions
dada de alta. También está disponible mediante la propiedad TransactionBinding de SqlConnectionStringBuilder.
La siguiente tabla describe los posibles valores.
Palabra clave | Descripción |
---|---|
Desenlace implícito | El valor predeterminado. La conexión se separa de la transacción cuando termina, y vuelve a cambiar al modo de confirmación automática. |
Desenlace explícito | La conexión sigue adjuntada a la transacción hasta que ésta se cierra. La conexión producirá errores si no está activa o no coincide con Current. |
Uso de TransactionScope
La clase TransactionScope crea un bloque de código transaccional dando de alta implícitamente las conexiones en una transacción distribuida. Debe llamar al método Complete al final del bloque TransactionScope antes de abandonarlo. Al salir del bloque se invoca el método Dispose . Si se ha producido una excepción que ocasiona que el código salga del ámbito, la transacción se considera anulada.
Se recomienda el uso de un bloque using
para asegurarse de que se llama a Dispose en el objeto TransactionScope cuando se sale de dicho bloque. Si no se confirman ni revierten las transacciones pendientes, el rendimiento puede verse seriamente afectado ya que el tiempo de espera predeterminado de TransactionScope es un minuto. Si no utiliza una instrucción using
, debe realizar todo el trabajo de un bloque Try
y llamar explícitamente al método Dispose del bloque Finally
.
Si se produce una excepción en TransactionScope, la transacción se marca como incoherente y se abandona. Se revertirá cuando se elimine el TransactionScope . Si no se produce ninguna excepción, las transacciones participantes se confirman.
Nota
La clase TransactionScope
crea una transacción con IsolationLevel de Serializable
de forma predeterminada. Dependiendo de la aplicación, puede considerar la opción de reducir el nivel de aislamiento para evitar conflictos en ella.
Nota
Se recomienda que solo realice actualizaciones, inserciones y eliminaciones en transacciones distribuidas, ya que consumen una cantidad considerable de recursos de base de datos. Las instrucciones SELECT pueden bloquear los recursos de base de datos de forma innecesaria y, en algunas situaciones, es posible que tengan que utilizarse transacciones para las selecciones. Todo el trabajo que no sea de base de datos debe realizarse fuera del ámbito de la transacción, a menos que estén implicados otros administradores de recursos de transacción. Aunque una excepción en el ámbito de la transacción impide que se confirme la misma, la clase TransactionScope no deja revertir los cambios que haya realizado el código fuera del ámbito de la propia transacción. Si es necesario realizar alguna acción cuando se revierta la transacción, deberá escribir su propia implementación de la interfaz IEnlistmentNotification y darla de alta explícitamente en la transacción.
Ejemplo
Trabajar con System.Transactions requiere disponer de una referencia a System.Transactions.dll.
La siguiente función muestra cómo crear una transacción promocionada en dos instancias de SQL Server diferentes, representadas por dos objetos SqlConnection diferentes, que se incluyen en un bloque TransactionScope .
El código crea el bloque TransactionScope con una instrucción using
y abre la primera conexión, que automáticamente lo inscribe en TransactionScope.
La transacción se da de alta inicialmente como una transacción ligera, no como una transacción distribuida completa. La segunda conexión se inscribe en TransactionScope únicamente si el comando de la primera conexión no produce una excepción. Cuando se abre la segunda conexión, la transacción se promociona automáticamente a una transacción completamente distribuida.
Más adelante, se invoca el método Complete, que confirma la transacción solo si no se ha iniciado ninguna excepción. Si en algún punto del bloque TransactionScope se ha producido una excepción, no se llamará a Complete
y, cuando se elimine TransactionScope al final de su bloque using
, se revertirá la transacción distribuida.
using System;
using System.Transactions;
using Microsoft.Data.SqlClient;
class Program
{
static void Main(string[] args)
{
string connectionString = "Data Source = localhost; Integrated Security = true; Initial Catalog = AdventureWorks";
string commandText1 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong size')";
string commandText2 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong color')";
int result = CreateTransactionScope(connectionString, connectionString, commandText1, commandText2);
Console.WriteLine("result = " + result);
}
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();
// Create the TransactionScope in which to execute the commands, guaranteeing
// that both commands will commit or roll back as a single unit of work.
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = new SqlConnection(connectString1))
{
try
{
// 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 by opening connection2
// only when there is a chance that the transaction can commit.
using (SqlConnection connection2 = new SqlConnection(connectString2))
try
{
// The transaction is promoted 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);
}
catch (Exception ex)
{
// Display information that command2 failed.
writer.WriteLine("returnValue for command2: {0}", returnValue);
writer.WriteLine("Exception Message2: {0}", ex.Message);
}
}
catch (Exception ex)
{
// Display information that command1 failed.
writer.WriteLine("returnValue for command1: {0}", returnValue);
writer.WriteLine("Exception Message1: {0}", ex.Message);
}
}
// If an exception has been thrown, Complete will not
// be called and the transaction is rolled back.
scope.Complete();
}
// The returnValue is greater than 0 if the transaction committed.
if (returnValue > 0)
{
writer.WriteLine("Transaction was committed.");
}
else
{
// You could write additional business logic here, notify the caller by
// throwing a TransactionAbortedException, or log the failure.
writer.WriteLine("Transaction rolled back.");
}
// Display messages.
Console.WriteLine(writer.ToString());
return returnValue;
}
}