Nivel intermedio

Redimensionar un control de texto con el tamaño de su contenido


En ocasiones, trabajando con WinForms, nos encontramos con un control contenedor de un texto variable en el que, o nos sobra espacio o nos falta. La clase Graphics de System.Drawing nos proporciona un método tremendamente útil para saber el tamaño, en píxels, de una cadena de texto.
Veremos a continuación cómo redimensionar de forma dinámica un control dependiendo del tamaño del texto contenido. Para ello crearemos, por ejemplo, un TextBox al que llamaremos txtPrueba, y en el que albergaremos un texto, por ejemplo «Más vale pájaro en mano, que ciento volando».


txtPrueba.Text = "Más vale pájaro en mano que ciento volando";

A continuación, declararemos dos variables enteras, una para el alto y otra para el ancho.

// Declaramos una variable entera para almacenar el nuevo ancho y el nuevo alto
int nuevoAncho = 0;
int nuevoAlto = 0;

Es ahora donde instanciaremos la clase Graphics a partir del control TextBox, y extraeremos la fuente que está utilizando.

// Creamos un objeto de tipo Graphics a partir del TextBox
Graphics g = txt.CreateGraphics();

// Extraemos la fuente del TextBox
Font fuente = txt.Font;

Obtenemos, a partir del objeto Graphics, su alto y ancho a partir del método MeasureString, que no hace otra cosa que medir, en pixels, el tamaño que ocupa la cadena con la fuente indicada.


nuevoAncho = (int)g.MeasureString(txt.Text, fuente).Width;
nuevoAlto = (int)g.MeasureString(txt.Text, fuente).Height;

Por último, asignamos los nuevos valores de alto y ancho y refrescamos el control.


txt.Width = nuevoAncho;
txt.Height = nuevoAlto;

txt.Refresh();

A partir de estos conceptos básicos, se pueden encontrar multitud de utilidades a este proceso, como por ejemplo, redimensionar el desplegable de un ComboBox al tamaño del mayor de sus elementos.

Crear un campo autoincrementable en ORACLE


Para los que nos iniciamos en los Gestores de Bases de Datos con SQL Server, la transición a ORACLE resulta siempre más dura que cuando se realiza el camino inverso. Realizando una analogía simple, en mi caso podría decir que T-SQL es a PL/SQL lo que C# es a C++. Más complicación, más potencia… y un sistema más «tiquismiquis» que los desarrollados por Microsoft.
SQL Server proporciona una forma muy sencilla de crear campos autonuméricos, generalmente utilizados en los IDs de las claves primarias de las tablas. Para crear un campo autoincrementable, lo único que teníamos que hacer era editar las propiedades de la columna que queríamos convertir en autoincrementable, acceder a sus propiedades y seleccionar la opción [Especificación de Identidad] marcando la casilla (Identidad) e informando del número de inicio del índice (Inicialización de identidad) y el número de unidades que se incrementará cada vez que se intente insertar un registro (Incremento de identidad).

Oracle nos pone las cosas un poco más difíciles, obligándonos a realizar dos pasos para realizar el mismo procedimiento: crear una secuencia y crear un trigger.

(más…)

Utilizar la Base de Datos como repositorio de imágenes (II)


En un artículo anterior aprendimos a almacenar una imagen en nuestra base de datos SQL Server. Ahora vamos a obtener la imagen que almacenamos previamente utilizando como dirección de la imagen un formulario aspx al que le pasaremos el identificador de la imagen a través de la QueryString.

Comenzaremos creando un nuevo formulario, al que llamaremos showImage.aspx. En el formulario, crearemos un nuevo método al que llamaremos ObtenerImagen, que recibirá como parámetro un entero (el ID de la imagen) y devolverá una secuencia de bytes (un byte[]).

byte[] ObtenerImagen(int idImagen)
{
}

Lo primero que hará este método será declarar una cadena de conexión y una sentencia SELECT que devuelva la imagen cuyo ID le pasamos. Para ello, declararemos ambas cadenas de texto.


string selectCommandText = "SELECT Fichero FROM Imagen WHERE IdImagen = " + Convert.ToString(idImagen);
string ConnectionString = @"Data Source=DANIGARCIASQLSERVER2005;Initial Catalog=TestDB;Persist Security Info=True;User ID=dani;Password=c0ntr4s3n14";

A continuación podemos hacer dos cosas: crear un SqlCommand y ejecutar la sentencia SELECT, o bien declarar un DataSet y un SqlDataAdapter y dejar que sea éste último objeto el que nos proporcione los datos. Optaremos por la segunda opción, que pese a ser menos óptima, resulta mucho más cómoda 🙂


// Declaramos un DataSet y un SqlDataAdapter
DataSet ds = new DataSet();
SqlDataAdapter adapter = new SqlDataAdapter(selectCommandText, selectConnectionString);

// Rellenamos el DataSet con la consulta y la cadena de conexión anteriores
adapter.Fill(ds);

Llegados a este punto, ya deberíamos tener almacenada en el DataSet la imagen que estábamos buscando. Por lo tanto, lo único que deberemos hacer será serializarla y devolverla, realizando las comprobaciones pertinentes:


if ((ds != null) && (ds.Tables.Count > 0) && (ds.Tables[0].Rows.Count > 0))
{
if (!ds.Tables[0].Rows[0].IsNull("Fichero"))
{
return ((byte[])(ds.Tables[0].Rows[0]["Fichero"]));
}
}

return null;

Llegados a este punto, codificaremos el contenido del Page_Load de nuestro aspx, comprobando la QueryString y pasándole el parámetro a nuestra función ObtenerImagen, declarando, en primer lugar, un array de Bytes para almacenar el resultado:


protected void Page_Load(object sender, EventArgs e)
{
byte[] bImagen = null;

if (!string.IsNullOrEmpty(Convert.ToString(Request.QueryString["id"])))
{
bImagen = ObtenerImagen(Convert.ToInt32((Convert.ToString(Request.QueryString["id"]))));
}

Si el resultado obtenido no es nulo, modificamos la respuesta para que sea de tipo imagen, haciendo lo siguiente:


if(bImagen != null)
{
Response.Clear();
Response.Buffer = true;
Response.ContentType = "image/jpeg";
Response.BinaryWrite(bImagen);
Response.End();
}
}

Con lo cual, si invocamos la ruta a través del ID, obtendremos directamente una imagen:


Esto es una prueba <br />
<img src="../aspx/showImage.aspx?id=1" />

Utilizar la Base de Datos como repositorio de imágenes (I)


En cierto proyecto necesitaba mostrar una serie de imágenes que el usuario debía insertar desde su equipo local. En un principio pensé enviar al servidor las imágenes, guardar la ruta relativa en Base de Datos y, cuando fuese necesario acceder a las imágenes, utilizar dicha ruta para acceder a la imagen. Pero no era tan sencillo. Por desgracia, no teníamos permiso de escritura en el disco duro, por lo que tenía que juguetear con las imágenes sin que éstas existieran físicamente. ¿Cómo? Almacenando y recuperando las imágenes de base de datos.

Guardando una imagen en SQL Server

Para empezar, veremos cómo almacenar en base de datos una imagen. Crearemos, desde el SQL Server Management Studio, una nueva tabla que tendrá tres campos:

  • Un Id único entero y autoincrementable (IdImagen)
  • Un nombre para la imagen nvarchar(50) (NombreImagen)
  • Una secuencia de bytes, variable de tipo image (Fichero).

09111201 Creada la tabla, crearemos una página que, a partir de la ruta de la imagen, la inserte en base de datos. Podemos utilizar un Input File o un control similar para indicarle la ruta, pero aquí indicaremos únicamente el código necesario para subir la imagen dada su ruta física. Lo que haremos a continuación será lo siguiente:

  • Crear una conexión a Base de Datos a partir de una cadena de conexión
string ConnectionString = @"Data Source=DANIGARCIASQLSERVER2005;Initial Catalog=TestDB;Persist Security Info=True;User ID=dani;Password=c0ntr4s3n14";
  • Crear una cadena de texto con la sentencia INSERT.
string CommandString = "INSERT INTO Imagen(NombreImagen, Fichero) VALUES (@NombreImagen, @Fichero)";
  • A continuación necesitaremos convertir nuestra imagen en un objeto que podamos manejar, por ejemplo un array de bytes. Para ello, a partir de la ruta física de la imagen crearemos un objeto de tipo System.Drawing.Bitmap que guardaremos en un MemoryStream que, a su vez, convertiremos en un array de bytes (byte[]).
  byte[] bImagen = null; System.IO.MemoryStream ms = new System.IO.MemoryStream();  System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(@"C:TEMPinicio.jpg"); if (bmp != null) { bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); bImagen = ms.ToArray(); ms.Close(); }  
  • Instanciar un objeto de tipo SqlCommand a partir de la  conexión y de la sentencia INSERT.
  SqlConnection conexion = new SqlConnection(ConnectionString);  SqlCommand command = new SqlCommand(CommandString, conexion);  
  • Añadiremos ahora los SqlParameters necesarios al objeto SqlCommand con los datos a insertar en la base de datos. Estos serán, en nuestro caso, el nombre de la imagen y el array de bytes con la imagen.
  SqlParameter nombre = new SqlParameter("@NombreImagen", "inicio.jpg"); SqlParameter imagen = new SqlParameter("@Fichero", bImagen);  
  • Por último, abrimos la conexión, ejecutamos la sentencia mediante ExecuteNonQuery() y cerramos la conexión. Hecho esto, tendremos nuestra imagen almacenada en base de datos.
  conexion.Open();  command.Parameters.Add(nombre); command.Parameters.Add(imagen); command.ExecuteNonQuery();  conexion.Close();  

Hecho esto insertaremos una imagen en una tabla de la base de datos. 09111202 Más adelante aprenderemos cómo recuperarla y referenciarla directamente a través de una URL con extensión aspx, es decir, invocar una ruta de tipo getImage.aspx?Id=xxx.

Enviar un e-mail desde .NET utilizando una cuenta SMTP de GMail


En mi proyecto de fin de carrera implementé un sistema automatizado de reservas que avisaba por e-mail del estado de las mismas. Para mi desgracia, olvidé mencionarlo durante la defensa, por lo que a nivel institucional fue una pérdida de tiempo, pero no así a nivel didáctico (que es, a fin de cuentas, lo importante).

Para implementarlo, utilicé los espacios de nombres System.Net y System.Net.Mail y una cuenta de Gmail a la que previamente le había activado el acceso POP/SMTP. Mandar un mail desde .NET es relativamente sencillo.

Si pensamos en la composición de un mail en términos de objetos, veremos que en realidad, enviar un mail es muy, pero que muy sencillo. Imaginemos que tenemos una cuenta en Gmail, por ejemplo ‘estoesunapruebademail25@gmail.com‘, cuya contraseña es ‘inteligible43‘. Queremos mandar un correo. ¿Qué información necesitamos?

  • Un mensaje, que implementará la clase MailMessage.
  • Un cliente de correo (SmtpClient) que se encargue de la autenticación y envío del mensaje.

A su vez, el mensaje necesitará:

  • Uno o varios destinatarios.
  • Un remitente.
  • Un asunto (subject).
  • El texto del mensaje.

Mientras que el cliente del correo necesitará saber:

  • Servidor SMTP al que conectarse y puerto.
  • Nombre de usuario y contraseña.

(más…)

Transacciones en ADO.NET (I): La clase TransactionScope


Hay determinadas ocasiones en las que se nos presenta la obligación de realizar inserciones, eliminaciones o actualizaciones en una base de datos en la cual los datos que se van a tocar están relacionados entre sí. Imaginemos la siguiente situación: nuestro cliente regenta un concesionario, y un cliente desea adquirir un vehículo. Tras rellenar el papeleo pertinente, la venta toca a su fin, y necesitamos realizar una serie de operaciones, tales como:

  • Enviar la orden de cobro al banco.
  • Enviar a Tráfico los datos de nuestro cliente para el registro del mismo.
  • Almacenar los datos de nuestro cliente en la base de datos de la empresa.

Imaginemos por un momento que una de estas operaciones falla, por ejemplo, la segunda: la orden al banco ya habrá sido cursada, mientras que tráfico y el concesionario no tendrán notificación alguna de la titularidad del vehículo. Los datos se encontrarían, pues, en un estado inconsistente. Para evitar este tipo de situaciones recurrimos a las transacciones. Una transacción es un conjunto de operaciones sobre una o varias fuentes de datos, con la peculiaridad de que o se ejecutan todas correctamente, o no se ejecuta ninguna. Una transacción se caracteriza por cumplir las llamadas propiedades ACID, que identifican los requisitos para que una transacción se realice. Estas propiedades son: atomicidad (Atomicity), coherencia (Consistency), aislamiento (Isolation) y permanencia (Durability). Así, en el ejemplo anterior, si la conexión con la base de datos de Tráfico devuelve un error, se revertirán los cambios realizados en la conexión con el banco, dejando la transacción en el estado inicial. Es lo que llamamos un ROLLBACK o «vuelta atrás». En el caso de que todo haya ido correctamente, procederíamos a «comprometer» la transacción, ejecutando todas las operaciones por las que ésta se componga. Es lo que llamamos COMMIT. Vista la introducción teórica ¿cómo implementamos una transacción en ADO.NET? Personalmente me gusta implementar las transacciones mediante entornos transaccionales o transactional scopes, implementados mediante la clase System.Transactions.TransactionScope. Lo primero que deberemos hacer será añadir una referencia a la biblioteca System.Transactions.

090701st

Hecho esto, mediante la cláusula using crearemos un entorno transaccional en el que encerraremos un bloque try/catch. En el interior del try será donde implementemos la lógica de acceso a datos, realizando múltiples consultas/inserciones/eliminaciones/actualizaciones. Si se produce un error, el flujo de control saltará al bloque catch, donde se interrumpirá la transacción y se realizará un ROLLBACK automático. Si no se produce ningún error, se llegará a la cláusula Complete() de la transacción, la cual comprometerá la misma y dejará la fuente de datos en un estado consistente. En C# sería algo como lo que sigue, suponiendo que la lógica de acceso a datos está encapsulada en los métodos del objeto DAO y que ‘listaElementos’ es un array de enteros que contiene identificadores de la tabla a modificar:

using (System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope())
{
try
{
foreach (int id in listaElementos)
{
dao.OrdenarEnvioCuentaBancaria(id, numeroCuenta, datosCliente);
dao.RegistrarVehiculoEnTrafico(id, matricula, datosCliente);
dao.GuardarDatosCompraCliente(id, matricula, datosCliente, fechaActual);
}
scope.Complete();
}
catch (Exception ex)
{
throw (ex);
}
}

Esto nos permitirá realizar transacciones de forma segura. Pero ¡ojo! ¿y si lo que queremos realizar son operaciones sobre distintas fuentes de datos? Para ello necesitaremos activar las transacciones distribuidas, que por defecto aparecen deshabilitadas. En un artículo posterior aprenderemos cómo activarlas.

Servicios Web (II): Consumiendo un servicio web


Veíamos anteriormente cómo crear un servicio web simple. A continuación veremos cómo consumirlo.

Si crear un WebService no tiene demasiada dificultad, consumirlo es, si cabe, mucho más sencillo. Crearemos una aplicación web normal y corriente, y sobre ésta, haremos click derecho y seleccionaremos la opción [Agregar Referencia Web…]

0906agregarreferenciaweb
Hecho esto, se desplegará un menú que nos permitirá la detección de servicios web. Si introducimos la dirección en la que el servicio está publicado y pulsamos en «Ir» se nos mostrará los métodos que el servicio expone. En la caja de texto derecha añadiremos el nombre que tendrá nuestra referencia (por ejemplo, ServicioSimple) y pulsaremos sobre [Agregar referencia].

0906servicio

Hecho esto, se agregará a nuestro proyecto una referencia a nuestro servicio web, que se mostrará en nuestro proyecto de la siguiente manera:

0906referencia

Finalmente, añadiremos controles a nuestra página para comprobar que nuestra aplicación se comunica perfectamente con el servicio web. Para ello crearemos un GridView, dos Label, una caja de Texto y tres botones.

En el primer botón codificaremos la funcionalidad para cargar el GridView con la primera tabla del DataSet devuelto por el método GetData() de nuestro servicio web. Para ello crearemos una referencia a nuestro servicio, crearemos una referencia a un DataSet e invocaremos el método GetData() del WebService. Finalmente rellenaremos el Grid con el resultado obtenido.

protected void ButtonGrid_Click(object sender, EventArgs e)
{
// Referenciamos el servicio
ServicioSimple.Service referenciaServicio = new ServicioSimple.Service();

// Creamos una referencia a un DataSet
DataSet ds;

// Invocamos el WebMethod GetData()
ds = referenciaServicio.GetData();

// Rellenamos el DataGrid
GridViewDatos.DataSource = ds.Tables[0];
GridViewDatos.DataBind();
}

En el segundo botón haremos que nuestro label muestre el resultado de la invocación al método GetDate(), que recordemos que devolvía la fecha actual.

protected void ButtonFecha_Click(object sender, EventArgs e)
{
// Referenciamos el servicio
ServicioSimple.Service referenciaServicio = new ServicioSimple.Service();

LabelFecha.Text = referenciaServicio.GetDate().ToString();
}

Por último, el tercer botón mostrará el resultado de invocar al método GetDateDiff(int). Para ello le pasaremos al WebService un parámetro que indicará el número de horas que tiene que añadir. Para ello utilizaremos la caja de texto.

protected void ButtonFechaDiff_Click(object sender, EventArgs e)
{
// Referenciamos el servicio
ServicioSimple.Service referenciaServicio = new ServicioSimple.Service();

if (!string.IsNullOrEmpty(TextBoxDiferencia.Text))
{
LabelFechaDiferencia.Text = referenciaServicio.GetDateDiff(Convert.ToInt32(TextBoxDiferencia.Text)).ToString();
}
}

Hecho. Ya hemos creado un servicio web y hemos aprendido a consumirlo.

0906resultado

Servicios Web (I): Creando un servicio web simple


Un servicio web no es más que un conjunto de protocolos y estándares cuyo objetivo es el intercambio de información entre distintas aplicaciones. Los servicios web tienen la peculiaridad de ser independientes de plataforma, es decir, cumplen estándares propios y no dependen ni del hardware sobre el que se ejecuta ni del sistema operativo o servidor web que lo aloja.

Un servicio web intercambia información. Al invocar un servicio web estamos realizando una petición a otra máquina, a la que (opcionalmente) le enviamos información, la procesa y nos devuelve una respuesta, generalmente atómica y serializable, es decir: lo común es que un servicio web nos devuelva un dato o estructura de datos con la información que necesitemos (atómica) en forma de cadena de texto (serializable). Esto es así por el lenguaje común que «hablan» los servicios web: XML.

Si quisiéramos crear un servicio web, en una solución en blanco pulsaríamos sobre Agregar > Nuevo sitio Web…

0906servicioweb01
Hecho esto, seleccionaremos la plantilla «Servicio Web ASP.NET», y alojaremos el servicio web en nuestro IIS indicándole la ruta que deseemos.

0906servicioweb02
A continuación, crearemos los métodos que queramos exponer, añadiendo el atributo [WebService] a la clase y el atributo [WebMethod] a los métodos que deban ser expuestos.

Crearemos tres métodos: uno que devuelva la hora actual, otro que devuelva nuestra localización geográfica y otro que añada las horas pasadas como parámetro a la hora actual.

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebService(Namespace = &quot;http://localhost/Services/Date&quot;)]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
public Service () {

//Eliminar la marca de comentario de la línea siguiente si utiliza los componentes diseñados
//InitializeComponent();
}

[WebMethod]
public DateTime GetDate() {
return DateTime.Now;
}

[WebMethod]
public string GetLocation()
{
return &quot;UTC/GMT +2, Madrid, Spain.&quot;;
}

[WebMethod]
public DateTime GetDateDiff(int horasDiferencia)
{
return DateTime.Now.AddHours(horasDiferencia);
}

}

Hecho esto, iremos a la dirección de nuestro servicio web, lo cual nos mostrará lo siguiente:
0906servicioweb03
Para probar el servicio web, haremos click sobre cualquiera de los métodos expuestos. Si el webservice no requiere ningún parámetro, se nos mostrará directamente el resultado de la invocación:

0906servicioweb05
En caso contrario, se nos pedirá que introduzcamos el parámetro requerido, lo cual nos llevará a la obtención del dato.

0906servicioweb04
Si analizamos el contenido de la respuesta, comprobaremos que, efectivamente, se trata de un fichero XML.

<?xml version="1.0" encoding="utf-8"?>
<dateTime xmlns="http://localhost/Services/Date">2009-06-09T17:27:00.0874935+02:00</dateTime&gt;

Posteriormente comprobaremos cómo realizar una invocación a un servicio web desde nuestra aplicación.

Comprobar la existencia de una tabla en SQL Server


Cuando tratamos con un procedimiento almacenado en el que utilizamos tablas temporales, es posible que un fallo en la consulta arruine el flujo normal del programa, haciendo que la eliminación de la propia tabla temporal no se realice.

Este caso en concreto puede provocar un error en la próxima ejecución del procedimiento, ya que la creación de la tabla temporal provocará un fallo si esta ya existe.
Para intentar subsanar esta posibilidad, podemos comprobar, antes de crear la tabla temporal, si esta ya existe. Para ello haremos lo siguiente:

IF OBJECT_ID('tempdb..#UsuarioTemp') IS NOT NULL
BEGIN
DROP TABLE #UsuarioTemp;
END

Esto comprobará la existencia de la tabla temporal, y si existe, la eliminará. Posteriormente podremos incluir el código de creación de la tabla.

El el caso de que queramos comprobar la existencia de una tabla real (no temporal) de la base de datos, utilizaremos el siguiente código T-SQL:

IF OBJECT_ID('USUARIO') IS NOT NULL
BEGIN
SELECT 'SELECT * FROM USUARIO';
END
ELSE
BEGIN
CREATE TABLE USUARIO(
[IdUsuario] [int],
[Nombre] [varchar](100),
[Apellido1][varchar](100),
[Apellido2][varchar](100),
[Login][varchar](100),
[Password][varchar](100),
)
END

Con lo cual se realizará una consulta sobre la tabla si ésta existe, o la creará en caso contrario. Queda a voluntad del programador las posibilidades de la existencia o no de las tablas.

Tratamiento de cadenas en SQL Server


En cualquier lenguaje de programación es tarea obligada poseer una manipulación de cadenas fluida.

Transact SQL proporciona una variedad bastante amplia de funciones para llevar a cabo tal tarea. Algunas de las operaciones que podemos realizar son concatenación, obtención de subcadenas, pasar a mayúsculas o minúsculas, eliminar espacios en blanco… incluso detectar la similitud entre expresiones.

A continuación veremos algunas de ellos y aplicaremos algunos ejemplos:

(más…)

Realizando una transacción en SQL Server


Por norma general, me gusta codificar la lógica de acceso a datos en programa, en lugar de en procedimientos almacenados en base de datos. Esta aproximación no es muy óptima (como se suele decir, al César lo que es del César y a Dios lo que es de Dios), ya que el entorno transaccional estará mejor gestionado por el SGBD que por el código .NET.
Si queremos trasladar la responsabilidad de gestionar la transacción al SGBD, deberemos saber en primer lugar que se declara del siguiente modo:

BEGIN TRAN
 -- Ejecución de sentencias
COMMIT TRAN

Como imaginaremos, la primera sentencia «arranca» la transacción, mientras que la última la compromete. Pero ¿y si ocurre un error en alguna de las sentencias? ¿Cómo lo detectamos?
En el siguiente ejemplo, utilizaremos variables enteras para trazar los fallos y realizar un ROLLBACK en el caso de que la transacción falle.

Se declarará una variable de tipo INT por cada sentencia a ejecutar, y se inician a 0. Se comprueba, después de cada sentencia ejecutada, la variable @@Error, y se almacena en las variables anteriormente creadas. Si es distinta de 0, hay un error, por lo que se añade información al mensaje de error.
Al final de la transacción, si todas las variables son 0, se hace el COMMIT. Si una de ellas es distinta de cero, significará que existe un error y se hace el Rollback, mostrando el mensaje de error.


DECLARE @CadenaError varchar(1000);
 DECLARE @CodigosError varchar(1000);
 DECLARE @ErrorSentencia1 int;
 DECLARE @ErrorSentencia2 int;
 DECLARE @ErrorSentencia3 int;

 SET @ErrorSentencia1 = 0;
 SET @ErrorSentencia2 = 0;
 SET @ErrorSentencia3 = 0;

 SET @CadenaError = 'Error al realizar actualización: ';
 SET @CodigosError = 'Códigos de error: ';

 -- Inicio de la transacción
 BEGIN TRAN

 -- Primera actualización
 UPDATE Usuario SET Nombre = 'Elena' WHERE Nombre = "Helena";

 -- Almacenamos el posible código de error
 SET @ErrorSentencia1 = @@Error;
 IF (@ErrorSentencia1  0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 1; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia1 AS VARCHAR(10)) + '; ';
 END

 -- Segunda actualización
 UPDATE Perfil SET Nombre = 'Administrador' WHERE Nombre = "Admin";

 -- Almacenamos el posible código de error
 SET @ErrorSentencia2 = @@Error;
 IF (@ErrorSentencia2  0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 2; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia2 AS VARCHAR(10)) + '; ';
 END

 -- Tercera actualización
 UPDATE UsuarioPerfil SET CodigoUsuario = 1 WHERE CodigoPerfil < 3;

 -- Almacenamos el posible código de error
 SET @ErrorSentencia3 = @@Error;
 IF (@ErrorSentencia3  0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 3; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia3 AS VARCHAR(10)) + '; ';
 END

 -- En ausencia de errores, comprometemos la transacción
 IF (@ErrorSentencia1 = 0) AND (@ErrorSentencia2 = 0) AND (@ErrorSentencia3 = 0)

BEGIN
     COMMIT TRAN;
     PRINT('Transacción realizada con éxito');
 END
 ELSE
 BEGIN
     ROLLBACK TRAN;
     PRINT('Transacción abortada');
     PRINT(@CadenaError);
     PRINT(@CodigosError);
 END

Con todo esto, obtendremos suficiente información (esperemos) como para detectar donde está el fallo.

Control Repeater


El control Repeater permite, como su propio nombre indica, repetir una plantilla compuesta por diversos controles un número de veces determinado, de acuerdo a una estructura de datos que pueda proveer los datos necesarios para poder rellenar esos controles. A continuación veremos un ejemplo en ASP.NET y VB.NET

Creación del control

Lo primero que debemos hacer es acceder al cuadro de herramientas y seleccionar el Repeater dentro de los controles de datos.

Control Repeater

Control Repeater

Una vez hayamos creado el control en nuestro aspx, deberemos introducir dentro del mismo aquellos controles que queramos repetir. Nuestro Repeater tendrá, en principio, el siguiente aspecto:

<asp :Repeater ID="Repeater1" runat="server">
</asp>

En el interior del mismo podremos introducir lo que queramos: controles HTML, tablas, controles ASPX… teniendo en cuenta que todo lo que introduzcamos se repetirá un número de veces igual al número de elementos del DataSource que le asignemos al Repeater.

Es aconsejable crear la sección a repetir antes de añadirla al Repeater. Si por ejemplo queremos insertar una tabla con un GridView y un Label, cuyo código sería el siguiente:

<table cellpadding="2" cellspacing="1" bgcolor="#f6f7f9">
  <tr>
    <td>
      <asp :GridView ID="DatosGridView" runat="server" AutoGenerateColumns="False"
              BackColor="White" BorderColor="#F6F7F9" BorderStyle="Solid"
              BorderWidth="1px" CellPadding="1"
              DataKeyNames="Id">
        <columns>
          <asp :BoundField DataField="Id" HeaderText="Id" SortExpression="Id" />
          <asp :BoundField DataField="Descripcion" HeaderText=" Descripcion " SortExpression=" Descripcion" />
        </columns>
      </asp>
      <asp :Label ID="NoResultadosLabel" runat="server" Text="Label"></asp>
    </td>
  </tr>
</table>

Lo que obtendríamos en nuestra página sería lo siguiente:

Ejemplo de Repeater

Ejemplo de Repeater

Una vez creado el componente a repetir, cortamos su código asociado y lo introducimos dentro del Repeater, del siguiente modo:

<asp :Repeater ID="Repeater1" runat="server">
  <itemtemplate>
    <table cellpadding="2" cellspacing="1" bgcolor="#f6f7f9">
      <tr>
        <td>
          <asp :GridView ID="DatosGridView" runat="server" AutoGenerateColumns="False"
                  BackColor="White" BorderColor="#F6F7F9" BorderStyle="Solid"
                  BorderWidth="1px" CellPadding="1"
                  DataKeyNames="Id">
            <columns>
              <asp :BoundField DataField="Id" HeaderText="Id" SortExpression="Id" />
              <asp :BoundField DataField="Descripcion" HeaderText="Descripcion" SortExpression=" Descripcion" />
            </columns>
</asp>
          <asp :Label ID="EtiquetaLabel" runat="server" Text="Label"></asp>
        </td>
      </tr>
    </table>
  </itemtemplate>
</asp>

Ya tenemos una tabla con un GridView y un Label dentro del Repeater. Ahora sólo tendremos que asociarle datos para que funcione.


Enlazado de datos

El Repeater tiene un evento llamado ItemDataBound, el cual se lanza al llamar al método DataBind() del Repeater, del siguiente modo:

Dim lista As New ArrayList
lista.Add(1)
lista.Add(2)
lista.Add(3)
lista.Add(4)

Repeater1.DataSource = lista
Repeater1.DataBind()

La llamada al DataBind() lanzará un evento ItemDataBound por cada uno de los elementos de su DataSource. El DataSource puede ser un ArrayList, un DataTable, un DataSet, etc. En nuestro caso, el evento se lanzará cuatro veces, uno por cada valor del ArrayList.

Además, el contenido del elemento del DataSource será accesible desde el evento ItemDataBound a través de la propiedad e.Item.

Protected Sub RepeaterRecibo_ItemDataBound(ByVal sender As Object, _
             ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) _
             Handles RepeaterRecibo.ItemDataBound

Dentro de este evento será donde realizaremos la asignación de los datos del interior del Repeater, es decir, la asignación de la propiedad Text del Label y el DataBind del GridView. Todo ello dependerá (si así lo deseamos, y es donde radica la potencia de este control) del elemento que en ese momento estemos manejando.

Si quisiéramos hacer dicho enlace, utilizaríamos el método FindControl() para instanciar el objeto en función de la iteración:


Dim EtiquetaLabel As Label
Dim DatosGridView As GridView
Dim dt As DataTable
Dim id As Integer

Select Case e.Item.ItemType
  Case ListItemType.Item, ListItemType.AlternatingItem
    id = CInt(CType(Repeater1.DataSource, ArrayList).Item(e.Item.ItemIndex))

    ' Se rellena el DataTable que obtenemos a partir del elemento del ArrayList
    dt = ConsultarPorId(id)

    ' Se realiza el enlace de los datos, tanto del GridView como del Label.
    EtiquetaLabel = CType(e.Item.FindControl("EtiquetaLabel"), Label)

    DatosGridView = CType(e.Item.FindControl("DatosGridView"), GridView)
    EtiquetaLabel.Text = dt.Rows(0).Item("Nombre").ToString
    DatosGridView.DataSource = dt
    DatosGridView.DataBind()

  End Select

Esto hará que se cargue un GridView y un Label para cada ID (en este caso se trata de IDs, podemos almacenar cualquier tipo de dato en el DataSource), repitiendo el código DataSource.Item.Count veces.