ADO.NET

Bases de datos portables: Utilizando SQLite en una aplicación .NET


English version here.

La mayor parte de las aplicaciones que desarrollamos en .NET de modo profesional suelen implicar la existencia de una base de datos, por norma SQL Server, Oracle, DB2 o MySQL. Sin embargo, algunas veces, pese a necesitar soporte de base de datos, resulta innecesario el montaje y mantenimiento de un gestor de base de datos, bien por portabilidad, bien por licencia, bien porque el volumen de datos y la complejidad de las tablas implicadas no sea muy complejo…

Vimos en artículos relacionados con Android que cada aplicación hace uso de una base de datos SQLite a nivel local. ¿Por qué no aplicar la misma filosofía a una aplicación .NET. Pues bien, es posible encapsular la base de datos en un fichero .db dentro de un directorio local del mismo modo que lo hacen las aplicaciones en Android. Veremos a continuación cómo instalar SQLite para .NET y realizar una pequeña aplicación que realice operaciones CRUD (Create, Retrieve, Update, Delete) sobre una entidad. A partir de ahí, será extrapolable a entidades más complejas.

(más…)

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.

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

Abstracción de Datos (V): Aplicando el patrón Abstract Factory con C# y ADO.NET


Por norma general, un proyecto estará “atado” a una fuente de datos en particular (SQL Server, MySQL, Oracle…). Sin embargo, hay veces que esto no será así: la fuente de datos podría cambiar en cualquier momento, por lo que deberíamos ser capaces de, en la medida de lo posible, abstraer nuestra aplicación de nuestra base de datos.

Previamente comentábamos la flexibilidad otorgada por el patrón de diseño Abstract Factory. Hoy veremos una aplicación práctica, orientada precisamente a obtener práctica independencia de la fuente de datos mediante la utilización de clases comunes a todas las fuentes de datos.

Por norma general utilizamos clases específicas para lograr interactuar con una fuente de datos en concreto (por ejemplo, usaremos un objeto de la clase SqlConnection cuando trabajemos con SQL Server). Éstas clases específicas están optimizadas para trabajar con unos esquemas determinados, aportando un importante incremento del rendimiento de la aplicación. Pero como todo en esta vida, no es gratis. ¿Cuál es el precio? La portabilidad.

(más…)