Capitulo 2: Clases conectadas de ADO.NET (Parte 3)

Lección 3: Trabajando con transacciones

¿Qué es una transacción?
Una transacción es una unidad atómica de trabajo que debe ser completado en su totalidad. La transacción tiene éxito si se ha comiteado y si no se cancela. Las transacciones tienen cuatro características esenciales: atomicidad, coherencia,aislamiento y durabilidad (conocidos como los atributos de ACID).
Atomicidad: El trabajo no se puede dividir en partes más pequeñas. Aunque una transacción puede contener muchas sentencias SQL, se debe ejecutar como un todo o nada, si una transacción está medio completa cuando se produce un error, el trabajo vuelve a su estado anterior al inicio de la transacción.
Consistencia: Una transacción debe operar en una visión consistente de los datos y debe dejar los datos en un estado coherente. Los trabajos en curso no deben ser visibles a otras transacciones hasta que la transacción se ha comiteado.
Aislamiento: Una transacción debe estar funcionando por sí misma, los efectos de otras operaciones en curso debe ser invisible a esta transacción, y los efectos de esta transacción debe ser invisible para otras operaciones en curso.
Durabilidad: Cuando se comitea una transacción, los cambios no se perderán. Sólo las transacciones comiteadas se recuperaràn durante el arranque y recuperación de errores, el trabajo no comiteado se revierte.

Modelos de concurrencia y de bloqueo de base de datos
Los atributos de la consistencia y el aislamiento se implementan mediante el mecanismo de bloqueo de la base de datos , que evita que una transacción afecte a otra. Si una transacción necesita acceder a los datos con los que está trabajando otra transacción,los datos se bloquean hasta que la primera transacción se comitea o se revierte. Las transacciones que deben acceder a los datos bloqueados se ven obligados a esperar hasta que se libere el bloqueo, lo que significa que las transacciones de larga duración puede afectar al rendimiento y la escalabilidad.

El uso de bloqueos para impedir el acceso a los datos que se conoce como un modelo de "pesimista" de concurrencia. En un modelo de "optimista" de la concurrencia, los bloqueos no se usan cuando se leen los datos. En cambio, cuando se realizan actualizaciones, los datos se comprueban para ver si los datos han cambiado desde que se leyó. Si los datos han cambiado, se producirá una excepción y la aplicación se aplica la lógica de negocios a recuperarse.

Niveles de aislamiento de una transacción
Un aislamiento completo puede ser bueno, pero tiene un costo elevado , esto significa que los datos leídos o escritos en una transacción deben estar bloqueados.
Dependiendo de la aplicación, puede que no se necesite un aislamiento completo. Al ajustar el nivel de aislamiento, puede reducir el número de bloqueos y aumentar la escalabilidad y el rendimiento:
■ Dirty read: permite leer los datos que han sido modificados por otra transacción, pero no se han comiteado aún. Esto puede ser un gran problema si la transacción que ha cambiado los datos se revierte.
■ Nonrepeatable read: es cuando una transacción lee la misma fila más de una vez con
resultados diferentes porque otra transacción ha modificado la fila entre lecturas.
■ Phantom read: Cuando una transacción lee una fila que otra transacción eliminará o cuando una segunda lectura descubre una nueva fila que se ha insertado por otra transacción.


Niveles de aislamiento de la transacción junto y sus efectos:

Nivel

DIRTY READ

NONREPEATABLE
READ

PHANTOM READ

MODELO DE CONCURRENCY

lectura sin comitear

si

si

si

ninguno

lectura comiteada con Locks

no

si

si

pesimista

lectura comiteada con Snapshots

no

si

si

optimista

lectura repetida

no

no

si

pesimista

Snapshot

no

no

no

optimista

Serializable

no

no

no

pesimista

Transacciones individuales y transacciones distribuidas
En el. NET Framework, una transacción por lo general representa todo el trabajo que se puede hacer en una sola conexión abierta.
Una transacción distribuida abarca varios recursos duraderos. En el. NET Framework, si
que necesita una operación para incluir el trabajo de múltiples conexiones, debe realizar una transacción distribuida. Una transacción distribuida utiliza un protocolo comiteo en dos fases y un administrador de transacciones dedicado (Coordinador de transacciones distribuidas - DTC).

Creación de una transacción
Cada sentencia SQL se ejecuta en su propia transacción implícita. Si no creamos explícitamente una transacción, esta se crea de forma implícita. Esto asegura que una sentencia SQL que actualiza filas como una unidad completa o se deshace.

Creación de una transacción mediante T-SQL
Una transacción explícita es la que se crea en el programa. Se puede crear una transacción de forma explícita en T-SQL con el siguiente script:

SET XACT_ABORT ON

BEGIN TRY

BEGIN TRANSACTION

--codigo para realizar algo

COMMIT TRANSACTION

END TRY

BEGIN CATCH

ROLLBACK TRANSACTION

--codigo de limpieza

END CATCH

Creación de una transacción utilizando el objeto ADO.NET DbTransaction
Otra forma de crear una transacción explícita es poner la lógica de la transacción en el codigo del .NET Framework. El DbConnection tiene el método BeginTransaction, que crea un objeto DbTransaction:

--------------------------------------VB------------------------------------------

Private Sub BeginTransactionToolStripMenuItem_Click( _

ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles BeginTransactionToolStripMenuItem.Click

Dim cnSetting As ConnectionStringSettings = _

ConfigurationManager.ConnectionStrings("nw")

Using cn As New SqlConnection()

cn.ConnectionString = cnSetting.ConnectionString

cn.Open()

Using tran As SqlTransaction = cn.BeginTransaction()

Try

'realizar algo

Using cmd As SqlCommand = cn.CreateCommand()

cmd.Transaction = tran

cmd.CommandText = "SELECT count(*) FROM employees"

Dim count As Integer = CInt(cmd.ExecuteScalar())

MessageBox.Show(count.ToString())

End Using

'commiteamos

tran.Commit()

Catch xcp As Exception

tran.Rollback()

'limpiamos

MessageBox.Show(xcp.Message)

End Try

End Using

End Using

End Sub

--------------------------------------CS------------------------------------------

private void beginTransactionToolStripMenuItem_Click(object sender, EventArgs e)

{

ConnectionStringSettings cnSetting =

ConfigurationManager.ConnectionStrings["nw"];

using (SqlConnection cn = new SqlConnection())

{

cn.ConnectionString = cnSetting.ConnectionString;

cn.Open();

using (SqlTransaction tran = cn.BeginTransaction())

{

try

{

//realizo tareas

using (SqlCommand cmd = cn.CreateCommand())

{

cmd.Transaction = tran;

cmd.CommandText = "SELECT count(*) FROM employees";

int count = (int)cmd.ExecuteScalar();

MessageBox.Show(count.ToString());

}

//commitear

tran.Commit();

}

catch (Exception xcp)

{

tran.Rollback();

MessageBox.Show(xcp.Message);

}

}

}

}

Establecer el nivel de aislamiento
Cada conexión de SQL Server (sesión de SQL) puede tener su nivel aislamiento de la transacción establecido. El ajuste se mantiene hasta que la conexión está cerrada o hasta que se asigne un nuevo valor. Una manera de determinar el nivel de aislamiento es añadir la instrucción SQL para el procedimiento almacenado.

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

Otra forma de establecer el nivel de aislamiento consiste en añadir una query a la declaración SQL.

SELECT * FROM CUSTOMERS WITH (NOLOCK)

El nivel de aislamiento también se puede establecer en la clase DbTransaction, de la quehereda la clase SqlTransaction. Basta con pasar el nivel de aislamiento de transaccióndeseada al método BeginTransaction.

La introducción del espacio de nombres System.Transactions
El espacio de nombres System.Transactions ofrece soporte mejorado de las transacciones para el código administrado (de SQL Server 2005 y posteriores).

Creación de una transacción mediante la clase TransactionScope
También puede crear una transacción utilizando el espacio de nombresSystem.Transactions. La clase más utilizada es la clase TransactionScope:

--------------------------------------VB------------------------------------------

Private Sub SystemTransactionToolStripMenuItem_Click(

ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles SystemTransactionToolStripMenuItem.Click

Dim cnSetting As ConnectionStringSettings = _

ConfigurationManager.ConnectionStrings("nw")

Using ts As TransactionScope = New TransactionScope()

Using cn As New SqlConnection()

cn.ConnectionString = cnSetting.ConnectionString

cn.Open()

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = "SELECT count(*) FROM employees"

Dim count As Integer = CInt(cmd.ExecuteScalar())

MessageBox.Show(count.ToString())

End Using

'commitea

ts.Complete()

End Using

End Using

End Sub

----------------------------------CS-------------------------------------

private void systemTransactionToolStripMenuItem_Click(

object sender, EventArgs e)

{

ConnectionStringSettings cnSetting =

ConfigurationManager.ConnectionStrings["nw"];

using (TransactionScope ts = new TransactionScope())

{

using (SqlConnection cn = new SqlConnection())

{

cn.ConnectionString = cnSetting.ConnectionString;

cn.Open();

using (SqlCommand cmd = cn.CreateCommand())

{

cmd.CommandText = "SELECT count(*) FROM employees";

int count = (int)cmd.ExecuteScalar();

MessageBox.Show(count.ToString());

}

//commitea

ts.Complete();

}

}

}

En la última línea de codigo se llama al método Complete para commitear la transacción. Este método puede ser llamado una sola vez. Una segunda llamada al completo será una excepción InvalidOperationException.

Configuración de las opciones de una transacción
Se puede configurar el nivel de aislamiento y tiempo de espera de la operación en el TransactionScope mediante la creación de un TransactionOptions.  El nivel de aislamiento es sólo una sugerencia para la base de datos. La mayoría de los motores de base de datos intenta utilizar el  nivel recomendado, si es posible.

El constructor del TransactionScope también toma un parámetro de  enumeración, TransactionScopeOption:

Nombre

Descripción

Required

El ámbito requiere una transacción. Utiliza una transacción de ambiente si ya existe una. De lo contrario, crea una nueva transacción antes de entrar en el ámbito. Este es el valor predeterminado.

RequiresNew

Siempre se crea una nueva transacción para el ámbito.

Suppress

Todas las operaciones dentro del ámbito se realizan sin un contexto de transacción de ambiente.

El siguiente código crea y configura un TransactionOptions, que se pasa al constructor de la TransactionScope.

--------------------------------------VB------------------------------------------

Private Sub TransactionOptionsToolStripMenuItem_Click( _

ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles TransactionOptionsToolStripMenuItem.Click

Dim cnSetting As ConnectionStringSettings = _

ConfigurationManager.ConnectionStrings("nw")

Dim opt As New TransactionOptions()

opt.IsolationLevel = IsolationLevel.Serializable

Using ts As TransactionScope = _

New TransactionScope(TransactionScopeOption.Required, opt)

Using cn As New SqlConnection()

cn.ConnectionString = cnSetting.ConnectionString

cn.Open()

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = "SELECT count(*) FROM employees"

Dim count As Integer = CInt(cmd.ExecuteScalar())

MessageBox.Show(count.ToString())

End Using

End Using

'commiteo

ts.Complete()

End Using

End Sub

--------------------------------------CS------------------------------------------

private void transactionScopeOptionsToolStripMenuItem_Click(

object sender, EventArgs e)

{

ConnectionStringSettings cnSetting =

ConfigurationManager.ConnectionStrings["nw"];

TransactionOptions opt = new TransactionOptions();

opt.IsolationLevel = System.Transactions.IsolationLevel.Serializable;

using (TransactionScope ts =

new TransactionScope(TransactionScopeOption.Required, opt))

{

using (SqlConnection cn = new SqlConnection())

{

cn.ConnectionString = cnSetting.ConnectionString;

cn.Open();

using (SqlCommand cmd = cn.CreateCommand())

{

cmd.CommandText = "SELECT count(*) FROM employees";

int count = (int)cmd.ExecuteScalar();

MessageBox.Show(count.ToString());

}

}

// commiteo

ts.Complete();

}

}

Trabajando con transacciones distribuidas
El namespace System.Transactions incluye el administrador de transacciones ligeras (LTM), además de la DTC (Distributed Transaction Coordinator). Estos representan a sus transacciones mediante el uso de la System.Transactions. La clase Transacción, tiene una propiedad estática (en shared  en Visual Basic) llamada Current que da acceso a la transacción actual. La transacción actual se conoce como la transacción del ambiente.
Esta propiedad es null (Nothing Visual Basic) si no hay ninguna transacción en curso. Se puede acceder a la propiedad actual directamente al cambiar el nivel de aislamiento de la transacción, deshacer la transacción, o ver el estado de la transacción.

Detalles de la promoción

Si la promoción es necesaria, el LTM le dice el administrador de recursos duraderos que proporcione un objeto capaz de realizar una transacción distribuida. Para soportar una notificación, el administrador de recursos duradero debe implementar la interfaz IPromotableSinglePhaseNotification. Esta interfaz, y su interfaz principal, el ITransactionPromoter, se muestran aquí.

--------------------------------------VB------------------------------------------

Imports System

Namespace System.Transactions

Public Interface IPromotableSinglePhaseNotification

Inherits ITransactionPromoter

Sub Initialize()

Sub Rollback(ByVal singlePhaseEnlistment As _

SinglePhaseEnlistment)

Sub SinglePhaseCommit(ByVal singlePhaseEnlistment As _

SinglePhaseEnlistment)

End Interface

Public Interface ITransactionPromoter

Function Promote() As Byte()

End Interface

End Namespace

--------------------------------------CS------------------------------------------

using System;

namespace System.Transactions

{

public interface IPromotableSinglePhaseNotification :

ITransactionPromoter

{void Initialize();

void Rollback(SinglePhaseEnlistment

singlePhaseEnlistment);

void SinglePhaseCommit(SinglePhaseEnlistment

singlePhaseEnlistment);

}

public interface ITransactionPromoter

{

byte[] Promote();

}

}

Viendo transacciones distribuidas
El DTC está disponible a través de los Servicios de componentes (en windows con idioma ingles : Start | Control Panel | Administrative Tools | Component Services | Computers | My Computer | Distributed Transaction Coordinator | LocalDTC | Transaction Statistics).
Si se ejecuta cualquiera de los ejemplos anteriores, la pantalla sigue sin mostrar actividad, porque los ejemplos utilizan transacciones estándar, cuando una transacción se promueve a una transacción distribuida, se ve un cambio en el recuento total de la transacción, así como a los otros contadores.

clip_image001[4]

Creación de una transacción distribuida
Para crear una transacción distribuida, puede utilizar el modelo de programación TransactionScope que haya usado para crear una transacción estándar, pero añadir el trabajo a realizar en una conexión diferente. El siguiente ejemplo de código utiliza dos conexiones. A pesar de que estos objetos de conexión utilizan la misma cadena de conexión, que son objetos de conexión diferentes, lo que hará que la transacción para ser promovidos a una transacción distribuida.

--------------------------------------VB------------------------------------------

Dim nwSetting As ConnectionStringSettings = _

ConfigurationManager.ConnectionStrings("nw")

Dim bulkSetting As ConnectionStringSettings = _

ConfigurationManager.ConnectionStrings("BulkCopy")

Using ts As TransactionScope = New TransactionScope()

Using cn As New SqlConnection()

cn.ConnectionString = nwSetting.ConnectionString

cn.Open()

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = _

"Update Products SET UnitsInStock = UnitsInStock -1 " _

& " Where ProductID=1"

cmd.ExecuteNonQuery()

End Using

End Using

Using cn As New SqlConnection()

cn.ConnectionString = bulkSetting.ConnectionString

cn.Open()

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = _

"Update Products SET UnitsInStock = UnitsInStock +1 " _

& " Where ProductID=2"

cmd.ExecuteNonQuery()

End Using

End Using

ts.Complete()

End Using

Dim dt As New DataTable()

Using cn As New SqlConnection()

cn.ConnectionString = nwSetting.ConnectionString

cn.Open()

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = _

"SELECT ProductID, UnitsInStock FROM Products " _

& " WHERE ProductID = 1"

dt.Load(cmd.ExecuteReader())

End Using

End Using

Using cn As New SqlConnection()

cn.ConnectionString = bulkSetting.ConnectionString

cn.Open()

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = _

"SELECT ProductID, UnitsInStock FROM Products " _

& " WHERE ProductID = 2"

dt.Load(cmd.ExecuteReader())

End Using

End Using

DataGridView2.DataSource = dt

--------------------------------------CS------------------------------------------

var nwSetting = ConfigurationManager.ConnectionStrings["nw"];

var bulkSetting = ConfigurationManager.ConnectionStrings["BulkCopy"];

using (var ts = new TransactionScope())

{

using (var cn = new SqlConnection())

{

cn.ConnectionString = nwSetting.ConnectionString;

cn.Open();

using (var cmd = cn.CreateCommand())

{

cmd.CommandText =

"Update Products SET UnitsInStock = UnitsInStock -1 "

+ " Where ProductID=1";

cmd.ExecuteNonQuery();

}

}

using (var cn = new SqlConnection())

{

cn.ConnectionString = bulkSetting.ConnectionString;

cn.Open();

//work code here

using (var cmd = cn.CreateCommand())

{

cmd.CommandText =

"Update Products SET UnitsInStock = UnitsInStock +1 "

+ " Where ProductID=2";

cmd.ExecuteNonQuery();

}

}

ts.Complete();

}

var dt = new DataTable();

using (var cn = new SqlConnection())

{

cn.ConnectionString = nwSetting.ConnectionString;

cn.Open();

using (var cmd = cn.CreateCommand())

{

cmd.CommandText =

"SELECT ProductID, UnitsInStock FROM Products "

+ " WHERE ProductID = 1";

dt.Load(cmd.ExecuteReader());

}

}

using (var cn = new SqlConnection())

{

cn.ConnectionString = bulkSetting.ConnectionString;

cn.Open();

 

using (var cmd = cn.CreateCommand())

{

cmd.CommandText =

"SELECT ProductID, UnitsInStock FROM Products "

+ " WHERE ProductID = 2";

dt.Load(cmd.ExecuteReader());

}

}

dataGridView2.DataSource = dt;

Como esta transacción tiene múltiples conexiones, requiere de varios administradores de recursos duraderos, y una transacción que fue delegada inicialmente a SQL Server 2005 y más tarde es promovida a una transacción distribuida.

Resumen de la lección
Esta lección proporciona una visión detallada de las clases ADO.NET conectadas.
 Transacciones tienen cuatro atributos esenciales: atomicidad, coherencia, aislamiento y durabilidad (conocido como propiedades ACID).
 Los atributos de la consistencia y el aislamiento se implementan mediante el  mecanismo de bloqueo del Administrador de la base de datos de transacción ligera (LTM) , que evita que una transacción afecte a otra.
■ En lugar de operar con un completo aislamiento, se puede modificar el nivel de aislamiento. Esto reduce la cantidad de bloqueos y aumenta la escalabilidad y el rendimiento.
 Para crear una transacción, se puede utilizar el método BeginTransaction en la claseDbConnection.
■ También se puede utilizar la clase TransactionScope para crear una transacción de ascenso.
 Una transacción promocionable comienza como una transacción explícita
, pero esta operación puede ser promovida a una transacción distribuida.

No hay comentarios:

Publicar un comentario