¿Qué pasa si en alguna en un procesos de actualización de datos de una base de datos falla a la mitad? Lo normal en la mayoría de los casos es deshacer todo lo que se ha hecho y dejar la base de datos como estaba. Este proceso sería muy complicado hacerlo a mano, por no decir imposible. Para esto están las transacciones, que de una manera muy fácil nos ayudan a resolver este tipo de problemas.
Esta forma de trabajar con las bases de datos, podemos hacerlo desde nuestro código o directamente en la base de datos (en procedimientos de almacenado). En este artículo sólo explicaré como hacerlo desde nuestro código.
El código para utilizar una transacción es el siguiente
public void ExecuteTransaction()
{
// Usamos la clase SqlConnectionStringBuilder para construir la cadena de conexión
SqlConnectionStringBuilder _connectionStringBuilder = new SqlConnectionStringBuilder();
_connectionStringBuilder.DataSource = @".\SQLEXPRESS";
_connectionStringBuilder.InitialCatalog = "prueba";
_connectionStringBuilder.IntegratedSecurity = true;
// Creamos la conexión
using(SqlConnection _connection = new SqlConnection(_connectionStringBuilder.ConnectionString))
{
// Abrimos la conexión
_connection.Open();
// Abrimos una transación en la conexión abierta
SqlTransaction _transaction = _connection.BeginTransaction();
// Creamos el objeto SqlCommand y asignamos los datos
// a los parámetros
SqlCommand _command = new SqlCommand();
_command.Connection = _connection;
_command.CommandType = CommandType.StoredProcedure;
_command.CommandText = "ProcedimientoAlmacenado"; // Aqui va el nombre del procedimiento de almacenado
// Añadimos los parametros
_command.Parameters.AddWithValue("@Par1", "Par1");
_command.Parameters.AddWithValue("@Par2", "Par2");
// Asignamos la transacción al comando
_command.Transaction = _transaction;
try
{
// Ejecutamos el comando
_command.ExecuteNonQuery();
// Si todo ha ido bien hacemos un "Commit" de la transacción
_transaction.Commit();
}
catch(Exception ex)
{
// Si se produce un error, desahacemos la transaccion (Rollback)
_transaction.Rollback();
}
// No se hace falta cerrar la conexión porque está dentro de un "using"
_connection.Close();
}
}
Lo primero que hacemos es crear una cadena de conexión y abrir una conexión con ésta. Con esta conexión abierta creamos la transacción (con el método “BeginTransaction”) y ejecutamos nuestro procedimiento de almacenado. Si todo ha ido bien, hacemos un “Commit” de la transacción y si algo ha fallado hacemos un “Rollback”. Es importante hacer un ”Commit” ya que si no, el sistema hará un “Rollback” automáticamente al cerrar la conexión.
Tal y como está el código sólo estamos haciendo una llamada la base de datos, pero modificando el código ligeramente podemos ver su verdadero potencial. Supongamos que tenemos la siguiente clase
public class Article
{
public int Id = 0;
public string Content = string.Empty;
}
Y tenemos un método que recibe una lista de artículos que tenemos que actualizar. Este método creará una transacción y la utilizará para actualizar cada artículo. El código sería como este
public void UpdateArticles(List<Article> articles)
{
SqlTransaction _transaction = CreateTransaction();
try
{
foreach (Article article in articles)
{
UpdateArticle(_transaction, article);
}
_transaction.Commit();
}
catch
{
_transaction.Rollback();
throw;
}
}
public void UpdateArticle(SqlTransaction transaction, Article article)
{
try
{
// Creamos el objeto SqlCommand y asignamos los datos
// a los parámetros
SqlCommand _command = new SqlCommand();
_command.Connection = transaction.Connection;
_command.CommandType = CommandType.StoredProcedure;
_command.CommandText = "UpdataArticle"; // Aqui va el nombre del procedimiento de almacenado
// Añadimos los parametros
_command.Parameters.AddWithValue("@Id", article.Id);
_command.Parameters.AddWithValue("@Content", article.Content);
// Asignamos la transacción al comando
_command.Transaction = transaction;
// Ejecutamos el comando
_command.ExecuteNonQuery();
}
catch
{
throw;
}
}
public SqlTransaction CreateTransaction()
{
try
{
// Usamos la clase SqlConnectionStringBuilder para construir la cadena de conexión
SqlConnectionStringBuilder _connectionStringBuilder = new SqlConnectionStringBuilder();
_connectionStringBuilder.DataSource = @".\SQLEXPRESS";
_connectionStringBuilder.InitialCatalog = "prueba";
_connectionStringBuilder.IntegratedSecurity = true;
// Creamos la conexión
SqlConnection _connection = new SqlConnection(_connectionStringBuilder.ConnectionString);
// Abrimos la conexión
_connection.Open();
// Abrimos una transación en la conexión abierta
SqlTransaction _transaction = _connection.BeginTransaction();
return _transaction;
}
catch
{
throw;
}
}
Cómo hemos visto el uso de transacciones desde .NET es muy fácil y hace nuestras aplicaciones más robustas. Pero, ¿qué pasa cuando entran en juego varias bases de datos, o incluso varios gestores de base de datos (varios Sql Server)? Para eso tenemos el TransactionScope, que podemos ver en http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx, pero esto lo dejo para otro momento.