Manipulando una imagen dentro de un mantenimiento de datos
Cuando nos enfrentamos al desarrollo de un mantenimiento de
datos típico en VB.NET contra un proveedor de datos ADO.NET,
pongamos como caso SQL Server, todos sabemos, al realizar por
ejemplo una inserción en una tabla, el modo de pasar los datos
tradicionales a las columnas de la nueva fila, entendiendo como
datos tradicionales los correspondientes a cadenas, números y
fechas.
Sin embargo, la cuestión ya no resulta tan obvia cuando entran en
juego tipos de datos como las imágenes, que salen del marco de
trabajo de los tipos habituales, por lo que la pregunta a formular
sería la siguiente: ¿cómo integrar una imagen en nuestra gestión de
datos?
La solución más directa y sencilla pasaría por tener nuestras
imágenes en archivos ubicados en una o varias rutas del servidor,
y una tabla en la base de datos con un campo de tipo carácter, que
contuviera la ruta del archivo.
Esta forma de resolver el problema es ampliamente utilizada y
resulta una excelente solución. No obstante, en este artículo, nos
hemos propuesto complicarnos un poco la vida, y nuestra pretensión
no es guardar una cadena con la ruta del archivo que contiene la
imagen, sino el contenido de la propia imagen en la base de datos,
lo que nos llevaría al siguiente interrogante: ¿cómo grabar una
imagen en una base de datos?
El tipo Image de SQL Server
La pregunta que acabamos de plantear nos lleva a adentrarnos en la
información que existe acerca de los tipos de datos en la
documentación de SQL Server (sistema gestor de bases de datos que
utilizaremos a lo largo del artículo), en donde encontramos un
elemento específicamente diseñado para nuestros propósitos: el tipo
Image, que consiste en un tipo de datos compuesto por información
binaria de longitud variable, desde 0 hasta 231-1 (2.147.483.647)
bytes.
Llegados al punto actual ya sabemos dónde hemos de depositar el
contenido de una imagen en una base de datos SQL Server, pero ahora
surge una nueva incógnita: ¿cómo conseguirlo?
¿Qué es una imagen?
Ciñéndonos a la problemática planteada sobre la manipulación de
imágenes dentro de la plataforma .NET Framework, podemos definir
una imagen como una secuencia de datos binarios o un array de
bytes. Detengámonos especialmente en la definición que acabamos de
dar: "secuencia de datos binarios", y su correspondencia con los
elementos que la plataforma .NET pone a nuestra disposición para su
manejo. Dentro de la jerarquía de tipos de .NET, la clase que nos
permite el trabajo con secuencias o flujos de datos es Stream, como
clase abstracta, y todas las clases concretas que derivan de ella
(FileStream, MemoryStream, etc.). Por lo tanto, si lo que
necesitamos es extraer de un archivo gráfico la secuencia de bytes
que contiene, abriremos el archivo con un objeto FileStream y
volcaremos su contenido en un array de tipo Byte; este array será
en última instancia lo que utilizaremos para grabar la imagen en la
base de datos.
El modo de gestión de los datos
ADO.NET es una tecnología que permite el tratamiento de la
información bajo una filosofía conectada o desconectada de la
fuente de datos con la que trabajemos, en función de las clases que
empleemos para dichas tareas de mantenimiento.
En este artículo abordaremos ambos modos de trabajo, de manera que
el lector pueda tener una idea de cuál de los dos esquemas conviene
mejor a sus necesidades, siendo incluso posible desarrollar un
sistema que mezcle ambos.
Como ejemplos ilustrativos de todas las operaciones de
mantenimiento de datos a realizar con imágenes y objetos de
ADO.NET, hemos desarrollado un proyecto que contiene todos los
casos que iremos exponiendo a lo largo de este artículo. Dicho
proyecto puede encontrarlo en los materiales de apoyo disponibles
para este artículo en la dirección www.dotnetmania.com.
El mencionado proyecto consiste en una aplicación Windows con
interfaz MDI, que contendrá dos formularios hijos, uno para los
ejemplos con operaciones en modo conectado, y el otro para los
ejemplos con operaciones desconectadas. En el código de ambos
deberemos importar los siguientes espacios de nombre:
System.Data.SqlClient y System.IO, que nos permitirán utilizar los
objetos de ADO.NET para el proveedor de SQL Server y los diferentes
tipos de stream respectivamente.
El objetivo de la aplicación consiste en almacenar y gestionar las
fotos que tomamos con una cámara digital, y que tenemos repartidas
en un numeroso conjunto de archivos. Para lo que crearemos una base
de datos SQL Server que llamaremos InfoViajes, y que contendrá una
tabla con el nombre Fotos y la estructura mostrada en la siguiente
tabla.
Una vez que hemos entrado en situación, comenzaremos con el
formulario frmModoConectado, que usaremos para grabar un archivo
gráfico en la base de datos, y posteriormente recuperar una de esas
imágenes almacenadas. La siguiente figura muestra una porción de
este formulario.
Insertar imágenes en una base de datos utilizando un objeto
Command
Tras introducir un número como identificador de foto en la caja de
texto del formulario, pulsaremos su botón "Grabar imagen en BD",
que ejecutará el código mostrado en el fuente 1.
El proceso que llevamos a cabo al ejecutar este código es el
siguiente: tras pasar las oportunas validaciones que comprueban si
hemos tecleado un código para la imagen a grabar, abrimos un
control OpenFileDialog que nos facilita la tarea de seleccionar el
archivo gráfico. Una vez elegido éste, lo abriremos haciendo uso de
una secuencia representada mediante un objeto FileStream, y
dimensionaremos un array de bytes con un tamaño igual a la longitud
del stream. Seguidamente leeremos el contenido del stream y lo
volcaremos en el array, con lo que ya tendremos la información
binaria de la imagen en un formato que nos permita tratarlo para su
inserción en la base de datos.
A continuación, construiremos la conexión, el comando, y la cadena
con la sentencia SQL a enviar a la base de datos. También
definiremos tantos objetos Parameter como campos a grabar; en el
caso concreto de la imagen, observemos que el parámetro lo creamos
especificando como tipo de dato SqlDbType.Image, correspondiente a
la enumeración que nos devuelve los tipos disponibles para SQL
Server; el valor que pasamos al parámetro será el array de bytes
que contiene la información binaria de la imagen.
Por último, abrimos la conexión, ejecutamos el comando y cerramos
la conexión. Si todo ha funcionado correctamente, tendremos la
imagen grabada en un registro de la tabla. Para comprobarlo podemos
ejecutar una consulta hacia dicha tabla desde el Analizador de
consultas de SQL Server, obteniendo un resultado como el que vemos
en la figura 2.
Obviamente no veremos en el campo Foto la imagen tal y como estamos
acostumbrados, sino como una sucesión de datos binarios, pero que
son suficientemente indicativos de que el contenido del archivo ha
sido grabado.
¿Y si utilizamos el proveedor de OLEDB?
Aunque este artículo está orientado hacia uso del proveedor de
datos para SQL Server, si el lector se encuentra ante la necesidad
de gestionar imágenes residentes en un origen de datos para el que
deba de usar el proveedor de OLEDB, la mecánica a seguir sería la
misma que acabamos de explicar, utilizando como es natural, los
objetos de ADO.NET específicos para este proveedor de datos:
OleDbConnection, OleDbCommand, OleDbParameter, etc. Veamos en el
fuente 2 una adaptación del ejemplo anterior pero usando el
proveedor de OLEDB.
Recuperar imágenes desde una base de datos mediante los objetos
Command y DataReader
Una vez que hemos grabado varias imágenes en la base de datos,
vamos a efectuar la operación inversa, por lo que tras introducir
un número identificador de foto en la caja de texto del formulario,
pulsaremos su botón "Recuperar imagen de BD", que ejecutará el
código fuente 3.
En esta ocasión, el proceso ejecutado consiste en crear una
conexión y comando, cuya sentencia SQL contenga el identificador de
fila a recuperar, que habremos introducido en el formulario.
Observe el lector que al crear el comando, lo configuramos
utilizando la enumeración CommandBehavior para optimizar su
ejecución, de modo que devuelva un DataReader compuesto por una
única fila.
Una vez obtenido el DataReader, su campo Foto contendrá una serie
de bytes que representan los datos binarios de la imagen, y que
volcaremos en un array, también de tipo byte.
A partir de aquí podemos tratar la imagen como necesitemos; en este
ejemplo realizamos dos operaciones: en primer lugar pasamos el
array a un objeto FileStream, creando de esta manera un archivo
gráfico, ayudándonos de un cuadro de diálogo SaveFileDialog; en
segundo lugar creamos un objeto MemoryStream con el array, y a
partir del stream, creamos un objeto Bitmap, que asignamos al
control PictureBox del formulario para visualizar la imagen; si no
necesitamos transferir la imagen a un archivo, es mucho más
efectivo tratarla en memoria con el stream disponible a tal efecto.
La siguiente figura muestra este formulario en ejecución.
Llegados a este punto, podemos dar por concluida la fase de gestión
de imágenes en ADO.NET desde un enfoque conectado a la fuente de
datos; es hora pues de abordar una técnica diferente: el modo
desconectado.
Manipulando imágenes mediante un DataSet
DataSet representa el objeto central sobre el cual se sustenta la
arquitectura de gestión de datos desconectados en ADO.NET. La forma
de tratar las imágenes es igual que con los objetos Command y
DataReader, salvando claro está, las diferencias entre estos tipos
de objeto en función de su natural operativa (conectada o
desconectada).
El ejemplo con DataSet que acompañamos a continuación, además de
las operaciones de edición de imágenes, contiene la capacidad de
navegación por las filas de la tabla, constituyendo un factor
adicional que le diferencia del anterior ejemplo basado en
comandos; el resultado conseguido consiste en un visualizador de
imágenes, que aunque sencillo, cumple correctamente su
misión.
El formulario de ejemplo encargado de tratar con el DataSet es
frmDesconectado, que podemos ver en la figura 4.
En este artículo asumimos que el lector conoce la mecánica básica
para crear y llenar un DataSet con datos, por lo que obviaremos
aquellas líneas de código pertenecientes a este formulario
encargadas de esta tarea y de la navegación de registros,
centrándonos exclusivamente en los procesos que competen al
tratamiento de imágenes con este objeto. Debemos aclarar también,
que para facilitar la programación de las operaciones en el
formulario, algunos elementos tales como el DataSet, las variables
de control de la posición de fila, filas totales, etc., han sido
declarados con ámbito a nivel de la clase.
Tras poblar el DataSet con el contenido de la tabla Fotos en el
evento Load del formulario, llamaremos al método CargarDatos, que
toma la fila en la que el DataSet se encuentra posicionado, y la
muestra en los controles del modo que vemos en el código fuente
4.
Como acabamos de comprobar, la operación realizada para obtener la
imagen es la misma que para un DataReader: pasamos el contenido del
campo de la tabla a un array de tipo byte, y utilizamos éste para
crear un stream en memoria; finalmente, creamos un bitmap con el
stream, y asignamos la imagen a un control PictureBox. A partir de
aquí, al pulsar los botones de navegación, se actualizará el número
de fila de la tabla del DataSet a mostrar, volviendo a llamar a
este mismo método para visualizar la imagen, como vemos en la
siguiente figura:
En lo que respecta a la grabación de un archivo gráfico empleando
este DataSet, pulsaremos el botón "Nuevo" para habilitar los
controles correspondientes del formulario. Después de teclear el
número identificador y la ruta del archivo, pulsaremos el botón
"Grabar", que ejecutará el siguiente código fuente:
La manera de grabar el archivo gráfico sobre la base de datos es,
en esencia, la misma que en el ejemplo con objetos conectados, ya
que abrimos el archivo con un FileStream, y volcamos éste a un
array de bytes; a continuación creamos un nuevo objeto DataRow y
asignamos los valores para sus campos, añadiendo este objeto a la
colección de filas de la tabla del DataSet. Finalmente
actualizaremos la base de datos física con el DataSet y
rellenaremos este último de nuevo, para que el orden de filas sea
el correcto durante la navegación de registros por el
formulario.
Y llegamos al final
En efecto estimado lector, por mucho que nos pese toda narración
tiene su fin, y nuestro artículo no iba a ser menos, esperamos que
el tema tratado en esta ocasión le sea de utilidad si se encuentra
ante la tesitura de crear un mantenimiento de datos en el que
intervengan imágenes.