Está en la página 1de 68

Captulo 4.

Programacin en ADO
4.1. Las bases de datos relacionales (RDBMS)
Una base de datos relacional es un conjunto de tablas que mantienen algn tipo de relacin entre ellas. Una base de datos es un conjunto de informacin que se organiza por mediacin de una estructura lgica. La informacin puede ser guardada en un fichero o en varios ficheros, tambin conocidos como tablas. 4.1.1. Las Tablas Dentro de la base de datos, las tablas son el objeto ms bsico e importante. El rendimiento de la base de datos y por extensin de las aplicaciones que accedan a ellas, dependen en gran medida del diseo de las tablas. Una tabla es una matriz bidimensional, que contiene filas y columnas. Las filas son los registros de la tabla y las columnas son los campos de la tabla. A las filas tambin se les conoce con el nombre de tuplas o registros. Un campo es una forma de decir que tipo de informacin contendrn las filas en esa posicin. Por ejemplo una columna llamada Nombre, podr contener el nombre de las personas.
Campos de la Tabla: Nombre, Apellidos y DNI

Estructura de la Tabla:
Nombre Antonio Luis Mara Apellidos Prez Lpez Martnez Snchez Garca Hernndez DNI 7328714K 3858582L 4738270X
Filas de la tabla o registros que contienen los datos organizados por las columnas de la tabla, tambin llamadas campos.

Las tablas de datos se pueden relacionar entre s, generando bases de datos relacionales. En el mercado, existen varios sistemas administradores de bases de datos relacionales, como son: Access, SQL Server, MySQL, Oracle, SysBase, Informix, etc. Una tabla suele ser la representacin de clases de objetos fsicos, como por ejemplo, clientes, empleados, facturas, etc. Cada objeto fsico, como un empleado, tiene su registro correspondiente en la tabla.

En esta imagen podemos ver una tabla de empleados con 7 campos.


Captulo 4. Programacin en ADO. Pgina 1

En este caso el campo ID de la tabla, es el campo Clave de la tabla, lo que significa que la tabla es accedida directamente por este campo y permite identificar a ese registro de forma inequvoca sobre cualquier otro registro de la tabla. El campo clave, ID de la tabla Empleados, permite que esta tabla se relacione con otras tablas de la base de datos, como por ejemplo Pedidos, o Privilegios de Empleados. 4.1.2. Las Claves. Existen distintos tipos de clave: Clave nica. Cada tabla puede tener uno o ms campos cuyos valores identifican de forma nica cada registro de dicha tabla, es decir, no pueden existir dos o ms registros diferentes cuyos valores en dichos campos sean idnticos. Este conjunto de campos se llama clave nica. Pueden existir varias claves nicas en una determinada tabla, y a cada una de stas suele llamrsele candidata a clave primaria. Clave primaria. Una clave primaria es una clave nica elegida entre todas las candidatas que define unvocamente a todos los dems atributos de la tabla, para especificar los datos que sern relacionados con las dems tablas. La forma de hacer esto es por medio de claves forneas. Slo puede existir una clave primaria por tabla y ningn campo de dicha clave puede contener valores NULL. Clave externa o fornea. Una clave externa es una referencia a una clave en otra tabla. Las claves externas no necesitan ser claves nicas en la tabla donde estn y s a donde estn referenciadas. Por ejemplo, el cdigo de departamento puede ser una clave externa en la tabla de empleados, obviamente se permite que haya varios empleados en un mismo departamento, pero existir slo un departamento. El objetivo en el diseo de las tablas es evitar la redundancia de los datos, es decir, imaginemos que la direccin del empleado apareciera en ms de una tabla como contenido de un campo. En ese caso, hablamos de redundancia. La redundancia ralentiza la ejecucin de la base de datos, as como genera un exceso de espacio en disco que, con una buena planificacin en las relaciones entre tablas, se podra solucionar. La forma de evitarlo, es crear por ejemplo una tabla con las direcciones de los empleados y usar un campo clave para acceder a ellas. A parte de la clave primaria, podemos definir claves secundarias, que son claves que se utilizan para poder relacionar de una forma ms precisa las tablas. El hecho de hace coincidir una clave secundaria con un valor de clave primaria, se denomina operacin de bsqueda.
Captulo 4. Programacin en ADO. Pgina 2

Las claves primarias suelen ser campos numricos de autoincremento, llamados Autonumricos en Access e Identify en SQL Server. El uso de valores de campos alfabticos como Clave Principal, ha cado en desuso por parte de los diseadores de bases de datos, debido a su lentitud a la hora de realizar operaciones, pero muchas veces es la mejor o nica opcin. Otro mtodo para generar claves exclusivas es utilizar los identificadores globales exclusivos (GUID). Los GUID son nmeros binarios de 16 bytes cuya exclusividad est garantizada local y universalmente; ningn otro ordenador del mundo puede duplicar un GUID. En SQL Server, uniqueidentifier, es un tipo GUID. Por mediacin de las claves podemos crear relaciones que agilizan las consultas y el acceso a los datos. Una tabla puede tener mltiples relaciones con otras tablas. El tipo de relaciones posibles que se pueden implementar en una base de datos son: Uno a Varios. Representa una relacin entre un solo valor de clave principal (uno) y varias instancias del mismo valor en el campo clave secundario (varios). Las relaciones uno a varios son representadas como un 1 y el smbolo de infinito. Uno a Uno. Son relaciones en las que se conectan los valores de clave principal de dos tablas. Es una relacin poco usada. Varios a Varios. Son relaciones que requieren tres tablas, una de las cuales es la tabla de vinculacin. La tabla de vinculacin debe de tener dos claves secundarias, cada una con una relacin varios a uno con una clave principal de dos tablas relaciones. Estas relaciones tambin se las conoce como indirectas. Ejemplo de Esquema de las relaciones de una Base de Datos:

Captulo 4. Programacin en ADO.

Pgina 3

4.1.3. Las Clave ndice Las claves ndices surgen con la necesidad de tener un acceso ms rpido a los datos. Los ndices pueden ser creados con cualquier combinacin de campos de una tabla. Las consultas que filtran registros por medio de estos campos, pueden encontrar los registros de forma no secuencial usando la clave ndice. Los ndices generalmente no se consideran parte de la base de datos, pues son un detalle agregado. Un ndice es un objeto que existe slo dentro del marco de una determinada tabla o vista. Un ndice funciona de una forma anloga al ndice de un libro, donde existe algn tipo de valor (clave) de bsqueda que se organiza de una forma y, posteriormente, se proporciona otra clave con la que podremos encontrar la informacin que estamos buscando. Un ndice nos proporciona mtodos para agilizar la bsqueda de nuestra informacin. Los ndices se organizan en dos categoras: Agrupados. Slo puede haber un ndice agrupado por tabla. Tener un ndice agrupado significa que la tabla que se basa en dicho ndice agrupado est ordenada fsicamente segn dicho ndice. En un libro, el ndice agrupado sera el nmero de pgina; la informacin se guarda en el orden de los nmeros de pgina. No Agrupados. Se pueden tener tantos ndices no agrupados como se quiera en la tabla. Este tipo de ndice apunta a otro valor que nos va a permitir encontrar el dato. En nuestro ejemplo, seran los conceptos detallados en el ndice del libro. 4.1.4. Los Desencadenadores Un desencadenador es un objeto que existe slo dentro del marco de una tabla. Los desencadenadores son elementos de cdigo lgico que se ejecutan automticamente cuando se producen diversos eventos en nuestra tabla, como inserciones, actualizaciones o eliminaciones. Los desencadenadores se pueden utilizar para diversas cosas, pero principalmente se utilizan para copiar datos a medida que se introducen o para comprobar la actualizacin para asegurarse de que se satisfacen algunos criterios. 4.1.5. Restricciones Una restriccin es otro objeto que slo existe dentro de los confines de una tabla. Las restricciones confinan los datos de nuestra tabla para satisfacer determinadas condiciones. En cierto sentido, las restricciones compiten con los desencadenadores como posibles soluciones para solucionar problemas de integridad de datos. Sin embargo, no son iguales; cada elemento tiene sus propias ventajas inconfundibles.
Captulo 4. Programacin en ADO. Pgina 4

La tabla es el elemento bsico de la estructura en una base de datos. Evidentemente es el objeto ms importante, pero existen otros que lo complementan y aaden la funcionalidad necesaria para sacar toda la potencia a un sistema de base de datos relacional. 4.1.6. Diagramas Un diagrama es una representacin visual del diseo de la base de datos, incluyendo las diversas tablas, los nombres de columna de cada tabla y las relaciones entre las mismas. Un Diagrama de entidad-relacin (ERD), es un diagrama en el que la base de datos se divide en dos partes: las entidades y las relaciones. 4.1.7. Vistas Una vista es algo parecido a una tabla virtual. Bsicamente se utilizan como una tabla, pero no contienen ningn dato propio. En su lugar, una vista es simplemente una planificacin previa de la asignacin y representacin de los datos guardados en tablas. El plan se guarda en la base de datos en forma de consulta. Esta consulta llama a los datos de algunas columnas (no tienen que ser todas) para su recuperacin por parte de una o ms tablas. Los datos recuperados pueden o no reunir unos criterios especiales (dependiendo de la definicin de la vista) para mostrarse como datos en dicha vista. Podemos usar una vista para incluir la informacin que todo el mundo pueda ver, dado que las aplicaciones normalmente discriminan la informacin en funcin del nivel de acceso del usuario. Asimismo, se puede hacer una vista a la medida para que los usuarios no tengan que realizar bsquedas a travs de informacin innecesaria. 4.1.8. Procedimientos Almacenados Un procedimiento almacenado sigue siendo bsico para las funciones de programacin en SQL Server. Un procedimiento almacenado es una serie de instrucciones Transact-SQL ordenadas e integradas en una sola unidad lgica. Su equivalente en C#, sera un mtodo.

Captulo 4. Programacin en ADO.

Pgina 5

4.2. Uso bsico de Transact-SQL o T-SQL Las bases de datos relacionales usan un lenguaje de programacin conocido como Transact-SQL o T-SQL. T-SQL es un lenguaje estndar para trabajar con bases de datos relacionales y es soportado por la mayora de los sistemas actuales. Los comandos o instrucciones ms habituales en SQL se aplican para: Crear y borrar tablas; Insertar registros en la tabla; actualizar la informacin de los registros en la tabla; borrar un registro o grupo de registros y; seleccionar un registro o conjunto de registros. Estas opciones son realizadas por: Create, Drop, Insert, Update, Delete y Select, respectivamente. 4.2.1. La instruccin SELECT La instruccin Select y las estructuras utilizadas en ella, forman la base de todos los comandos que vamos a ver. Sintaxis: SELECT <lista de columnas> [ FROM <tabla o tablas de origen> ] [WHERE <condicin restrictiva>] [GROUP BY <nombre de columna o expresin que utiliza una columna en la lista de seleccin>] [HAVING <condicin restrictiva basada en los resultados de GROUP BY>] [ORDER BY <lista de columnas>] Para empezar vamos a utilizar el ejemplo Northwind suministrado como ejemplo de ayuda por Microsoft. Tanto esta base como Pubs, fueron diseadas para su uso con Access y SQl Server 2000. A partir de la versin 2005 fueron sustituidas por AdventureWorks. Esta ltima base incorpora funcionalidades de SQL Server 2005 muy avanzadas que en ocasiones dificultan el aprendizaje del T-SQL. Por ello, Microsoft mantiene tanto Pubs como Northwind disponibles para SQL Server 2005 y SQL Server 2008. Como primera prueba de Select, usemos la siguiente sintaxis: SELECT * FROM employees El resultado de esta consulta nos dar todas las columnas (*) de la tabla employee y todos los registros que contenga. Si no hemos manipulado el ejemplo, deber de dar como resultado nueve filas. El uso del * le indica al intrprete de SQL que nos devuelva todos los campos (columnas) que tiene la tabla employees. La clausula FROM, le indica cual es la tabla o tablas de las que tiene que tomar los datos.

Captulo 4. Programacin en ADO.

Pgina 6

Si quisiramos obtener slo las columnas de nombre y apellidos, utilizaremos la sentencia SELECT con esta estructura: SELECT lastname, firstname FROM employees Como podemos ver los campos que queremos obtener van separados por coma. Si ejecutamos la consulta veremos que siguen saliendo 9 filas pero slo nos da las columnas LastName y FirstName. Una cosa a tener en cuenta con el SQL es que no distingue a la hora de escribir las sentencias entre maysculas y minsculas. Por ejemplo: SELECT y Select son la misma cosa para el intrprete de SQL. Hay algunos editores de SQL que ponen las instrucciones en maysculas y otros en minsculas, pero slo tiene un efecto visual. La clusula Where. Esta clusula nos permite agregar a la consulta condiciones sobre los resultados deseados. Hasta ahora, slo hemos obtenido el total de las filas que contiene la tabla. Por ejemplo, supongamos que queremos los empleados SELECT lastname, firstname FROM employees WHERE Title = 'Sales Representative' Como observaremos, ya no son 9 las filas afectadas, ahora son slo seis. Hemos utilizado la columna Title, para restringir las filas que queramos consultar. A continuacin vemos una tabla con los operadores que podemos utilizar en una clusula Where: Operador < > < > y = <= >= = > <! AND, OR, NOT Uso Menor que Mayor que Distinto de Menor Igual que Mayor Igual que Igual que No mayor que No menor que Valores lgicos booleanos estndar. Se pueden utilizar para combinar mltiples condiciones. NOT se evala primero, AND, despus y por ltimo OR. Podemos cambiar el orden de evaluacin colocando parntesis. La comparacin es verdadera si valor se encuentra comprendido entre los valores facilitados. Utiliza los caracteres % y _ como
Pgina 7

BETWEEN

LIKE
Captulo 4. Programacin en ADO.

IN

ALL, ANY, SOME

EXISTS.

comodines. % indica que un valor de cualquier longitud puede reemplazar al %. _ indica que cualquier carcter puede reemplazar a este carcter. [ ] los corchetes indican que cualquier carcter nico dentro de dichos corchetes es correcto, por ejemplo [az]. En este caso, cualquier carcter entre la a y la z es vlido. ^ este carcter funciona como operador NOT indicando que se excluye el siguiente carcter. Devuelve true si el valor que se encuentra a la izquierda de la palabra clave IN se corresponde con cualquier valor de la lista proporcionada tras la palabra clave IN. Normalmente se utiliza en subconsultas. Devuelve true si alguno o todos los valores (dependiendo del elegido) en una subconsulta coinciden con la condicin del operador de comparacin (por ejemplo, <, >, = , >=). ALL indica que el valor tiene corresponderse con todos los valores del conjunto. ANY y SOME son equivalentes funcionales y se evaluarn como true si la expresin se corresponde con cualquier valor del conjunto. Devuelve true si la subconsulta devuelve, al menos, una fila.

Por ejemplo, si quisiramos obtener todos los trabajadores cuya alta en la empresa sea entre 1990 y 1993, la consulta sera: SELECT * FROM employees WHERE hiredate BETWEEN '01-01-1990' AND '01-01-1993' El resultado de esta consulta es de tres filas afectadas. La clusula Order By. En las consultas, puede ocurrir que los datos salgan ordenados alfabticamente. Eso podra ser por casualidad. Si no le indicamos que deseamos ordenar los resultados de la consulta de una forma especfica, se obtendrn los datos en la forma menos costosa para recopilar los datos.

Captulo 4. Programacin en ADO.

Pgina 8

Normalmente se basar, bien en el orden fsico de una tabla o bien en uno de los ndices de SQL Server utilizados para buscar los datos. Order By nos permite seleccionar cual es la columna o columnas que sern la base para ordenar los resultados de la consulta. Ejemplo: SELECT * FROM employees WHERE hiredate BETWEEN '01-01-1990' AND '01-01-1993' ORDER BY FirstName Si observamos esta consulta respecto a la anterior, observaremos que en esta ocasin los datos son mostrados en el orden alfabtico de la columna FirstName. Si no queremos, no es necesario colocar una clusula Where, pero en caso de ir, tiene que ir antes del Order By. Por ejemplo: SELECT * FROM employees ORDER BY FirstName, LastName En este ejemplo seleccionamos todos los empleados, pero mostrados en orden alfabtico por el FirstName y despus por el LastName. Evidentemente podemos aplicar ordenaciones por campos numricos, incluso cambiar si es ascendente o descendente, por ejemplo: SELECT * FROM products WHERE unitsonorder > 0 AND unitsinstock < 10 ORDER BY unitsonorder DESC Este ejemplo, muestra todos los valores cuyo campo unitsonorder sea superior a cero y su campo unitsinstock se inferior a 10. El resultado es ordenador de forma descendente por unitsonorder. De forma implcita un Order By ejecuta la ordenacin de forma ascendente. Podramos indicrselo de forma explcita, colocando ASC. DESC le indica que la ordenacin sea descendente. SELECT * FROM employees WHERE hiredate BETWEEN '01-01-1990' AND '01-01-1993' ORDER BY hiredate, employeeid DESC Nos muestra la consulta ordenada primeramente por la columna, hiredate, de forma descendente y si hubiera coincidencias, utilice la columna employeeid para ordenar. La clusula Group By. Esta clusula se utiliza para agregar informacin. Si realizamos la siguiente consulta, nos devolver todos los artculos que se han pedido dentro de un conjunto concreto de pedidos.

Captulo 4. Programacin en ADO.

Pgina 9

SELECT orderid, quantity FROM [order details] WHERE orderid BETWEEN 11000 AND 11002 Como podemos observar al ejecutar la consulta, nos ha devuelto 11 filas de la tabla order details, que son las que coinciden con el orderid entre el 11000 y el 11002. Si observamos detenidamente, podemos ver que por ejemplo de la orden 11000 hay tres filas, lo que significa que aunque en realidad la orden era slo para ver tres pedidos, la consulta nos ha devuelto el detalle individual de cada pedido. Cuatro filas para el pedido 11001 y 4 filas para el pedido 11002. Si quisiramos ver slo los pedidos, sin l detalle, podemos usar Group By y, adems, aplicarle un agregado a la sentencia select, que en este caso, es la funcin de la suma sum(). Si ejecutamos la orden como sigue, obtendremos tres filas con el total del valor del pedido. SELECT orderid, SUM(quantity) FROM [order details] WHERE orderid BETWEEN 11000 AND 11002 GROUP BY orderid Sum() realiza la suma, pero si lo que necesitamos es el nmero de filas, podemos utilizar la funcin Count(). Ejemplo: SELECT customerid, employeeid, COUNT(*) FROM orders WHERE customerid BETWEEN 'A' AND 'B' GROUP BY customerid, employeeid Esta consulta obtiene todos los pedidos por clientes y por empleados que hicieron esos pedidos. El resultado es que para cada cliente nos dice el nmero de pedidos que gestiono los empleados. Al usar Group By, toda columna en la lista de seleccin tienen que formar parte de Group By o tiene que ser un agregado. Agregados. Un agregado es una funcin que acta sobre grupos de datos. En el ejemplo que usamos el agregado SUM(), lo que obtuvimos fue la suma de la columna quantity. La suma se calcula y se devuelve sobre la columna seleccionada para cada grupo definido en la clusula GROUP BY(en ese caso, slo OrderId). Hay muchas clases de agregados, pero slo vamos a estudiar las ms usuales: sum, count, avg (media), min, max. AVG. Realiza una media de los valores. Por ejemplo: SELECT orderid, AVG(quantity) FROM [order details] WHERE orderid BETWEEN 11000 AND 11002 GROUP BY orderid Esta instruccin ha calculado el importe medio para cada pedido.

Captulo 4. Programacin en ADO.

Pgina 10

MIN / MAX. Devuelve el valor mnimo o mximo de una agrupacin. Por ejemplo: SELECT orderid, MAX(quantity) FROM [order details] WHERE orderid BETWEEN 11000 AND 11002 GROUP BY orderid COUNT(expression). Hace un recuento de las filas en una consulta. Por ejemplo: SELECT COUNT(*) FROM employees WHERE employeeid = 2 Devuelve el nmero de filas que el campo employeedid es igual a 2. En este caso slo hay una fila. Si por el contrario ejecutamos esta orden: SELECT COUNT(*) FROM employees El resultado ser nueve. Nos ha dado el nmero total de filas que contiene la tabla employees. Supongamos que queremos obtener el recuento para una columna concreta. En ese caso, utilizaremos una sentencia como esta: SELECT COUNT(fax) FROM customers En este caso hemos pedido saber cuntas filas hay en la tabla customers, por el campo fax. El resultado es 69. Pero esta consulta podra hacernos pensar que el resultado es idntico a preguntar por todos, por ejemplo: SELECT COUNT(*) FROM customers Si ejecutamos esta consulta, descubriremos que el resultado es 91. Cmo puede ser?. La respuesta est en los valores null. Si observamos la ventana de mensajes cuando ha realizado la primera consulta, vernos un mensaje con este texto: Advertencia: valor NULL eliminado por el agregado u otra operacin SET. Significa que cualquier valor null sobre un agregado del tipo, count, avg, etc, las columnas que contienen el null, no son consideradas para el clculo y por tanto no cuentan. En una media, el valor no est representado sobre todas las filas, sino que se aplica sobre todas las filas que no contienen null. Por ejemplo, si quisiramos saber cuntas son null, utilizaremos la siguiente sentencia: SELECT COUNT(*) FROM customers WHERE fax IS NULL El resultado de la consulta es 22, que sumadas a las 69 que dio, hacen un total de 91.

Captulo 4. Programacin en ADO.

Pgina 11

Count se puede usar junto con Group By para, por ejemplo, saber que empleados reportan a que director: SELECT reportsto, COUNT(*) FROM employees GROUP BY reportsto Hemos agrupado los resultados por reportsto. Como count es un agregado, en la clusula group by no tiene que ir. La clusula HAVING. La clusula Having se usa si existe una clusula GROUP BY en la consulta. Mientras que la clusula Where se aplica a todas las filas antes de convertirse en grupo, la clusula Having se aplica al valor agregado de dicho grupo. Por ejemplo: SELECT reportsto AS manager, COUNT(*) employees GROUP BY reportsto AS reports FROM

En este ejemplo, la columna reportsto aparece con el nombre de manager y la columna del count, como reports. El resultado puede ser equivoco, ya que presupone que todo el mundo de la empresa depende de dos directores. Y si queremos que slo aparezcan los directores que tienen a su cargo ms de cuatro personas. Si usamos la clusula Where no podemos hacer nada porque Where se ejecuta antes de la agregacin y, por tanto, no podemos devolver filas basndonos en la agregacin. Para resolver este problema tenemos la clusula Having: SELECT reportsto AS manager, COUNT(*) AS reports FROM employees GROUP BY reportsto HAVING COUNT(*) > 4 Por ejemplo, podemos realizar esta otra consulta que nos devolver 830 filas. SELECT orderid, SUM(quantity) AS Total FROM [order details] GROUP BY orderid Si quisiramos filtrar la instruccin para que slo nos de aquellos cuya cantidad es superior a 300, podramos usar una clusula Having como sigue a continuacin: SELECT orderid, SUM(quantity) AS Total FROM [order details] GROUP BY orderid HAVING SUM(quantity) > 300 En este caso solo nos da dos filas afectadas.

Captulo 4. Programacin en ADO.

Pgina 12

Predicados Distinct y ALL Supongamos que deseamos obtener un listado de todos los Id de proveedores de todos los productos que tenemos almacenados. La sentencia podra ser: SELECT supplierid FROM products WHERE unitsinstock > 0 La sentencia nos devuelve toda la lista de productos, repitiendo el id de proveedor cada vez que hay un producto de ese proveedor. La solucin, si slo queremos saber que proveedores tienen productos, pasara por el uso del predicado Distinct. La sentencia podra quedarnos: SELECT DISTINCT supplierid FROM products WHERE unitsinstock > 0 Ahora slo muestra un Id de proveedor aunque aparezca en ms de una fila. Por defecto siempre el predicado es ALL, lo que significa que incluye todas las filas.

4.2.2. La instruccin INSERT Insert es una instruccin de accin. Le indica a SQL Server lo que vamos a hacer con esta instruccin, ya que todo lo que va a continuacin son los detalles de la operacin. Sintaxis: INSERT [intro] <tabla> [(lista_de_columnas)] VALUES (valores_de_datos) La palabra clave intro es de relleno. Su nico propsito es que la instruccin sea ms legible. Es completamente opcional pero su uso es recomendable por el tema de la legibilidad. Despus de intro viene la tabla en la que deseamos insertar los valores: La lista de columnas. Podemos prescindir de una lista explcita (son listas donde se especifica concretamente que columnas recibirn los valores). Es opcional, pero si no suministramos ninguna, tenemos que tener mucho cuidado de asegurarnos que los valores corresponden a las columnas. A continuacin y despus de la palabra VALUES, van los valores de las columnas separados por comas y entre parntesis. Si queremos no establecer un valor en una columna podemos usar la palabra default para que la base de datos ponga uno predeterminado. Tambin y, siempre que este permitido, podemos usar Null. Para el siguiente ejemplo usamos Pubs. Ejemplo de Insert: INSERT INTO stores VALUES ('Prue', 'Prueba Almacen', 'Prueba 15', Madrid, 'MD', '00319')
Captulo 4. Programacin en ADO. Pgina 13

La ejecucin de esa instruccin genera una nueva fila dentro de la tabla stores. Si intentamos repetir la instruccin, nos dar el siguiente error: Infraccin de la restriccin PRIMARY KEY 'UPK_storeid'. No se puede insertar una clave duplicada en el objeto 'dbo.stores'. El motivo es porque la columna stor_id es un campo de Clave Primaria y no admite repeticiones. A pesar de que ha funcionado bien, lo ideal hubiera sido haber suministrado una lista de columnas explcita. Veamos un ejemplo en que facilitamos una lista de columnas: INSERT INTO stores (stor_id, stor_name, city, state, zip) VALUES ('Pru2', 'Prueba Almacen', 'Madrid', 'MD', '00319') En este segundo ejemplo hemos omitido el campo correspondiente a stor_address, pero no importa porque al ser un campo que permite valores null, dar de alta la fila colocando un null en esa columna. El hecho de usar una lista de columnas nos permite especificar qu valores van a qu columnas. Si comprobamos la insercin descubriremos que en la columna que no hemos dado un valor a colocado null. Una columna que acepta valores null permite que en una instruccin Insert se omitan valores para esa columna. Si una columna no permite null, entonces hay que proporcionar una de estas tres condiciones: La columna se ha definido con un valor predeterminado. Un valor predeterminado es un valor constante que se inserta si no se proporciona ningn valor. La columna est definida para admitir valores generados por el sistema. El valor ms comn de este tipo es un valor Identity. Suministramos un valor para la columna. Nota: Existe un procedimiento almacenado en el sistema que permite ver las propiedades de cualquier objeto de la base de datos en la ventana de consultas: sp_help. Para poder ejecutarlo usaremos la siguiente sintaxis: EXEC sp_help sales

Instruccin Insert Into - Select La instruccin Insert que hemos desarrollado permite insertar slo una fila cada vez. Si necesitamos insertar ms de una fila, entonces debemos de usar la instruccin Select en la sintaxis de la instruccin Insert.

Captulo 4. Programacin en ADO.

Pgina 14

Sintaxis: INSERT INTO <nombre_tabla> [<lista_columnas>] <instruccin SELECT> En una instruccin Insert con Select podemos seleccionar los registros a insertar de distintos orgenes: Otra tabla de nuestra base de datos. Una base de datos totalmente diferente en el mismo servidor. Una consulta de otro SQL Server u otros datos. De la misma tabla (normalmente para ordenar clculos matemticos). En el siguiente ejemplo creamos una tabla en memoria y le asignamos por mediacin de la sentencia Insert con Select datos de la base de datos Northwind, tabla orders: USE northwind DECLARE @mitabla Table ( OrderId int, CustomerId char(5) ) INSERT INTO @mitabla SELECT OrderId, CustomerId northwind.dbo.Orders WHERE OrderId BETWEEN 10240 AND 10250 SELECT * FROM @mitabla Ejemplo: Uso InsertarTablaMemoria

FROM

4.2.3. La instruccin UPDATE La instruccin Update permite modificar valores de registros que se encuentran en la tabla. Una instruccin Update puede crearse a partir de varias tablas pero slo puede realizar cambios a una. Esto quiere decir que podemos crear una condicin, o recuperar valores de cualquier nmero de tablas distintas, pero slo una tabla a la vez puede estar sujeta a la accin de la actualizacin. Sintaxis: UPDATE <nombre de tabla> SET <columna> = <valor> [, <columna> = <valor> ] [FROM <tabla-s de origen>] [WHERE <condicin restrictiva>]

Captulo 4. Programacin en ADO.

Pgina 15

Tomando la base de datos Pubs, supongamos que queremos actualizar el valor de la columna city, de la tabla stores: UPDATE stores SET city = 'Toledo' WHERE stor_id = 'prue' En esta sentencia hemos cambiado el valor de una sola columna, pero podemos cambiar varias columnas a la vez: UPDATE stores SET city = 'Toledo', state = 'CM' WHERE stor_id = 'prue' Pero los cambios pueden afectar a grupos de registros que cumplan una condicin. Por ejemplo vamos a probar una sentencia Select en la que aplicamos el operador Like: SELECT title_id, price FROM titles WHERE title_id LIKE 'BU%' Al ejecutarla vemos que muestra todos los ttulos que empiezan por BU, sin tener en consideracin ms valores dentro de la cadena. Ahora, viendo el resultado, vamos a aplicar una sentencia Update usando el operador Like para cambiar el precio en los libros que coincidan en la consulta: UPDATE titles SET price = price * 1.1 WHERE title_id LIKE 'BU%' Este ejemplo actualiza los valores de la columna precio, incrementando su valor en un 10%, para todos aquellos libros cuyo campo title_id empiecen por BU. Si volvemos a realizar un Select para ver los resultados, observaremos que cada elemento tiene un nmero variable de decimales. Si quisiramos ajustarlo podemos utilizar una funcin de redondeo. UPDATE titles SET price = ROUND(price * 1.1, 2) WHERE title_id LIKE 'BU%'

4.2.4. La instruccin DELETE La instruccin Delete puede que sea de las que hemos visto hasta ahora de las ms sencillas. No incorpora listas de campos, ya que la posibilidad de borrar es a toda la fila y no a una parte de ella. Sintaxis: DELETE <nombre_tabla> [WHERE <condicin_busqueda>] La clusula Where funciona como hemos venido viendo hasta ahora.

Captulo 4. Programacin en ADO.

Pgina 16

Ejemplo: DELETE stores WHERE stor_id = 'prue' Puede ocurrir que en una operacin Delete aparezca un mensaje como el que aparece a continuacin: Instruccin DELETE en conflicto con la restriccin REFERENCE "FK__sales__stor_id__0AD2A005". El conflicto ha aparecido en la base de datos "pubs", tabla "dbo.sales", column 'stor_id'. Este error se produce porque la tabla stores, tiene su campo clave y est relacionado con la tabla sales, columna stor_id. No nos deja borrar la fila porque si lo hace dejara hurfanos registros en otras tablas. Se perdera la integridad referencial. Para poder borrar el registro, es necesario borrar antes los que dependen de l.

4.2.5. La clusula JOIN En las bases de datos relacionales las tablas grandes se suelen dividir en tablas ms pequeas para evitar la redundancia de datos, ahorrar espacio, mejorar el rendimiento y aumentar la integridad de datos. Cuando una base de datos est normalizada es cuando se han aplicado tcnicas de diseo que permiten tener las tablas grandes en tablas ms pequeas. Esto hace que al final los datos se tengan que tomar de ms de una tabla. El proceso de combinacin de tablas en un conjunto de resultados requiere el uso de la clusula JOIN, que incluye: INNER JOIN OUTER JOIN (tanto LEFT como RIGHT) FULL JOIN CROSS JOIN

Usamos JOIN para combinar la informacin de dos o ms tablas en un solo conjunto de resultados. El conjunto de resultados puede ser tratado como una tabla virtual. Tiene columnas, tipos de datos y valores. A JOIN hay que indicarle como queremos que se una la informacin. En el siguiente esquema podemos ver un ejemplo del uso de JOIN

Captulo 4. Programacin en ADO.

Pgina 17

Tabla Modelos
IDCoche 1 2 3 4 Modelo Coche Xara Picaso Megant C5 Ibiza IDFabricante 1 3 1 2

Campo Clave
El campo IDFabricante Relaciona ambas tablas.

Tabla Fabricantes
IDFabricante 1 2 3 Nombre Fabricante Citroen Seat Renault

Tabla Resultado de aplicar una sentencia JOIN


IDFabricante 1 1 Nombre Fabricante Citroen Citroen IDCoche 1 3 Modelo Coche Xara Picaso C5

Como podemos ver la tabla Fabricantes tiene un campo clave llamado IdFabricante que vincula esta tabla con la tabla Modelos. Al usar una sentencia JOIN en una consulta con ambas tablas, obtenemos una tabla de resultados, en la que se unen los registros que coinciden con el campo, IDFabricante en la tabla modelos, con el registro seleccionado de la tabla fabricante, cuyo IDFabricante sea el mismo. En el ejemplo obtenemos los modelos para Citroen. Sintaxis: SELECT <lista de seleccin> FROM <primera tabla> <tipo_de-unin> <segunda tabla> [ON <condicin_de_unin>]

Captulo 4. Programacin en ADO.

Pgina 18

INNER JOIN Esta clusula es la ms conocida de JOIN. Empareja los registros basndose en uno o ms campos comunes, como la mayora de clusulas JOIN, pero INNER JOIN devuelve slo los registros que se corresponden con el campo (o campos) que hayamos especificado en JOIN. En el siguiente ejemplo usamos la base de datos Northwind: SELECT * FROM Products INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID Si nos fijamos en el resultado de la consulta, descubriremos que la columna SupplierID (ID Proveedor) aparece dos veces, pero no se sabe qu columna proviene de que tabla. Adems, podemos observar que se han devuelto todas las columnas de ambas tablas y, por ltimo, las primeras columnas son de la primera tabla. Aparecen todas las columnas porque en el Select le hemos indicado con el * que ponga todas. Esto no es muy recomendable en una sentencia Join, debido a los problemas de lectura que acarrea. Supongamos que lo que queramos era el nombre del proveedor, de todos nuestros productos, entonces la consulta podra quedarnos as: Vamos a realizar la misma consulta pero esta vez usando un alias para el nombre de la tabla. Para crear un alias, justo detrs del nombre de la tabla va el alias: SELECT tp.*, companyname FROM Products tp INNER JOIN Suppliers ON tp.SupplierID = Suppliers.SupplierID Si usamos un alias, hay que usar el alias en todas las apariciones del nombre de la tabla en la consulta. Supongamos otro ejemplo: SELECT tp.productid, ts.supplierid, tp.productname, ts.companyname FROM Products tp INNER JOIN Suppliers ts ON tp.SupplierID = ts.SupplierID En este ejemplo, usamos el alas tp para la tabla productos y el alias ts para la tabla suppliers. En el select hemos especificado que columnas y en qu orden queremos que aparezcan en la tabla de respuesta. Un JOIN del tipo INNER JOIN es una unin exclusiva, es decir, excluye todos los registros que no tienen un valor en ambas tablas. Vamos a ver otro ejemplo. Imaginemos que nos piden cuales son los clientes que nos han hecho algn pedido. Para ello necesitamos la tabla
Captulo 4. Programacin en ADO. Pgina 19

Customers(Clientes) y Orders(Pedidos). El campo CustomerID de la tabla Customers, es la relacin existente entre ambas tablas. Para que un pedido sea vlido necesitamos dar de alta el ID del cliente en el pedido. Por tanto, la sentencia podra ser: SELECT DISTINCT tc.customerid, tc.companyname FROM Customers tc INNER JOIN Orders tod ON tc.customerid = tod.customerid Hemos usado la palabra clave DISTINCT porque slo queremos conocer que clientes han hecho algn pedido, no todos los pedidos. Probemos un ejemplo que combina dos JOIN sobre la base de datos Pubs, utilizando tres tablas. Buscamos una consulta que devuelva todos los autores que han escrito libros y los ttulos de dichos libros. Lo primeo que podemos observar es que en la tabla authors, tenemos el autor y en la tabla titles, los ttulos. En principio no tenemos nada que los una, pero hay una tabla llamada titleauthor, que es una tabla que resuelve las relaciones de varios a varios. Tambin se las conoce como tablas unin o asociadas. SELECT ta.au_lname + ', ' + ta.au_fname AS "Autor", tl.title FROM authors ta INNER JOIN titleauthor tt ON ta.au_id = tt.au_id INNER JOIN titles tl ON tl.title_id = tt.title_id El operador + utilizado en el Select, permite ver el contenido del campo Lname y Fname en uno solo. La coma que aparece entre los dos es por un tema visual, pudindose colocar cualquier smbolo o carcter. Como en cualquier lenguaje que maneje string, el operador + concatena los string en uno slo. Esta concatenacin deja en una columna a la que llamamos Autor, el contenido de los dos campos separados por una coma. OUTER JOIN Este tipo de clusula es ms bien una excepcin, no una regla y no porque no tenga su uso, sino por lo siguiente: Normalmente deseamos la exclusividad proporcionada por INNER-JOIN. A veces es un desconocimiento por parte de los programadores. Normalmente existen otros mtodos para conseguir lo mismo. Una clusula INNER JOIN es exclusiva, mientras que OUTER JOIN y FULL JOIN son inclusivas. Pueden resultar muy tiles desde el punto de vista del rendimiento cuando se utilizan en lugar de subconsultas anidadas. JOIN tiene lados (uno izquierdo y otro derecho). La primera tabla con nombre se considera que est a la izquierda y la segunda que est a la

Captulo 4. Programacin en ADO.

Pgina 20

derecha. INNER JOIN trata a los dos lados de la misma forma. En las clusulas OUTER JOIN, es muy importante saber cul es nuestro izquierdo y derecho. El tema es que la tabla que aparece antes del OUTER JOIN es la izquierda y la que aparece a continuacin es la derecha. Esto es muy importante cuando especificamos con Left o Right. Si decimos LEFT OUTER JOIN le estamos diciendo agrega toda la informacin de la tabla que est a la izquierda y, con RIGHT OUTER JOIN, le estamos diciendo que tome toda la informacin de la tabla que est a la derecha. Supongamos que necesitamos saber cules son nuestros descuentos, la cantidad total de cada uno y las tiendas que los utilizan. Si examinamos nuestra base de datos pubs, podremos ver que tenemos la siguiente tabla sobre descuentos (discounts) y tiendas (stores). Podemos unir estas dos tablas basndonos en el ID de tienda, stor_id. Si lo hicisemos con INNER JOIN, la consulta resultara: SELECT discounttype, discount, ts.stor_name FROM discounts td JOIN stores ts ON td.stor_id = ts.stor_id El resultado sera de slo una fila. Buscamos los descuentos que tenemos, los que existen actualmente. Pero esta consulta slo nos proporciona los descuentos para los que existen tiendas correspondientes. Buscamos todos los descuentos y las tiendas donde se aplican: SELECT discounttype, discount, ts.stor_name FROM discounts td LEFT OUTER JOIN stores ts ON td.stor_id = ts.stor_id Podemos comprobar que nos ha devuelto todas las filas de discounts. Cuando se ejecuta, en la tabla de la derecha si una fila de la tabla discounts no tiene una coincidencia con la tabla stores, muestra un null. En nuestro caso de los tres registros que salen, dos son null y el tercero es el nombre de una tienda. Qu ocurre si cambiamos el LEFT por RIGHT: SELECT discounttype, discount, ts.stor_name FROM discounts td RIGHT OUTER JOIN stores ts ON td.stor_id = ts.stor_id Aunque parece un cambio pequeo cambia totalmente la salida de la consulta. Una de las ventajas que nos puede ofrecer OUTER JOIN es la posibilidad de encontrar registros hurfanos o no coincidentes. Siguiendo con el ejemplo anterior, veamos como lo podemos aplicar para buscar tiendas que no tengan asignados ningn tipo de descuento.

Captulo 4. Programacin en ADO.

Pgina 21

Una clusula OUTER JOIN nos devuelve un valor null en las columnas basadas en los descuentos siempre que no exista una correspondencia. Ejemplo: SELECT ts.stor_name AS 'Nombre de la Tienda' FROM discounts td RIGHT OUTER JOIN stores ts ON td.stor_id = ts.stor_id WHERE td.stor_id IS NULL El resultado de esta consulta son cinco filas afectadas donde la tienda no tiene asignado ningn tipo de descuento. Si analizamos la consulta comprobamos lo siguiente: Si la columna stores.stor_id tiene un valor distinto de NULL, entonces, segn el operador ON de la clusula JOIN, si existen registros en discounts, entonces discounts.stor_id tambin tiene que tener el mismo valor que stores.stor_id (ON td.stor_id = s.stor_id) Si la columna stores.stor_id tiene un valor distinto de NULL, entonces, segn el operador ON de la clusula JOIN, si no existe ningn registro en discounts, entonces discounts.stor_id se devolver como NULL. Si stores.stor_id tiene un valor NULL y discounts.stor_id tambin tiene un valor NULL, no existira ninguna unin y discounts.stor_id devolver NULL porque no existe ningn valor coincidente. Un valor NULL nunca es igual a otro valor NULL. Nota: A partir de la versin SQL server 7.0, un valor null nunca es igual a otro valor null. Pero puede ocurrir que mantenga el servidor de SQL compatibilidad con la versin 6.5, teniendo desactivados los ANSI_NULLS a travs de las opciones de servidor o de una instruccin SET. El tener desactivado los ANSI_NULLS es una infraccin al estndar ANSI y ya no es compatible con la configuracin actual de SQL Server. FULL JOIN Esta clusula tambin es conocida como FULL OUTER JOIN, hace corresponder los datos de ambos lados de JOIN, independientemente del lado JOIN que sea. Aunque al principio parece una muy buena eleccin, luego casi nunca se usa. Con FULL JOIN podremos obtener todos los registros coincidentes basndonos en el campo (o campos) de JOIN. Tambin podremos obtener cualquier registro existente en la parte de la izquierda, devolvindose todas las columnas con valores NULL de la parte derecha. Pero tambin podemos obtener cualquier registro existente slo en la parte derecha, devolviendo los valores NULL de las columnas de la parte izquierda. Usamos la base de datos Pubs. Ejemplo:
Captulo 4. Programacin en ADO. Pgina 22

SELECT ta.au_fname, ta.au_lname, tp.pub_name FROM authors ta FULL OUTER JOIN publishers tp ON ta.city = tp.city ORDER BY tp.pub_name En este ejemplo podemos ver que ha obtenido los registros coincidentes de ambos lados, colocando NULL cuando en la otra tabla no haba valores. CROSS JOIN Es una clusula bastante extraa en su funcionamiento y su uso bastante restringido. Se sabe que para aplicaciones matemticas, se suele aplicar, ya que lo que hace en realidad es generar tablas cartesianas. No dispone del operador ON, a diferencia de las otras instrucciones JOIN. Vemos un ejemplo de su aplicacin: SELECT a.au_fname, a.au_lname, p.pub_name FROM authors a CROSS JOIN publishers p Como podemos ver en los resultados, obtiene todas las filas de authors, a las que le aade el equivalente de publishers, y todas las filas de publishers a las que le aade el equivalente de authors. Es decir, multiplicando el nmero de registros de una tabla por otra, sabremos cuantos registros obtendr la consulta. 4.2.6. Create Create se aplica tanto a bases de datos como a tablas. El comando Create soporta dos variantes en funcin de si creamos una Base de Datos, o creamos una tabla dentro de la Base de Datos. Crear la Base de Datos: CREATE DATABASE <nombrebasedatos> Crear la tabla: CREATE TABLE [nombrebasedatos] nombretabla(<nombrecolumna> <tipodatos>) Estructura de los tipos de datos:
Tipo SQL Integer Real Float Char Varchar Binary DATE Tipo MS Access Nmero entero largo Nmero simple Nmero doble Texto Texto Binario Fecha/Hora Tipo SQL Framework SqlInt32 SqlSingle SqlDouble SqlString SqlString SqlBinary SqlDateTime de .NET

Captulo 4. Programacin en ADO.

Pgina 23

La diferencia entre el tipo char y Varchar, radica en que el primero (longitud fija) rellena con espacios en blanco la longitud de texto que no se haya usado y el segundo no (longitud variable). Es decir, al final el char debe obligatoriamente grabar un nmero concreto de bytes definidos por su tamao, mientras que Varchar, podr almacenar cualquier valor de bytes que no exceda su longitud mxima. Adems de Varchar, existe en SQL el tipo NVarchar. La diferencia entre ambos radica en que cualquier tipo de datos de SQl que empiece por N, utiliza la codificacin Unicode que implica que para guardar un carcter utiliza dos bytes y por tanto, usa un juego de caracteres mucho mayor. Estos ltimos no se suelen usar a no ser que se use por problemas de compatibilidad con aplicaciones o, por que se usa en idiomas como el japons. Cuando se crean columnas en la base de datos, es posible especificar si una columna concreta es la clave principal de la tabla (PRIMARY KEY). Esto significa que una columna del tipo Primary Key, no puede tener ni valores del tipo null, ni valores duplicados (unique). Este campo ser el punto de acceso de la base de datos a los datos de la tabla. Tambin podemos hacer que un campo, sin ser Primary Key, contenga datos que no sean duplicados. Para ello, usaremos la clusula UNIQUE. Esto hace que sea el propio sistema de bases de datos, el que verifique si existe el dato en la tabla y no sea necesario realizar por programa una comprobacin. La clusula NOT NULL permite que no se pueda dejar un campo sin informacin, es decir, que el campo contenga el valor de tipo null (sin datos). Ejemplo de creacin de una tabla: CREATE TABLE Empleados(Nombre VARCHAR(30) NOT NULL, Apellidos VARCHAR(50) NOT NULL, Direccion VARCHAR(100) NOT NULL, Departamento VARCHAR(30) NOT NULL)

4.2.7. La instruccin DROP Esta instruccin permite borrar una base de datos o una tabla. Su sintaxis es la siguiente: Borrar la Base de Datos: DROP DATABASE <nombre de la base de datos> Crear la tabla: DROP TABLE <nombre de la tabla>

Captulo 4. Programacin en ADO.

Pgina 24

4.3. El objeto DataTable


El objeto DataTable, al igual que el objeto DataSet que veremos ms adelante, existen desde las primeras versiones de Net Framework. Son tan importantes para el conjunto de datos, que han ido aumentando en prestaciones e importancia con las distintas versiones. El DataSet ha sido siempre el ncleo de ADO .Net, proporcionando una representacin en memoria interna de datos relacionales, incluidos claves, restricciones e incluso capacidad de consulta. Muchas veces se trabaja con una sola tabla, y el uso del DataSet era casi obligado, dado que el objeto DataTable fuera de l no tena muy buena funcionalidad. A partir de la versin de Net Framework 2.0, se mejor notablemente la clase DataTable, convirtindola en un elemento muy importante y con una funcionalidad propia que hace que ya no sea dependiente del objeto DataSet. Un DataTable es en realidad un representacin de una tabla de datos, donde encontramos otros dos objetos: el DataColumn, que permite especificar y manejar las columnas; y el objeto DataRow, que permite acceder a las filas de la tabla. Adems podemos aplicar restricciones usando el objeto Constrains, as como relaciones entre tablas usando el objeto DataRelations. ADO, por mediacin del DataTable y el DataSet, proveen de todos los elementos necesarios para desarrollar una base de datos sin necesidad de contar con un proveedor especfico. El DataTable, incluye la coleccin DataRowCollection de objetos DataRow (filas de la tabla), la coleccin DataColumnCollectiom de objetos DataColumn (columna de datos) y la coleccin ConstraintCollection de objetos Constraint (restricciones).

4.3.1. Cargar valores en una tabla. El objeto DataColumn y DataRow 4.3.1.1. El objeto DataColumn. Para crear una tabla, es necesario empezar por definir las columnas que contendrn la tabla. Una columna tiene dos valores importantes a definir: el nombre de la columna y el tipo de dato que contiene. Podemos declarar la columna de dos formas: Podemos aadir columnas a la coleccin de columnas del objeto DataTable, especificando el nombre de la columna y el tipo de datos que guarda:
Captulo 4. Programacin en ADO. Pgina 25

TablaDatos.Columns.Add("Producto", typeof(string));

Una variante de esta forma podra ser esta:


Type Cadena = typeof(string); TablaDatos.Columns.Add("Producto", Cadena);

En ambos casos declaramos la columna con su nombre y su tipo. Pero si lo que queremos es utilizar ms funcionalidades de las columnas, como son los valores autoincrementados, campos clave y dems, la forma sera:
DataColumn ColumnaIdentidad = new DataColumn(); ColumnaIdentidad.AutoIncrement = true; ColumnaIdentidad.AutoIncrementSeed = 1; ColumnaIdentidad.AutoIncrementStep = 1; ColumnaIdentidad.DataType = typeof(int); ColumnaIdentidad.Unique = true;

En este ejemplo, hemos creado una columna que ser una columna del tipo autoincrementado, cuyo valor inicial es 1 (AutoIncrementSeed) y los incrementos sern de uno en uno (AutoIncrementStep). La propiedad Unique a true indica que la columna es de valores nicos, lo que significa que no puede contener valores repetidos. Una vez declarada la columna, la forma de agregarla a la coleccin de columnas del objeto DataTable sera:
TablaDatos.Columns.Add(ColumnaIdentidad);

Cuando cargamos las distintas columnas en el objeto DataTable, lo que tenemos, es la estructura de la tabla. Pero ahora es el momento de empezar a dar valores utilizando para ello el objeto DataRow. 4.3.1.2. El objeto DataRow. Este objeto tiene la finalidad de crear una fila que cumpla con la estructura de la tabla, para posteriormente, dar de alta los valores que contendr la tabla. Podemos crear una fila, especificando manualmente las columnas que contendr, pero dado que usaremos el objeto DataRow para dar de alta una fila sobre un objeto DataTable ya creado, lo razonable sera crear una fila nueva a partir del objeto DataTable:
DataRow FilaInsertar = TablaDatos.NewRow();

En este ejemplo, hemos creado una fila con la estructura que tiene el objeto DataTable, TablaDatos. Suponiendo que la tabla sea una tabla que contenga las siguientes columnas: Nombre, Apellidos y DNI, la forma de dar valores sera:
Captulo 4. Programacin en ADO. Pgina 26

FilaInsertar[0] = "Alvaro"; FilaInsertar[1] = "Snchez Snchez"; FilaInsertar[2] = 5746565-K;

En este ejemplo, cmo podemos observar, utilizamos el nombre del objeto DataRow facilitando entre corchetes el nmero de columna al que deseamos dar valores. Se comporta como una matriz o coleccin y el ndice comienza en cero. Tambin es posible dar el nombre de la columna en lugar del ndice. Por ltimo, para aadir los valores al DataTable:
TablaDatos.Rows.Add(FilaInsertar);

DataRow, incluye la propiedad RowState, que permite saber si la fila cambi y cal fue el motivo del cambio, pudiendo conocer cualquier de estos estados: Added (aadir), Deleted (borrar), Modified (modificar) y Unchanged (sin cambios). Existe adems el DataView, que representa una vista personalizada que puede enlazar datos de un DataTable para ordenacin, filtrado, bsqueda, edicin y exploracin.

4.3.2. Recorrer los valores de un DataTable Las filas de un objeto DataTable son una coleccin y por tanto podemos recorrerla con un bucle for o con un bucle foreach:
for (int i = 0; i < TablaDatos.Rows.Count; i++) { DataRow Fila = TablaDatos.Rows[i]; richTextBox1.Text += Fila[1]; }

En este ejemplo, obtenemos del objeto DataRow, Fila, el valor de la columna 1 (Fila[1]). Si en lugar de usar un bucle for usamos un bucle foreach:
foreach (DataRow Fila in TablaDatos.Rows) richTextBox1.Text += Fila[1];

Como podemos observar, es ms cmodo utilizar un bucle foreach que un bucle for. 4.3.3. Recorrer los valores de un DataTable usando un DataReader. Por mediacin del objeto DataTableReader, podemos implementar un DataReader, con un funcionamiento similar a un ADO Conectado (este punto se ver ms adelante en este mismo captulo).
Captulo 4. Programacin en ADO. Pgina 27

DataTableReader Lector = TablaDatos.CreateDataReader(); while (Lector.Read()) { MessageBox.Show(Lector[2].ToString()); }

En este ejemplo hemos implementado un objeto DataTableReader que nos permite recorrer el DataTable como si fuera una consulta realizada en modo ADO Conectado. Las filas del DataTable son volcadas a Lector y, al igual que si fuera un objeto DataRow, podemos sacar el valor de la columna, bien sea por su ndice o por su nombre. Las columnas del lector son de tipo object. Ver Uso DataTable-01

4.3.4. Cargar un fichero XML en un DataTable. El XML se ha convertido en un estndar para el intercambio de informacin entre proveedores de datos. En .Net disponemos de la posibilidad de cargar un DataTable desde un fichero en XML. A partir de la versin de Net Framework 2.0, se mejor notablemente la clase DataTable, convirtindola en un elemento muy importante y con una funcionalidad propia que hace que sea muchas veces til sin necesidad de contar con un objeto DataSet. Dentro de las nuevas funcionalidades que se dieron al objeto DataTable, est la posibilidad de leer y cargar un fichero XML en el objeto. Implementa los mtodos ReadXml y WriteXml, similar al uso que tienen en el DataSet. El mtodo ReadXml permite leer un fichero XML a un objeto DataTable:
DataTable objDataTable = new DataTable("MisClientes"); objDataTable.ReadXml("Clientes.xml");

En este ejemplo, cuando creamos el DataTable, le damos un nombre a la tabla, en este caso: MisClientes. A continuacin, usando el mtodo ReadXml, cargamos el fichero clientes.xml dentro del DataTable. Automticamente, se generan las columnas y las filas dentro del mismo.

4.3.5. Grabar un DataTable en un fichero XML. Otra de las posibilidades es escribir el contenido de un DataTable dentro de un fichero XML, lo que nos permite un fcil intercambio de informacin con los actuales sistemas de bases de datos y aplicaciones. Para poder realizar una escritura del DataTable en el fichero XML, utilizamos el mtodo WriteXML.
Captulo 4. Programacin en ADO. Pgina 28

DataTable objDataTable = new DataTable(); objDataTable.Columns.Add("IDCliente", typeof(int)); objDataTable.Columns.Add("NombreCliente", typeof(string)); objDataTable.Columns.Add("ApellidosCliente", typeof(string)); objDataTable.Rows.Add(new object[] { 1, "Pedro", "Lpez Snchez" }); objDataTable.Rows.Add(new object[] { 2, "Mara Jos", "Martnez" }); objDataTable.Rows.Add(new object[] { 3, "Jesus", "Snchez Alpino" }); objDataTable.Rows.Add(new object[] { 4, "Ana", "Moreno Lpez" }); objDataTable.WriteXml("clientes.xml", XmlWriteMode.WriteSchema);

En este ejemplo, usamos el mtodo WriteXml de los DataTable para grabar el fichero XML con los datos contenidos dentro del DataTable, objDataTable. A pesar de que en la documentacin de MSDN indica que la inferencia de esquemas es posible en un DataTable desde datos XML, el hecho de inferir esquemas en tiempo de ejecucin desde XML provoca una excepcin que indica que no es compatible con la clase DataTable. Para evitar esto, en el ejemplo Uso DataTableXML, se usa la opcin XmlWriteMode.WriteSchema para incluir el esquema en la parte superior del documento. Ejemplo Uso DataTableXML

Captulo 4. Programacin en ADO.

Pgina 29

4.4. El objeto DataSet


El DataSet representa una cache de memoria con datos y es el eje de la arquitectura ADO.NET. Cada DataSet puede contener varios objetos DataTable, y cada DataTable contiene datos de un solo origen, como SQL Server o Access. Igualmente y, como hemos visto anteriormente, podemos crear un DataTable con datos locales sin necesidad de conectar a ningn origen de datos en SQL. Este DataTable, evidentemente, tambin puede ser cargado dentro del DataSet. Es decir, el DataSet puede mantener mltiples datos que pertenecen a distintos orgenes de datos. El DataSet, incluye la coleccin DataTableCollection de objetos DataTable (que son las tablas de datos) y la coleccin DataRelationCollection de objetos DataRelation (relaciones entre las tablas). Para conectar datos a un DataSet podemos usar un objeto DataAdapter (se estudiar ms adelante), que almacena los datos ledos del origen de datos en un conjunto de datos. Cuando deseamos modificar ese origen de datos desde nuestra aplicacin, volver a realizar la conexin con el origen de datos, suministrndole la nueva informacin desde el conjunto de datos que se encuentra en nuestra mquina. Podramos acceder directamente al origen de datos por mediacin de comandos de SQL o procedimientos almacenados. 4.4.1. Cargar objetos DataTable en el DataSet El elemento bsico de informacin dentro de un DataSet es el DataTable. Por tanto, no tiene sentido crear un DataSet si luego no vamos a cargar en su interior objetos DataTable. Para cargar un DataTable dentro del DataSet disponemos de la coleccin Tables del DataSet. Por ejemplo:
//Damos nombres a las tablas. objDataTable1.TableName = "Empleados"; objDataTable2.TableName = "Categorias"; //Agregamos las tablas al DataSet. objDataSet.Tables.Add(objDataTable1); objDataSet.Tables.Add(objDataTable2);

En este ejemplo, aadimos a la coleccin Tables del DataSet el objeto objDataTable1 y el objetoDataTable2, ambos del tipo DataTable. Previamente, usando la propiedad TableName, hemos asignado un nombre a cada Tabla. 4.4.2. Relacionar objetos DataTable Una funcin muy importante de los DataSet es la de manejar los DataTable como si de una Base de Datos Relacional se tratara. Esto nos permite crear DataTables dentro del DataSet, para despus vincularlos entre s
Captulo 4. Programacin en ADO. Pgina 30

y aumentar las prestaciones de los datos. En la siguiente figura podemos ver el diseador de DataSet con varias tablas y las relaciones entre ellas.

Diseador de DataSet
Relaciones entre DataTables

Nombre del DataTable

Columnas del DataTable. Objeto DataColumn

Objetos DataTable

Figura: Imagen del Diseador de DataSet.

Ver Uso DataSet-ADO. Si usamos el diseador de DataSet definiremos las relaciones con las herramientas visuales que para ello tiene definido el objeto. Pero en el caso de que necesitemos desarrollar todo el trabajo desde el cdigo, los pasos a seguir son: Definir las columnas que actuaran como campos clave dentro del DataTable, especificando incluso las caractersticas de unicidad, campo clave y dems caractersticas que usaramos en una tabla dentro de un gestor de bases de datos relacional. Ejemplo:
//Creamos una columna que ser columna clave del DataTable. //Ser una columna de autoincremento y valores nicos. DataColumn IdEmpleado = new DataColumn("IdEmpleado", typeof(int)); IdEmpleado.AutoIncrement = true; IdEmpleado.AutoIncrementSeed = 1; IdEmpleado.AutoIncrementStep = 1; IdEmpleado.Unique = true; //Agregamos la columna a la coleccin de columnas del DataTable objDataTable1.Columns.Add(IdEmpleado); //Creamos una matriz de columnas con la columna o columnas que sern //Columna clave en la tabla. DataColumn[] Claves = { IdEmpleado }; //Especificamos que columna o columnas son la clave del DataTable. objDataTable1.PrimaryKey = Claves;

Captulo 4. Programacin en ADO.

Pgina 31

A continuacin creamos las relaciones que pudieran existir entre las distintas tablas dentro del DataSet, tal y como podemos ver en este ejemplo:
//Existe una columna que es la clave principal y otra que es la clave //fornea. //Definimos dos objetos DataColumn con los nombres de las dos columnas //que vamos a relacionar. DataColumn ClavePrincipal = objDataSet.Tables["Categorias"].Columns["IdCategoria"]; DataColumn ClaveForanea = objDataSet.Tables["Empleados"].Columns["IdCategoria"]; //A Continuacin aplicamos la relacin. DataRelation relacionClaves = new DataRelation("RelacionCategoria", ClavePrincipal, ClaveForanea); objDataSet.Tables["Empleados"].ParentRelations.Add(relacionClaves);

4.4.3. Obtener objetos DataTable. Para obtener un DataTable que se encuentra dentro de un DataSet, accedemos por mediacin de la coleccin Tables, igual que cuando guardamos el DataTable. Ejemplo:
DataTable objTabla = objDataSet.Tables["Empleados"];

En este ejemplo hemos accedido al DataTable Empleados y colocado una copia en objTabla. Tambin podemos acceder por el ndice del DataTable dentro de la coleccin de Tables del DataSet. Por ejemplo, si sabemos que el DataTable Empleados es el cero dentro del ndice, podemos recuperarlo de la siguiente forma:
DataTable objTabla = objDataSet.Tables[0];

4.4.3.1. El mtodo Select de los DataTable. Podemos obtener datos de un objeto DataTable usando el mtodo Select. En este caso, la informacin es volcada sobre un objeto del tipo matriz de DataRow. El mtodo Select se comporta de una forma muy similar a la instruccin Select del T-SQL. Un ejemplo de aplicacin del mtodo Select sera:
DataRow[] Filas = objDataSet.Tables["Empleados"].Select("IdCategoria = 2");

En este ejemplo obtenemos todas las filas del DataTable Empleados cuyo valor de IdCategoria sea 2. Las filas coincidentes son guardadas en la matriz del tipo DataRow Filas.
Captulo 4. Programacin en ADO. Pgina 32

Otro ejemplo sera:


DataRow[] Filas = objDataSet.Tables["Empleados"].Select("IdCategoria = 2 AND NombreEmpleado = 'Miguel Angel'");

En este otro ejemplo, la bsqueda se realizar por el IdCategoria igual a 2 y, por el NombreEmpleado igual a Miguel Angel. Si deseamos obtener los datos ordenados, podemos incluir el parmetro Sort del mtodo Select:
DataRow[] Filas = objDataSet.Tables["Empleados"].Select("IdCategoria = 2", "NombreEmpleado DESC");

Este ejemplo ordena los resultados por la columna NombreEmpleado y de formas descendente (DESC). Por defecto es ascendente. Otra posibilidad de ordenacin sera:
DataRow[] Filas = objDataSet.Tables["Empleados"].Select("IdCategoria = 2", "ApellidosEmpleado ASC, NombreEmpleado ASC");

En este otro caso el resultado es ordenado primeramente por la columna ApellidosEmpleado de forma ascendente y por la columna NombreEmpleado de forma tambin ascendente. Otra posibilidad es obtener filas que cumplan un criterio basado en si han sido borradas, modificadas u obtener sus valores originales. Para este punto disponemos del miembro enumerable DataViewRowState:
DataRow[] Filas = objDataSet.Tables["Empleados"].Select(null, null, DataViewRowState.Added);

En este ejemplo obtiene todas las filas que fueron aadidas al DataTable. Las distintas posibilidades del DataViewRowState son: Valor de DataViewRowState Added CurrentRows Deleted ModifiedCurrent ModifiedOriginal OriginalRows Unchanged None Significado Fila nueva Las filas actuales, incluyendo las agregadas, modificadas y sin modificar. Fila eliminada La versin actual, que es una modificacin de los datos actuales. La versin original de todas las filas modificadas. Las filas originales, incluyendo las filas que fueron eliminadas. Fila sin modificar. Ninguno

Captulo 4. Programacin en ADO.

Pgina 33

Parmetros y expresiones vlidas en el mtodo Select de los DataTable:


Para utilizar una columna denominada "Column#" en una expresin, se deber escribir "[Column#]". Ejemplo: Total * [Column#] Puesto que los corchetes son caracteres especiales, se debe utilizar una barra diagonal ("\") para crear un carcter de escape para el corchete, si forma parte de un nombre de columna. Por ejemplo, una columna denominada "Column[ ]" se escribir: Total * [Column[\]] Slo se debe crear un carcter de escape para el segundo corchete. Valores Definidos por el Usuario Los valores definidos por el usuario se pueden utilizar en expresiones para compararlos con valores de columnas. Los valores de cadena se deben escribir entre comillas sencillas. Los valores de fecha se deben poner entre signos de libra esterlina (#) o comillas simples (') dependiendo del proveedor de datos. Se permiten decimales y notaciones cientficas para los valores numricos. Por ejemplo: "FirstName = 'John'" "Precio <= 50.00" "Fecha < #1/31/2010#" Fecha < 31/1/2010 Para el caso de las fechas, puede ocurrir que se den errores derivados del uso de los formatos de fecha en americano o en espaol. Si usamos notacin americana usaremos las #, pero para el caso de las fechas en espaol usamos las comillas simples. Para las columnas que contienen valores de enumeracin, el valor se convierte en un tipo de datos entero. Por ejemplo: "EnumColumn = 5" Operadores Se permite la concatenacin mediante operadores booleanos AND, OR y NOT. Se pueden utilizar parntesis para agrupar clusulas y forzar una precedencia. El operador AND tiene precedencia sobre otros operadores. Por ejemplo: (LastName = 'Smith' OR LastName = 'Jones') AND FirstName = 'John'

Captulo 4. Programacin en ADO.

Pgina 34

Al crear expresiones de comparacin, se permiten los siguientes operadores:

Tambin se admiten los siguientes operadores aritmticos en las expresiones:

Operadores de Cadena Para concatenar una cadena se utiliza el carcter +. El valor de la propiedad CaseSensitive de la clase DataSet determina si en las comparaciones de cadenas se distingue entre maysculas y minsculas. Sin embargo, se puede reemplazar ese valor por la propiedad CaseSensitive de la clase DataTable. Caracteres Comodn Tanto * como % se pueden utilizar indistintamente como caracteres comodn en una comparacin LIKE. Si la cadena de una clusula LIKE contiene un carcter * o %, dichos caracteres se deben establecer como caracteres de escape entre corchetes ([]). Si hay un corchete en la clusula, los caracteres de corchete se deben establecer como caracteres de escape entre corchetes (por ejemplo, [[] o []]). Se permite un carcter comodn al comienzo y al final de un modelo, al final de un modelo o bien al comienzo de un modelo. Por ejemplo: "ItemName LIKE '*product*'" "ItemName LIKE '*product'" "ItemName LIKE 'product*'" No se permiten los caracteres comodn en mitad de una cadena. Por ejemplo, no se admite 'te*xt'.
Captulo 4. Programacin en ADO. Pgina 35

Referencia a Relaciones primarias y secundarias Se puede hacer una referencia a una tabla primaria en una expresin anteponiendo Parent al nombre de la columna. Por ejemplo, Parent.Precio hace referencia a la columna denominada Precio de la tabla primaria. Se puede hacer referencia a una columna de una tabla secundaria en una expresin anteponiendo Child al nombre de la columna. Sin embargo, dado que las relaciones secundarias pueden devolver varias filas, se debe incluir la referencia a la columna secundaria en una funcin de agregado. Por ejemplo, Sum(Child.Precio) devolvera la suma de la columna denominada Precio de la tabla secundaria. Si una tabla tiene varias tablas secundarias, la sintaxis es: Child(RelationName). Por ejemplo, si una tabla tiene dos tablas secundarias denominadas Customers y Orders, y el objeto DataRelation se denomina Customers2Orders, la referencia sera la siguiente: Avg(Child(Customers2Orders).Quantity) Agregados Se admiten los siguientes tipos de agregados:

Normalmente los agregados se llevan a cabo en las relaciones. Se crea una expresin de agregado mediante una de las funciones enumeradas anteriormente y una columna de una tabla secundaria, como se ha descrito en Referencia a relaciones primarias y secundarias. Por ejemplo: Avg(Child.Precio) Avg(Child(Orders2Details).Precio) Un agregado tambin se puede realizar en una sola tabla. Por ejemplo, para crear un resumen de cifras de una columna denominada "Price": Sum(Precio) Ver: Uso DataTable-DataSet

Captulo 4. Programacin en ADO.

Pgina 36

4.5. Introduccin ADO.NET (ActiveX Data Object)


ADO.NET es una tecnologa de Mircrosoft que sustituyo a DAO (Data Access Object), a RDO (Remote Data Object) y a la versin anterior, llamada simplemente ADO. La filosofa de diseo de ADO .NET es evitar el mantener activa permanentemente una conexin con la base de datos, aunque tambin pude trabajar en modo permanentemente conectado. Esto significa que la aplicacin se conecta slo cuando necesita realizar una tarea con la base de datos, estando el resto del tiempo desconectado del Servidor de Bases de Datos. Al final, esto repercute muy beneficiosamente en el uso del Servidor, ya que permite disponer de un mayor nmero de conexiones posibles. ADO .NET trabaja como intermediario entre nuestra aplicacin y la base de datos. El programa nunca ve directamente los datos, sino que le son suministrados por ADO .NET y, este a su vez, devuelve la informacin a la base de datos cuando el programa as lo requiere. Todo acceso a la base de datos se realiza desde ADO .NET por mediacin de rdenes, que son objetos que encapsulan sentencias de SQL o procedimientos almacenados. Cuando solicitamos datos, estos son almacenados en memoria cache, lo que permite trabajar sobre los datos sin tener una conexin abierta. En realidad trabajamos sobre una copia de los datos originales. Una vez que hemos realizado operaciones con esos datos, el modelo de ADO .NET permite restablecer la conexin con la base de datos y actualizar los datos que estbamos manejando. El formato de transferencia de ADO .NET es el XML. Un fichero XML es un fichero de texto que puede ser enviado con cualquier protocolo, como por ejemplo, HTTP. Esquema del modelo de trabajo de ADO

Capa de Presentacin
Aplicacin Windows Aplicacin Consola Aplicacin WEB

Capa Lgica Negocio


Conjunto de Datos

Capa de Datos
Bases de Datos

Adaptador de Datos Adaptador de Datos

Conexin

Conexin

Captulo 4. Programacin en ADO.

Pgina 37

Esquema de los modos de trabajo de ADO


Aplicacin

DataSet
Proveedor de Acceso a Datos

DataReader

Command

DataAdapter

Connection

Leyenda: Modo Conectado Modo Desconectado

Base de Datos

Componentes de ADO .NET. Espacio de nombres: System.Data Los objetos de ADO .NET pertenecen al espacio de nombres System.Data, es decir, proporciona acceso a las clases que permiten trabajar con ADO.NET. Algunos de los componentes que incorpora son: Connection. Responsable de realizar las conexiones con la base de datos. Command. Responsable de las rdenes o comandos sobre la base de datos. DataReader. Lector de los datos. Lee una secuencia de datos de slo avance y slo lectura desde un origen de datos. DataAdapter. Representa un conjunto de comandos SQL y una conexin de base de datos que se utilizan para rellenar el objeto DataSet y actualizar el origen de datos.

Captulo 4. Programacin en ADO.

Pgina 38

Todos estos objetos se analizarn ms adelante cuando tratemos los dos modos de acceso de ADO: Conectado y Desconectado. El Proveedor de datos El proveedor de datos es el responsable de crear una comunicacin entre la aplicacin y el origen de datos, as como de suministrar todos los componentes necesarios para trabajar con los datos. Se utiliza en ambos sentidos, es decir, para recuperar datos de un origen, o para actualizar esos datos en el origen. Cada Servidor de Datos usa su propio proveedor de datos, tambin llamado proveedor de datos nativo. En algunas circunstancias, podemos encontrarnos que no existe un proveedor nativo y por tanto debemos de usar uno genrico. Esquema del Proveedor de Datos

El Proveedor de Datos
Connection Transaction DataAdapter SelectCommand InsertCommand UpdateCommand DeleteCommand DataReader

Command Parameters

Cada proveedor tiene su correspondiente espacio de nombres asignado. As encontramos para: SQL Server. El System.Data.SqlClient. Cualquier versin del Sql Server 7, o superior. Oracle. El System.Data.OracleClient ODBC. El System.Data.Odbc OLE DB. El System.Data.OleDb

Por mediacin de ODBC y OLE DB, podemos acceder a la mayora de los Servidores de SQL que hay en el mercado. Para el caso de MySQL, podemos usar ODBC, pero es necesario acudir al fabricante para que nos
Captulo 4. Programacin en ADO. Pgina 39

suministre el proveedor de datos nativo, ya que estos se comportan mucho mejor que un proveedor genrico como es ODBC. De todas formas, Microsoft facilita en el siguiente enlace proveedores de datos por terceros: http://msdn.microsoft.com/en-us/data/dd363565.aspx Dentro del proveedor de datos, disponemos del objeto Connection, que permite realizar la conexin. En funcin del proveedor de datos que estemos usando cambia el objeto Connection. As, si queremos conectar con SQL Server usamos el objeto SqlConnection, para conectarnos a Access, usamos OleDbConnection. El objeto Connection permite, por mediacin de propiedades, especificar el id de usuario, el password de conexin, la base de datos que se conecta, etc. Su funcin es proveer de los mtodos y propiedades necesarios para realizar una correcta conexin con el origen de datos. Este objeto es comn tanto para el modo Conectado como para el modo Desconectado. Recomendaciones a la hora de realizar las conexiones: 1. Para un ptimo rendimiento de la aplicacin, lo ideal es usar el proveedor nativo del Servidor de Base de Datos con el que vamos a trabajar. 2. La apertura de la conexin es lo ltimo que hay que hacer. Es decir, todas las variables que se puedan definir antes de la conexin, se definirn. 3. La conexin debe de cerrarse lo antes posible, obviamente se cerrar si no se utilizar ms adelante. 4. Nunca se debe dejar una conexin sin cerrar. 5. Todas las conexiones hay que realizarlas bajo Try..Catch.. La mejor eleccin del modo de trabajo depender del entorno y las necesidades y requerir un estudio detallado de cada situacin. Normalmente se realizarn distintas pruebas para verificar el nivel de respuesta de la aplicacin. Como hemos visto, ADO puede trabajar tanto en modo permanentemente conectado a la base de datos, como en modo desconectado (conecta slo cuando es necesario). A continuacin vemos en detalle los dos modos de trabajo de ADO.

Captulo 4. Programacin en ADO.

Pgina 40

4.6. El modo ADO Conectado


El modo conectado mantiene la conexin con el servidor, mientras la parte de la aplicacin que ha accedido a los datos este activa. El modo conectado tiene un esquema de trabajo que podemos ver en el siguiente esquema:

Esquema ADO Conectado


Aplicacin Origen Datos DataReader Command Connection

Representacin en cdigo del esquema


string Conexion = "Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=C:.\\..\\..\\empresa.mdb;"; OleDbConnection objConexion = new OleDbConnection(Conexion); string Consulta = "SELECT nombre, apellidos FROM empresa"; OleDbCommand objComando = new OleDbCommand(Consulta, Conexion); OleDbDataReader Informacion = objComando.ExecuteReader();

Una vez ejecutado un objeto Connection, podemos ejecutar comandos de SQL por mediacin del objeto Command, que al igual que Connection, vara en funcin del Servidor de Datos. Para OleDb tenemos OleDbCommand, para ODBC, tenemos OdbcCommand, para Sql Server tenemos SqlCommand y para Oracle, tenemos OracleCommand. Muchas veces no es necesario almacenar los datos ledos en un Conjunto de Datos (DataSet), ya que esta opcin puede consumir mucha memoria. En su lugar podemos usar el objeto DataReader. Este objeto, vuelca directamente la informacin a la aplicacin, pero la trae en pequeas unidades de trabajo que descarga la memoria de la mquina cliente. Al igual que para los objetos anteriores, disponemos de un objeto concreto para cada origen de datos. As, tenemos OleDbDataReader para OleDb, SqlDataReader para Sql Server, OdbcDataReader para ODBC y OracleDataReader para Oracle. Ejemplo: Uso ADOConectado-SQL y Uso ADOConectado-ACCESS
Captulo 4. Programacin en ADO. Pgina 41

El Modo Conectado usa un objeto del tipo DataReader para recuperar un conjunto de datos desde la base de datos y posteriormente los almacena en un buffer intermedio (aunque los datos no son almacenados en ninguna memoria cache). Un DataReader necesita obligatoriamente un objeto Command para poder trabajar. El resultado de la ejecucin de un objeto Command es utilizado por el DataReader para crear ese buffer intermedio. Para poder cargar el DataReader, ExecuteReader() del objeto Command: DataReader = Command.ExecuteReader(), La propiedad HasRows del DataReader permite saber si existen filas en el objeto DataReader despus de ejecutar el Command.ExecuteReader(); Ejemplo:
// Crear la conexin con la base de datos Empleados de //Access string objConexion = "Provider=Microsoft.Jet.OLEDB.4.0; Data + Source=C:.\\..\\..\\empleados.mdb;";

aplicamos

el

mtodo

OleDbConnection objConexionOleDb = new OleDbConnection(objConexion); //Crear una consulta para la base de datos string strConsulta = "SELECT nombre, apellidos FROM Empleados"; OleDbCommand objOrden = new OleDbCommand(strConsulta, objConexionOleDb); // Abrir la base de datos objConexionOleDb.Open(); // ExecuteReader hace la consulta y devuelve un OleDbDataReader OleDbDataReader objLector = objOrden.ExecuteReader(); // Usamos el mtodo Read para acceder a los datos. HasRows // permite saber si hay registros cargados desde la consulta. if (objLector.HasRows) { while (objLector.Read()) // siguiente registro MessageBox.Show(objLector["nombre"] + " " + objLector["apellidos"]); } // Llamar siempre a Close una vez finalizada la lectura objLector.Close();

Mtodo Read. Se utiliza el mtodo Read del objeto DataReader para obtener una fila a partir de los resultados de una consulta. Para obtener su contenido, podemos acceder a cada columna o campo contenido en el DataReader, por su nombre (como hemos realizado en el ejercicio anterior nombre y apellidos-) o por su referencia numrica. Es decir, en el ejemplo,
Captulo 4. Programacin en ADO. Pgina 42

podemos sustituir objLector[nombre], por objLector[0]. Esto es porque la columna que se encuentra en la posicin 0 del DataReader es la de Nombre. De todas formas, el mejor rendimiento se consigue usando los mtodos que dispone el DataReader y que permiten tener acceso a los valores de las columnas en sus tipos de datos nativos (GetDateTime, GetDouble, GetString, GetGuid, GetInt32, etc.). Si se utilizan los mtodos de descriptor de acceso con tipo, dando por supuesto que se conoce el tipo de datos subyacentes, se recude el nmero de conversiones de tipo necesarias para recuperar el valor de una columna. Para el ejemplo anterior, podemos sustituir la lnea que escribe los datos por esta otra:
MessageBox.Show(objLector.GetString(0) + " " + objLector.GetString(1));

Obsrvese que entre los parntesis del mtodo de descriptor de acceso con tipo GetString, hay que indicar la referencia numrica de la columna a la que nos estamos refiriendo. Hay que tener cuidado si el campo al que se accede es null, el uso de GetString da un error. Los DataReader, cuando se han terminado de utilizar, es necesario llamar al mtodo Close() para su cierre, ya que si el objeto Command contiene parmetros de salida o valores devueltos, stos no estarn disponibles hasta que se cierre el DataReader. Un DataReader usa de forma exclusiva el objeto Connection, por tanto, no se podr ejecutar ningn comando para el objeto Connection hasta que se cierre el DataReader original, incluida la creacin de otro DataReader. Un DataReader dispone del mtodo GetSchemaTable(), que permite recuperar informacin sobre el esquema actual de datos. El resultado lo devuelve en un objeto del tipo DataTable, rellenando cada fila por cada una de las columnas del conjunto de datos. Cada Columna de una fila de la tabla de esquemas est asociada a una propiedad de la columna que se devuelve en el conjunto de datos. ColumnName es el nombre de la propiedad y el valor de la Columna es el de la propiedad. Ejemplo:
DataTable obj = objLector.GetSchemaTable(); foreach (DataRow fila in obj.Rows) { foreach (DataColumn columna in obj.Columns) { Console.WriteLine(columna.ColumnName + " = " + fila[columna]); } }

Captulo 4. Programacin en ADO.

Pgina 43

Ejecutar un comando insertar en ADO Conectado Cuando hacemos consultas usando el objeto Command de ADO Conectado, le suministramos la sentencia como un string y a continuacin ejecutamos el comando con ExecuteReader. Pero si lo que deseamos es realizar otros comandos que necesitan parmetros, como ocurre en el caso del comando Insert que necesita una lista con los valores a insertar, tenemos que utilizar la coleccin Parameters del objeto Command y luego podemos ejecutar el comando pero con el mtodo ExecuteNonQuery. ExecuteReader devuelve en un objeto DataReader el resultado de la consulta. Para el caso de ExecuteNonQuery, lo que devuelve es un valor de tipo int con el nmero de filas afectadas por el comando ejecutado. Ejemplo:
SqlConnection myConn = new SqlConnection("Data Source=PORTATIL\\SQLEXPRESS;Integrated Security=True;database=Empleados"); string str = "INSERT INTO empleados(NombreEmpleado, ApellidosEmpleado) + "VALUES(@Nombre, @Apellidos)"; SqlCommand myCommand = new SqlCommand(str, myConn); myCommand.Parameters.AddWithValue("@Nombre", "Pedro"); myCommand.Parameters.AddWithValue("@Apellidos", "Lpez Ruiz");

Como podemos ver, str, contiene la cadena de SQL que se tiene que ejecutar en el servidor. En la clusula values aparecen variables de SQL que son las que guardarn el valor pasado con la propiedad, Parameters, del objeto Command. El mtodo AddWithValue, nos permite cargar en una sola accin el parmetro y su valor. Otra posibilidad, menos acadmica, es pasar los valores a la sentencia como podemos ver en este otro ejemplo:
string str2 = "INSERT INTO empleados(NombreEmpleado, ApellidosEmpleado, DNIEmpleado, EmpleadoActivo, IdDepartamento, IdCategoria) " + "VALUES(" + Nombre + "," + Apellidos + "," + DNI + "," + Activo + "," + Depar + "," + Cate + ")"; SqlCommand myCommand = new SqlCommand(str2, myConn);

Las variables como Nombre, Apellidos, etc, deben de contener el valor entre comillas simples. Por ejemplo: string Nombre = Antonio . NOTA: Esto permite un fallo de seguridad conocido como SQL inyectado. Esta ltima forma, aunque pueda parecer prctica no se debe de realizar nunca.

Captulo 4. Programacin en ADO.

Pgina 44

Ejecutar un procedimiento almacenado con parmetros de salida. Debido a temas de rendimiento del servidor de SQL, la mayor parte de los accesos a los datos no se suelen realizar por mediacin de instrucciones ejecutadas directamente, sino que se utilizan procedimientos almacenados. Aqu vamos a disear un ejemplo que accede a un procedimiento almacenado llamado ActualizaPrecios, que tomando dos parmetros de entrada: Codigo y Precio, actualiza el precio para el Id de libro facilitado por cdigo. El procedimiento devuelve con un parmetro de salida llamado result, el nmero de filas afectadas. En principio slo ser una (el Id del libro es Clave Primaria y por tanto no acepta repeticiones). Lo primero que tenemos que saber es que en ADO es necesario crear tanto los parmetros de entrada como el parmetro de salida con el mismo nombre que tienen en el procedimiento almacenado. Ejemplo Uso ProcedimientoAlmacenado
SqlCommand objComando = objConexion.CreateCommand(); objComando.CommandText = "actualizaprecios"; objComando.CommandType = CommandType.StoredProcedure; objComando.Parameters.Add(new SqlParameter("@result", SqlDbType.Int)); objComando.Parameters.Add(new SqlParameter("@Codigo", "1")); objComando.Parameters.Add(new SqlParameter("@Precio", "167")); objComando.Parameters[0].Direction = ParameterDirection.Output; objComando.ExecuteNonQuery();

Hay que indicar que el objeto que aparece en este ejemplo, objConexion, es un objeto del tipo Connection que realiza la conexin con la base de datos. Por mediacin de la propiedad CommandText introducimos el nombre del procedimietno almacenado. A continuacin y, por mediacin de la coleccin Parameters del objeto Command, introducimos los tres parmetros: uno de salida (result) y dos de entrada (Codigo y Precio). En los parmetros de entrada especificamos el valor que van a tener cuando se ejecute el procedimiento. Dado que el primer parmetro declarado en el objeto Command ha sido el valor de salida, cuando queremos invocarlo llamamos a la coleccin Parameters[0]. Sabemos que est en la posicin cero. Observamos que por mediacin del miembro enumerado SqlDbType, especificamos en el parmetro de salida el tipo correspondiente al valor devuelto por el procedimiento almacenado.

Captulo 4. Programacin en ADO.

Pgina 45

4.7. El modo ADO Desconectado


La norma general es usar un adaptador por cada origen de datos y un solo objeto DataTable del conjunto de datos.

Esquema ADO Desconectado

DataAdapter Aplicacin Conjunto de Datos


SelectCommand InsertCommand DeleteCommand UpdateCommand

Origen de Datos

Conexin

El Modo Desconectado. Siguiendo la estructura explicada anteriormente, para trabajar con ADO desconectado necesitamos los siguientes objetos: Connection, DataAdapter y DataSet. La Clase DataSet Si bien el DataSet no forma parte del ADO como tal, ya que podemos usar un DataSet sin necesidad de conectarnos a una base de datos, forma parte importante del modelo de ADO Desconectado.

Jerarqua de la clase DataSet


DataTable DataRelation DataTable

Datacolumn

Datacolumn

DataRow

DataRow

Un DataSet es una base de datos de memoria interna. Por mediacin del objeto DataTable puede contener en su interior mltiples tablas compuestas por campos de distintos tipos, al igual que si estuviramos en SQL. Como tablas que son, las filas pueden ser relacionadas con otras tablas, a travs de claves exteriores y de relaciones complejas que imponen restricciones de datos
Captulo 4. Programacin en ADO. Pgina 46

padre/hijo. Los DataTable permiten asignar valores automticos segn se insertan filas en la tabla, al igual que hacen los campos Identity en SQL Server. En un DataSet se pueden utilizar mtodos para buscar el contenido de las tablas que lo componen. Adems se puede tratar un DataSet como si fuera un documento XML y realizar consultas XPath. Evidentemente un DataSet puede guardar informacin en formato XML y en un formato binario propio para ADO .Net. Podemos ver un ejemplo de cmo se crea un DataSet de forma dinmica, as como las opciones de agregar, eliminar y consultar filas contenidas en un DataTable. Ejemplo Uso DataSet En el ejemplo de Uso DataSet podemos comprobar como el propio DataSet es capaz de guardar tablas, por mediacin del objeto DataTable, como si se tratase de un gestor de SQL. En este ejemplo hemos diseado una simple tabla que contena un campo clave y hemos realizado una bsqueda sobre la tabla. ADO Desconectado. Ejemplo utilizando la interfaz grfica de Visual Studio. En este caso vamos a utilizar a modo de ejemplo los componentes disponibles en la barra de herramientas. Empezamos con el objeto SQLDataAdapter. Si introducimos un objeto SQLDataAdapter (realizaremos la conexin contra SQL Server), se lanzar de forma automtica la siguiente ventana:

Captulo 4. Programacin en ADO.

Pgina 47

El hecho de utilizar el SQLDataAdapter, permite definir en el mismo proceso el objeto SQLConnection. Pulsamos una conexin existente o nueva.

En la imagen superior, en el objeto DataAdapter, podemos definir todas las propiedades necesarias para poder realizar una conexin. Se puede especificar un Origen de Datos distinto usando para ello el botn Cambiar. Nos permite especificar cuatro orgenes distintos: tres en modo nativo (el de Access es OleDb) y uno en modo ODBC, que es un estndar. Seleccionamos en la ventana Agregar Conexin, Origen de Datos SQL Server. A continuacin seleccionamos el nombre del Servidor donde se encuentran la Base de Datos que queremos consultar. Especificamos Usar Autenticacin por Windows (si el servidor requiriera conexin por usuario y contrasea de SQL Server, habra que introducirlas en estas casillas). A continuacin seleccionamos la Base de Datos de la lista desplegable. En nuestro caso seleccionamos Telefonos (utilizar el fichero de Generar la base de datos de telfono en caso de no estar en el servidor). Despus, en el men Elegir un Tipo de Comando, seleccionamos Usar Instrucciones SQL. Para facilitarnos la escritura de la sentencia de SQL, disponemos de un asistente en el botn Generador de Consultas. Despus de seleccionar en la interfaz grfica la instruccin, pulsamos Aceptar. Devuelta a la ventana de Elegir un Tipo de Comando, pulsamos el botn Siguiente, no el de Finalizar. Aparecer una pantalla explicando que todas las instrucciones para el adaptador se han realizado de forma correcta. Ahora s, pulsamos el botn de Finalizar.

Captulo 4. Programacin en ADO.

Pgina 48

Crear el Conjunto de Datos. Hasta el momento, slo hemos definido la conexin, la tabla o tablas de las que obtendremos los datos y hemos generado las instrucciones necesarias para ejecutar la consulta sobre la Base de Datos. Ahora debemos generar el resultado de la consulta. Para ello usaremos en el men Datos, del Visual Studio, la opcin de Generar conjunto de Datos. En nuevo tecleamos el nombre que tendr el DataSet que utilizaremos para rellenar los datos (ver imagen de la derecha) y pulsamos aceptar. Si lo que queremos es incluir una consulta en una nueva tabla (DataTable) dentro de un DataSet ya creado, seleccionamos en Existente el nombre del DataSet. A continuacin incorporamos al formulario un objeto del tipo DataGridView, que ser el responsable de controlar y editar los datos que nos son suministrados por el DataSet. En el DataGridView, pulsamos sobre la flecha que aparece en la parte superior derecha del Grid, cuando lo seleccionamos. En Elegir origen de Datos, (ver imagen de la derecha) seleccionamos, segn el ejemplo, dataSetTelefonos1. Esto hace, que la propiedad DataSource del DataGridView, sea definida con el nombre del DataSet. A continuacin seleccionamos la tabla Telefonos en la propiedad DataMember del DataGridView.

Captulo 4. Programacin en ADO.

Pgina 49

Ahora falta cargar el DataGridView con los datos disponibles, para ello, incorporamos un botn al formulario que servir para mostrar los datos en el Grid y en su evento click, tecleamos el siguiente cdigo.
//Limpia cualquier contenido que tuviera. dataSetTelefonos1.Clear(); //Despus llamamos al adaptador y a su mtodo Fill, para //que cargue los datos en el DataSet. sqlDataAdapter1.Fill(dataSetTelefonos1);

Un DataGridView, permite modificar los datos que se encuentran en las filas. Si no queremos que se pueda modificar, debemos de poner su propiedad ReadOnly a true. Pero a veces, es muy cmodo para el usuario modificar directamente los datos en un DataGridView y debemos dar esa posibilidad al usuario. En el caso de las conexiones con ADO, podemos modificar el contenido de los datos obtenidos con el DataAdapter que se encuentran en el DataGridView, pero estos no se actualizaran en la Base de Datos hasta que no se lo indiquemos. Para guardar los datos, escribiremos este cdigo en otro botn que utilizaremos para que el usuario cuando quiera pueda guardar:
// Primero pregunta si se han producido cambios en el // Conjunto de Datos contenidos en el DataSet. if (dataSetTelefonos1.HasChanges()) { if (MessageBox.Show("Quiere Modificar los cambios en la Base de Dato (S o No)", "Modificar Datos", MessageBoxButtons.YesNo) == DialogResult.Yes) { sqlDataAdapter1.Update(dataSetTelefonos1); MessageBox.Show("Datos Actualizados"); } }

ADO Desconectado usando cdigo en lugar del asistente. Si bien el entorno de diseo es muy cmodo de usar, en el modelo de programacin de tres capas, la parte de conexin a Bases de Datos, es sin lugar a dudas Capa Lgica de Negocio o Intermedia. Por tanto, no es muy buena idea usar los asistentes para su desarrollo, ya que estos acaban en la Capa Presentacin que es la ventana. En su lugar desarrollaremos clases cuya finalidad sea generar los datos desde la Base de Datos. En el modo ADO Conectado ya vimos algunos ejemplos de cmo desarrollarlo en modo cdigo. El ejemplo que viene a continuacin tiene como finalidad desarrollar las cuatro instrucciones bsicas para trabajar con datos desde ADO Desconectado: Select, Insert, Update y
Captulo 4. Programacin en ADO. Pgina 50

Delete. Para ello, en lugar de utilizar el objeto Command, usaramos el objeto CommandBuilder:
string Cadena = @"Data Source=PROFESOR\SQLEXPRESS;Initial Catalog=Empresa;Integrated Security=True"; SqlConnection Conexion = new SqlConnection(Cadena); SqlDataAdapter Adaptador = new SqlDataAdapter("SELECT * from categorias", Conexion); Adaptador.MissingSchemaAction = MissingSchemaAction.AddWithKey; SqlCommandBuilder RestoSQL = new SqlCommandBuilder(Adaptador); Adaptador.InsertCommand = RestoSQL.GetInsertCommand(); Adaptador.DeleteCommand = RestoSQL.GetDeleteCommand(); Adaptador.UpdateCommand = RestoSQL.GetUpdateCommand(); DataSet DatosDataSet = new DataSet(); Adaptador.Fill(DatosDataSet); DataRow FilaNueva = DatosDataSet.Tables[0].NewRow(); FilaNueva[1] = textBox1.Text; FilaNueva[2] = textBox2.Text; DatosDataSet.Tables[0].Rows.Add(FilaNueva); Adaptador.Update(DatosDataSet);

En este ejemplo, cabe destacar la propiedad MissingSchemaAction, cuya finalidad es aadir las columnas y columna clave necesarias para operar cuando no existan un DataTable y DataSet que lo contenga. En nuestro caso, es necesario crear antes las instrucciones que generar el DataSet y por supuesto el DataTable. Como podemos ver el SqlCommandBuilder recibe como parmetro de entrada (usamos el constructor) al propio adaptador y, a partir del adaptador que tiene fijada la instruccin Select, usamos los mtodos GetInsertCommand, GetDeleteCommand y GetUpdateCommand, para construir las otras tres instrucciones. El resto es llamar al mtodo Fill del adaptador para que cargue el DataSet con la tabla generada por la instruccin Select y, a continuacin, insertar una fila en el DataTable, para posteriormente actualizar todos los datos con el mtodo Update del adaptador.

Captulo 4. Programacin en ADO.

Pgina 51

4.8. Control BindingSource


Es un objeto que hace de intermediario entre el control y el conjunto de datos. Simplifica la conexin facilitando la actualizacin del contenido, la notificacin de cambios, etc. Se incluye la navegacin, ordenacin, filtrado y actualizacin. El origen de datos subyacente se fija a travs de uno de los siguientes mecanismos: Usar el mtodo Add para aadir un elemento al componente BindingSource. Asignar a su propiedad DataSource una lista, objeto o un tipo.

Su funcionamiento permite enlazar universalmente todos los controles de formularios Windows a orgenes de datos muy diversos. Ejemplo Uso BindingSource. Para poder enlazar una matriz de tipo List, a un objeto DataBinding y posteriormente mostrar todos los datos de esa matriz en dos TextBox llamados: tbxNombre y tbxApellidos, usando para ello dos botones (siguiente, anterior):
private List<Persona> MatrizPersonas = new List<Persona>(); private BindingSource Navegador = new BindingSource(); //Asignamos al BindingSource, el origen de datos. Navegador.DataSource = MatrizPersonas; //Vinculamos las cajas de texto al control BindingSource. tbxNombre.DataBindings.Add("Text", Navegador, NombrePersona"); tbxApellidos.DataBindings.Add("Text", Navegador, "ApellidosPersona");

A continuacin diseamos los eventos click de los dos botones:


private void btnSiguiente_Click(object sender, EventArgs e) { Navegador.MoveNext(); } private void btnAnterior_Click(object sender, EventArgs e) { Navegador.MovePrevious(); }

Si en lugar de asignar el DataBinding para controlar una matriz quisiramos hacerlo para acceder a un conjunto de datos almacenado en un DataSet:
tbxNombre.DataBindings.Add("Text", DataSetPersonas, "Personas.Nombre");

Previamente se ha asignado al DataBinding el DataSet (DataSetPersonas). Personas.Nombre, Personas es la tabla que contiene el DataSet y Nombre es el campo de la tabla.
Captulo 4. Programacin en ADO. Pgina 52

4.9. Control BindingNavigator


El control BindingSource, provee de varias funcionalidades para trabajar con conjuntos de datos. A partir de la versin 2005, Microsoft incorpor un nuevo objeto llamado BindingNavigator, que permite realizar funciones de navegacin por el conjunto de datos. Estas funciones son: Primer registro, ltimo, siguiente, anterior, nmero de registros totales en el conjunto de datos y posicin actual. Ejemplo: Uso BindingNavigator Es un control basado en un objeto ToolStrip que incluye los componentes mencionados anteriormente. Necesita de un objeto BindingSource para funcionar, el cual se vincula al objeto BindingNavigator por mediacin de su propiedad BindingSource. Prctica para crear un BindigNavigator: 1. Creamos un objeto SQLDataAdapter. Seleccionamos el origen de datos y definimos la consulta que se realizar sobre la base de datos empleados. Nuestro objetivo es obtener todos los registros de empleados mostrados por IdEmpleado, NombreEmpleado, ApellidosEmpleado, Nombre del Departamento Empleado y Nombre de la Categora del Empleado. 2. Vamos al men Datos y generamos el Conjunto de Datos. Lo Llamamos objDataSetEmpleados. Automticamente genera un objeto, cuyo nombre es el que le hemos suministrado pero al final le aade un 1 (objDataSetEmpleados1). 3. Incluimos en el formulario los Textbox y label que contendrn la informacin. 4. Incluimos un objeto BindingSource, llamado BindingSource1, al cual le asignamos a su propiedad DataSource, el objeto objDataSetEmpleados1 y a su propiedad DataMember, el nombre de la tabla que va a manejar. 5. Incluimos un objeto BindingNavigator, al cual le asignamos a su propiedad BindingSource, el objeto BindingSource1. 6. A continuacin seleccionamos la propiedad Text del DataBindings de cada Textbox y le asignamos el campo de la tabla que se mostrar. Usamos para ello los campos contenidos en el BindingSource1. Ver figura. 7. Es necesario cargar el DataSet Previamente. Para ello podemos utilizar el evento Load de la ventana en la cual introducimos las siguientes lneas:
objDataSetEmpleados1.Clear(); sqlDataAdapter1.Fill(objDataSetEmpleados1); Captulo 4. Programacin en ADO. Pgina 53

4.10. Programacin avanzada en ADO


4.10.1. Transacciones. Operaciones de RollBack en la Base de Datos. Espacio de nombres: System.Transactions Microsoft incluy a partir de Visual Studio 2005 un nuevo espacio de nombres, System.Transactions, que proporciona una medio unificado por el cual podemos acceder a recursos transaccionales, tales como la base de datos SQL Server y la herramienta de Microsoft para realizar el manejo de colas de mensajes MSMQ(Microsoft Message Queues). Una de las ventajas de estas clases es que la aplicacin puede utilizar recursos locales de bajo coste durante una ejecucin y luego autoincrementarse utilizando operaciones distribuidas la prxima vez que se ejecute, basadas en los recursos a los que el usuario final accede. El nuevo sistema de transacciones proporciona dos formas de hacer uso de transacciones como un cliente: explcitamente, utilizando derivados de la clase Transaction e, implcitamente, usando los mbitos de transacciones. 4.10.1.1. Transacciones Explcitas. Lo primero a tener en cuenta es que es necesario agregar como referencia al proyecto el espacio de nombres System.Transactions. Este espacio de nombres nos va a permitir usar la clase CommitableTransaction, que alberga los mtodos Commit y Rollback, utilizados para ejecutar la transaccin o deshacer la transaccin. La operacin queda vinculada a la conexin, lo que significa que estos dos mtodos slo actan sobre la conexin a la que son vinculados. Ejemplo Uso TransaccionesExplicitas. Como podemos ver en el ejemplo, si el borrado de las filas que cumplen la condicin se encuentra con el mtodo Rollback, a pesar de que al ejecutar el Comando con el mtodo ExecutenonQuery a provocado el borrado, la operacin es anulada. Por el contrario si colocamos el mtodo Commit, la operacin quedar ejecutada. 4.10.1.2. Transacciones Implcitas. Microsoft recomienda realizar transacciones implcitas en lugar de transacciones explcitas. La razn es que cuando se usan transacciones implcitas, las operaciones desde mltiples administradores de recursos pueden coexistir y operar unas con otras conforme a reglas predefinidas. Si utilizamos transacciones implcitas, el cdigo puede automticamente alistarse en una transaccin primaria si existe o puede crear una nueva transaccin si es necesario. Mucho ms til es que si el cdigo invoca a otro cdigo que tambin usa transacciones implcitas, la transaccin implcita no se acomete hasta que todas las transacciones anidadas decidan acometerse tambin.
Captulo 4. Programacin en ADO. Pgina 54

Las transacciones implcitas se realizan mediante la clase TransactionScope., que tiene un uso muy parecido a using. El cdigo que reside dentro de un mbito de transaccin es intrnsecamente transaccional, y accede a los administradores de recursos compatibles (como SQL Server 2005) desde dentro de un mbito de transaccin que utilizar transacciones implcitamente. Si el cdigo no llega a la lnea donde se llama al mtodo Complete en un TransactionScope, la transaccin creada dentro del mbito se eliminar y las transacciones primarias utilizadas a travs del nido se nominarn para eliminarse. Ejemplo Uso TransaccionesImplicitas Como podemos ver en el ejemplo, por mediacin de un using hemos declarado un mbito de transaccin implcita. Si el cdigo dentro del using no se llega a ejecutar en su totalidad porque hay un error, al no llegar a la sentencia AmbitoTransaccion.Complete(), no se ejecutar ninguna orden que se hubiera efectuado sobre el servidor de SQL. Ejemplo Uso TransaccionesAnidadas
static SqlConnection objConexion; private void btnAceptar_Click(object sender, EventArgs e) try { using (TransactionScope scope = new TransactionScope()) { using (objConexion = new SqlConnection("data source=portatil\\sqlexpress; initial catalog=Empresa; Integrated Security=SSPI;")) { objConexion.Open(); for (int x = 1; x < 8; x++) { DeleteEmpleado(x); } } scope.Complete(); } } catch (TransactionAbortedException) { MessageBox.Show("La operacin ha sido abortada porque alguna de las transacciones ha fallado"); } } private void DeleteEmpleado(int IdEmpleado) { using (TransactionScope scope = new TransactionScope()) { SqlCommand objComando = objConexion.CreateCommand(); objComando.CommandText = "DELETE Empleados WHERE IdEmpleado = " + IdEmpleado.ToString(); objComando.ExecuteNonQuery();

Captulo 4. Programacin en ADO.

Pgina 55

if (IdEmpleado < 5) scope.Complete(); } }

Quizs donde se pueda obtener un mayor rendimiento de las Transacciones sea en la posibilidad de deshacer transacciones anidadas, donde una de ellas provoca un error, o antes de que se ejecute se produce un problema, y es necesario deshacer todo lo anterior. En este ejemplo se crea un mbito primario y en su interior un bucle que se itera ocho veces; cada iteracin intenta eliminar un empleado. El mtodo DeleteEmpleado eliminar todos los empleados excepto aquellos que su IdEmpleado sea mayor de 5. Cada una de las iteraciones llam al mtodo Complete de la transaccin y se ejecut perfectamente, pero si un Id sobrepasa el valor de cinco, la transaccin se anula. Cuando llamamos a un Id que es mayor de cinco, la iteracin fall ya que no se llam a su mtodo Complete y es est iteracin la culpable de que se provoque la llamada a la excepcin TransactionAbortedException. Esto tiene como consecuencia que el mbito primario se detenga y su mtodo Complete nunca se cumpla.

4.10.2. Acceso Asncrono a la Base de Datos. Antes de Net Framework 2.0, si tenamos que acceder a una base de datos con un tiempo de espera para realizar la consulta muy alto, y queramos que la aplicacin no quedara suspendida, la llamada a la base de datos y la consulta a ejecutarse se incluan en un mtodo que era lanzado en segundo plano (subprocesamiento). A partir de Net Framework 2.0 Microsoft implemento mtodos en muchos objetos cuya finalidad es precisamente ejecutarse en hilos independientes, al margen de la aplicacin principal, para de esta forma liberar a nuestra aplicacin de procesos complejos. El caso que vamos a tratar es el del objeto SqlCommand, que incorpora a los mtodos: ExecuteNonQuery, ExecuteReader y ExecuteXmlReader la posibilidad de ejecutarse en segundo plano por mediacin de Begin/End. De esta forma podemos ejecutar los mtodos en modo asncrono, mientras la aplicacin sigue respondiendo a otros eventos que se pudieran producir. Ejemplo: Uso ADOAsincrono

Captulo 4. Programacin en ADO.

Pgina 56

Lo primero que podemos ver en el ejemplo es que la cadena de conexin incorpora un parmetro que permite ejecutar el comando en segundo plano:
SqlConnection conn = new SqlConnection("data source=(localhost); initial catalog=Empresa; Integrated Security=SSPI; Asynchronous Processing=true;");

El parmetro Asynchronous Processing a true le indica al objeto ADO que esta conexin utilizar subprocesamiento mltiple. Si no se activa este parmetro no lo permitir y cuando se llame al mtodo dar un error. El mtodo BeginExecuteReader inicia el proceso de ejecucin asincrnica de una instruccin de Transact-SQL o de un procedimiento almacenado que devuelve filas, para que otras tareas puedan ejecutarse simultneamente mientras se ejecuta la instruccin. Cuando termina la instruccin se debe de llamar al mtodo EndExecuteReader para finalizar la operacin y recuperar el valor de SqlDataReader devuelto por el comando. El mtodo BeginExecuteReader devuelve un valor inmediatamente, pero hasta que el cdigo ejecute la llamada al mtodo EndExecuteReader correspondiente, no debe ejecutar ninguna otra llamada que comience una ejecucin sincrnica o asincrnica con respecto al mismo objeto SqlCommand. Es decir, el objeto declarado en una llamada asncrona no puede utilizarse nuevamente hasta que la llamada finalice. Si se llama a EndExecuteReader antes de que finalice la ejecucin del comando, el objeto SqlCommand se bloquear hasta que la ejecucin termine. En la sintaxis de la llamada que hemos realizado en el ejemplo tenemos:
public IAsyncResult BeginExecuteReader (AsyncCallback callback, Object stateObject)

El mtodo asncrono BeginExecuteReader, devuelve un valor del tipo IAsyncResult, que se puede utilizar para sondear y/o esperar los resultados; este valor tambin es necesario al invocar a EndExecuteReader, que devuelve una instancia de SqlDataReader que se puede usar para recuperar las filas devueltas. Como parmetros de entrada tenemos: CallBack. El parmetro callback permite especificar un delegado de AsyncCallback al que se llama una vez finalizada la instruccin. Se puede llamar al mtodo EndExecuteReader desde este procedimiento de delegado o desde cualquier otra ubicacin de la aplicacin. Adems, se puede pasar cualquier objeto en el parmetro stateObject y el procedimiento de devolucin de llamada puede recuperar esta informacin mediante la propiedad AsyncState.

Captulo 4. Programacin en ADO.

Pgina 57

Observe que el texto del comando y los parmetros se envan al servidor de forma sincrnica. Si se enva un comando de gran tamao o un gran nmero de parmetros, este mtodo puede bloquearse durante las operaciones de escritura. Una vez enviado el comando, el mtodo devuelve un valor inmediatamente sin esperar una respuesta del servidor; es decir, las lecturas son asincrnicas. Aunque la ejecucin de comandos es asincrnica, la obtencin de valores sigue siendo sincrnica. Esto significa que las llamadas a Read pueden bloquearse si se requieren ms datos y se bloquea la operacin de lectura de la red subyacente. Dado que el procedimiento de devolucin de llamada se ejecuta desde un subproceso de fondo proporcionado por el motor en tiempo de ejecucin de Microsoft .NET, es muy importante que se controlen rigurosamente las interacciones entre los subprocesos desde las aplicaciones. Por ejemplo, no se debe interactuar con el contenido de un formulario desde el procedimiento de devolucin de llamada. En caso de que se tenga que actualizar el formulario, se debe volver al subproceso del formulario. Todos los errores que aparecen durante la ejecucin de la operacin se inician como excepciones en el procedimiento de devolucin de llamada. Las excepciones se deben controlar en el procedimiento de devolucin de llamada.

4.10.3. Ejecucin de procesos por lotes En este caso vamos a utilizar un objeto DataSet para que sea el responsable de actualizar los datos en el SQL por mediacin de un proceso por lotes. El ejemplo tiene tres partes muy concretas: la primera recupera los datos de la base de datos empresa, usando el DataSet (ADO Conectado); la segunda modifica filas existentes (las recuperadas en el DataSet), aade filas nuevas y elimina otras; y por ltimo, se llama al mtodo Update() del DataSet, que ejecuta todas las filas de la tabla dentro del DataSet y le aplica la instruccin correspondiente a su estado (aadir, borrar, insertar o modificar). Ejemplo: Uso ProcesoPorLotes Hay que tener en cuenta que los procesos por lotes tienen problemas de rendimiento cuando la cantidad de registros a modificar es muy grande. Cuando esto ocurre, el crear una instruccin SQL, ejecutarla y desplazarla al siguiente registro actualizado de la lista, aade una sobrecarga innecesaria a la operacin de actualizacin. Ahora podemos establecer el tamao del lote de comando. Si se establece en 0, el adaptador de datos utilizar la mxima capacidad de lote disponible. Si se determina un valor mayor de 1, el adaptador de datos realizar su actualizacin por lotes que contiene el nmero de instrucciones
Captulo 4. Programacin en ADO. Pgina 58

que previamente se indic. El nico tema importante es especificar la propiedad UpdateRowSource, con el valor de UpdateRowSource.None, en todos los comandos implicados en el proceso por lotes.

4.10.4. El uso de Link En LINQ to SQL se puede utilizar la tecnologa LINQ para tener acceso a las bases de datos de SQL igual que se obtendra acceso a una coleccin en memoria. Conocemos a este modelo de programacin como LINQ que proviene de .NET Language Integrated Query. Los desarrolladores podemos usar LINQ con cualquier fuente de datos. Podemos expresar consultas en los lenguajes de programacin que se elijan y opcionalmente transformar/incrustar los resultados de las consultas en el formato que se quiera, para de esta forma manipular fcilmente los resultados. Los lenguajes habilitados para LINQ pueden aportar seguridad de tipos y chequeo en tiempo de compilacin de las expresiones de consulta, y desarrollar herramientas que aporten intelisense y debugging cuando escriban cdigo de LINQ. LINQ soporta un modelo de extensibilidad muy rico que facilita la creacin de operadores eficientes para fuentes de datos. La versin 3.5 del .NET Framework viene con libreras que habilitan LINQ sobre objetos, XML y bases de datos. 4.10.4.1. LINQ to SQL. LINQ to SQL es una implementacin de O/RM(Object Relational Mapping -Mapeador de Objetos Relacionales-) que viene con la versin 3.5 del .NET Framework, y nos permite modelar bases de datos relacionales con clases de .NET. Podemos consultar bases de datos con LINQ, as como actualizar, aadir y borrar datos de ellas. Modelando bases de datos con LINQ to SQL: Visual Studio 2008 viene con un diseador de LINQ to SQL que nos aporta una forma fcil de modelar y visualizar una base de datos como un modelo de objeto de LINQ to SQL. Para utilizar el diseador hay que agregar al proyecto un nuevo elemento llamado: Clases de Link a SQL Usando ese diseador LINQ to SQL se puede crear fcilmente una representacin de la base de datos Northwind.

Captulo 4. Programacin en ADO.

Pgina 59

El diseo de arriba define cuatro clases: Products, Categoryes, Order y OrderDetail. Las propiedades de cada clase mapean las columnas de cada table en la base de datos. Cada instancia de esa clase representa una fila en las tablas. Las flechas entre las cuatro clases de arriba representan las asociaciones/relaciones entre las diferentes entidades. Son tpicamente modeladas como relaciones primary-key/foreign-key en la base de datos. La direccin de las flechas en el diseador indican si la relacin es uno-a-uno o uno-a-varios. Se aadiran propiedades fuertemente tipadas a las entidades basndose en esto. Por ejemplo, la clase Category de arriba tiene una relacin de uno-a-varios con la clase Product. Esto implica que tendr una propiedad Categories que es una coleccin de objetos Product con esa categora. La clase Product entonces tiene una propiedad Category que apunta a una instancia de la clase Category representando la categora a la que pertenece el producto. Esquema en el diseador para la base de datos Northwing:

Entendiendo la clase DataContext Cuando pulsamos el botn save del diseador de LINQ to SQL, Visual Studio generar clases .NET para representar las entidades y las relaciones de la base de datos que hemos modelado. Por cada archivo aadido a nuestra
Captulo 4. Programacin en ADO. Pgina 60

solucin por el diseador LINQ to SQL tambin se generar una clase DataContext. Esta clase es a travs de la cual realizaremos las consultas a las entidades de nuestra base de datos. Esta clase tendr propiedades que representarn a cada tabla que hemos modelado, as como mtodos para cada procedimiento almacenado que aadamos. Por ejemplo, en la imagen de la izquierda podemos ver la clase DataClasses1DataContext, que se ha generado a partir de las tablas que hemos incluido en la vista de diseo. Adems, podemos observar que ha generado una clase por cada una de las tablas que se han incorporado. Una vez diseada la estructura, podemos realizar las operaciones que vienen a continuacin: Consultar Datos:
DataClasses1DataContext objNort = new DataClasses1DataContext(); var objProductos = from p in objNort.Products where p.Categories.CategoryName == "Beverages" select p.ProductName; foreach (var obj in objProductos) Console.WriteLine(obj);

Modificar Datos:
DataClasses1DataContext objNoMo = new DataClasses1DataContext(); Products objPro = objNoMo.Products.Single(p => p.ProductName == "Tofu"); objPro.UnitPrice = 100; objPro.UnitsInStock = 100; objNoMo.SubmitChanges();

Insertar Datos:
DataClasses1DataContext objIn = new DataClasses1DataContext(); Products objProducto = new Products(); objProducto.ProductName = "Jamn"; objProducto.SupplierID = 3; objProducto.CategoryID = 2; objProducto.QuantityPerUnit = "18 - 500 g pkgs."; objProducto.UnitPrice = 20; objProducto.UnitsInStock = 100; objProducto.ReorderLevel = 0; objProducto.Discontinued = false; objIn.Products.InsertOnSubmit(objProducto);

Captulo 4. Programacin en ADO.

Pgina 61

//Guardamos los cambios en la base de datos objIn.SubmitChanges();

Borrar Datos:
//Borrar una fila usando Linq a SQL. DataClasses1DataContext objNoBo = new DataClasses1DataContext(); var objBorrar = from p in objNoBo.Products where p.ProductName.Contains("Jam") select p; if (objBorrar.Count() > 0) { objNoBo.Products.DeleteOnSubmit(objBorrar.First()); objNoBo.SubmitChanges(); }

Como hemos podido ver en cada uno de los ejemplos, salvo en el consultar, es necesario realizar un SubmitChanges para que los cambios realizados en la base de datos surtan efecto. Llamar a un procedimiento almacenado. Igual que realizamos operaciones sobre la base de datos con el equivalente en Linq a las instrucciones de SQL, tambin podemos llamar a procedimientos almacenados. La sintaxis es:
DataClasses1DataContext objNoBo = new DataClasses1DataContext(); var objProcedimiento = objNoBo.CustOrdersOrders(21);

En objProcedimiento almacena en una coleccin las filas devueltas por la ejecucin del procedimiento llamado CustOrdersOrders. En este procedimiento, pasndole un Id de cliente (21), devuelve los pedidos realizados por el cliente. Al ser una coleccin podramos usar un bucle foreach para recorrerla. 4.10.4.2. Linq a XML. Espacio de nombres System.XML.LINQ XML se ha adoptado ampliamente como un modo de dar formato a datos en diversos contextos. Por ejemplo, se puede encontrar XML en la web, en archivos de configuracin, en bases de datos, etc. LINQ to XML es un mtodo actualizado y rediseado para la programacin con XML. Proporciona capacidades de modificacin de documento en memoria de Document Object Model (DOM), y es compatible con expresiones de consulta LINQ. Aunque estas expresiones de consulta difieren sintcticamente de XPath, proporcionan una funcionalidad similar. LINQ to XML simplifica el cdigo XML al proporcionar una experiencia de consulta similar a SQL. Con un poco de estudio, los programadores
Captulo 4. Programacin en ADO. Pgina 62

acostumbrados a Transact-SQL, pueden aprender a escribir consultas concretas y eficaces en el lenguaje de programacin que prefieran. Una de las ventajas de usar LINQ, es que permite escribir menos cdigo, que a su vez resulte ms expresivo, compacto y eficaz. Se pueden usar expresiones de consulta de distintos dominios de datos simultneamente. LINQ to XML es una interfaz de programacin XML en memoria y habilitada para LINQ que permite trabajar con XML desde los lenguajes de programacin de .NET Framework. Se parece al Modelo de objetos de documento (DOM) en lo que respecta a la insercin del documento XML en la memoria. Puede consultar y modificar el documento; una vez modificado, puede guardarlo en un archivo o serializarlo y enviarlo a travs de Internet. Sin embargo, LINQ to XML es diferente de DOM: proporciona un nuevo modelo de objetos ms ligero con el que se trabaja ms fcilmente y que aprovecha las mejoras de lenguaje de Visual C# 2008. La ventaja ms importante de LINQ to XML radica en su integracin con Language-Integrated Query (LINQ). Esta integracin permite escribir consultas en el documento XML en memoria para recuperar colecciones de elementos y atributos. La capacidad de consulta de LINQ to XML es comparable en cuanto a funcionalidad (aunque no cuanto a sintaxis) a XPath y XQuery. La integracin de Linq en Visual C# 2008 proporciona una escritura ms rpida, comprobacin en tiempo de compilacin y una compatibilidad mejorada con el depurador. Otra ventaja es la capacidad de usar los resultados de la consulta como parmetros en constructores de objetos XElement y XAttribute, que habilita un mtodo eficaz para crear rboles XML. Este mtodo, denominado construccin funcional, permite que los desarrolladores transformen fcilmente rboles XML de una forma a otra. Los objetos bsicos seran XElement y XDocument, cuyo funcionamiento y salvando el tema de las consultas en Linq, es similar a las clases aplicadas al modelo DOM en Net Framework. Ejemplo Uso LinqaXML:
//Documento XML generado por XElement. XDocument srcTree = new XDocument(new XComment("TComentario"), new XElement("Root", new XElement("Child1", "data1"), new XElement("Child2", "data2"), new XElement("Child3", "data3"), new XElement("Child2", "data4"), new XElement("Info5", "info5"), new XElement("Info6", "info6"), new XElement("Info7", "info7"), new XElement("Info8", "info8")));

Captulo 4. Programacin en ADO.

Pgina 63

//Documento XML generado por una consulta. XDocument doc = new XDocument( new XComment("Esto es un comentario en la consulta"), new XElement("Root", from el in srcTree.Element("Root").Elements() where ((string)el).StartsWith("data") select el)); Console.WriteLine(doc); doc.Save("c:\\fichero.xml"); Console.ReadLine(); XElement xmlTree1 = new XElement("Root", new XElement("Child", 1), new XElement("Child", 2), new XElement("Child", 3), new XElement("Child", 4), new XElement("Child", 5), new XElement("Child", 6)); XElement xmlTree2 = new XElement("Root", from el in xmlTree1.Elements() where ((int)el >= 3 && (int)el <= 5) select el); xmlTree2.Save("c:\\Elemento.xml"); Console.WriteLine(xmlTree2); Console.ReadLine();

4.10.5. Detectar Mensajes del Servidor de Datos en la aplicacin. Muchas veces nos podemos encontrar ante la necesidad de mantener permanentemente actualizados unos datos que hemos solicitado al servidor. Una solucin podra ser utilizar un Timer para que ejecute un proceso de consulta al servidor, por ejemplo, cada 2 segundos. Esta solucin puede ser fcil de implementar en cdigo, pero a buen seguro, que resulta muy costosa en recursos y tiempo en el servidor de datos si la consulta afecta a varias tablas y adems es compleja. Una alternativa sera crear un trigger y una tabla complementaria que nos permitiera realizar una consulta muy simple para comprobar el estado de un valor en esa tabla complementaria. Por ejemplo, podemos crear una tabla y trigger como el que tenemos en este ejemplo:
CREATE TABLE dbo.TablaObservar (Version int not null) GO CREATE TRIGGER Observar ON dbo.Empleados AFTER INSERT, DELETE, UPDATE AS BEGIN SET NOCOUNT ON UPDATE dbo.TablaObservar SET Version = Version + 1 END GO

Captulo 4. Programacin en ADO.

Pgina 64

Este ejemplo crear una tabla llamada TablaObservar que contiene un campo llamado Version. Por mediacin del Trigger, cuando se produzca un cambio sobre la tabla Empleados (alta, baja o modificacin) se modificara el valor del campo sumando uno al valor que tuviera. Desde una aplicacin en .Net podemos configurar un Timer para que nos facilite en una consulta el valor del campo Version de TablaObservar:
private void timer1_Tick(object sender, EventArgs e) { SqlConnection Conexion = new SqlConnection(Cadena); //Consulta que devuelve el valor del campo Version en TablaObservar SqlCommand Comando = new SqlCommand("SELECT TOP 1 Version FROM TablaObservar", Conexion); if (Conexion.State != ConnectionState.Open) Conexion.Open(); SqlDataReader Cambios = Comando.ExecuteReader(); if (Cambios.HasRows) { Cambios.Read(); int Version = (int)Cambios[0]; if (UltimaVersion != Version) { this.Text = Version.ToString(); CargarDatos(); UltimaVersion = Version; } } Cambios.Close(); }

En este cdigo realizamos una consulta que nos devuelve slo la primera fila de TablaObservar. Esa primera fila contiene el valor de Version. Cuando se produce sobre la tabla una modificacin, alta o baja, el trigger programado suma uno al valor que tuviera ese campo. Como previamente hemos guardado el valor que tena en la variable UltimaVersion, automticamente detectar que los valores han cambiado y por tanto pasar a realizar una recargar de los datos que estamos visualizando en el DataGridView. De todas formas este sistema sigue realizando consultas al servidor, mucho menos pesadas y ms rpidas, pero al fin y al cabo son cargas de trabajo para el servidor. El proveedor de SqlClient, dispone de una clase llamada SqlDependency que se puede utilizar para poner en vigilancia una consulta. Si los datos de la consulta sufren cambios, el Servidor notifica esos cambios. Para ello, el servidor de Base de Datos, debe de permitir el Service Broker y tenerlo activado. Uso NotifiacionCambiosSQLServer.

Captulo 4. Programacin en ADO.

Pgina 65

4.10.6. Programa objetos de espera en la ejecucin de operaciones Muchas veces tenemos que ejecutar en el servidor procedimientos almacenados que tienen un alto coste en tiempo de ejecucin. Al ejecutarse en el servidor, desde nuestra aplicacin no podemos conocer el estado final del procedimiento. En la mayora de los casos lo recomendable sera colocar una barra de progreso que nos vaya indicando el tiempo transcurrido y que el usuario conozca el tiempo aprximado que resta para terminar la ejecucin. Podemos utilizar un sistema parecido al anterior con un objeto Timer para ir moviendo la barra y ralentizando su presentacin segn pasa el tiempo hasta conocer el fin del procedimiento. Pero es muy costo en recursos y ademas no representa claramente el avance en tiempo de ejecucin del procedimiento. Para resolver el problema contamos con el sistema de notificaciones del objeto SqlConnection, que por mediacin del evento InfoMessage, podemos detectar mensajes generados por el servidor de SQL con las instrucciones Raiserror y Print. Para probar un ejemplo del sistema de notificaciones, podemos crear el siguiente procedimiento almacenado en SQL:
CREATE PROCEDURE dbo.ProcedimientoLargo AS BEGIN SET NOCOUNT ON DECLARE @i INT SET @I = 1 WHILE @i<=10 BEGIN WAITFOR DELAY '00:00:01' --Simula una operacin costosa en el SQL. DECLARE @msg NVARCHAR(50) SET @msg = 'Progreso: ' + CONVERT(nvarchar, @i) --Es importante colocar el WITH NOWAIT para que los mensajes --No se acumulen y se notifiquen al final. RAISERROR (@MSG, 1, 1) WITH NOWAIT SET @i = 1 + @i END END

Este ejemplo provoca una demora para simular que se est realizando una operacin costosa en tiempo en el servidor. Es importante que el mensaje lanzado por Raiserror lleve la clausula With Nowait, que permite enviar el mensaje inmediatamente se ejecute la lnea. Por defecto, los mensajes se acumularan y seran enviados al final de la ejecucin del procedimiento almacenado. Este ejemplo cuenta hasta 10 y para. Cada vez que cuenta tiene una demora (Waitfor Delay) para simular que est trabajando. En cada pasada realiza una notificacin para que sea capturada desde nuestro programa.

Captulo 4. Programacin en ADO.

Pgina 66

Para capturar los mensajes implementamos el siguiente cdigo.


SqlConnection objConexion = new SqlConnection(); private void btnEjecutar_Click(object sender, EventArgs e) { BarraProgreso.Value = 0; objConexion.ConnectionString = @"Data Source=SERVIDOR;Initial Catalog=Empresa;Integrated Security=True"; //Programamos el evento InforMessage para el objeto Conexin. objConexion.InfoMessage += new SqlInfoMessageEventHandler(objConexion_InfoMessage); if (objConexion.State != ConnectionState.Open) objConexion.Open(); SqlCommand objProcedimiento = new SqlCommand("dbo.ProcedimientoLargo", objConexion); objProcedimiento.CommandType = CommandType.StoredProcedure; SqlDataReader objReader = objProcedimiento.ExecuteReader(); if (objReader.HasRows) while (objReader.Read()) { //Los resultados son procesados en este bloque de cdigo. } } private void objConexion_InfoMessage(object sender, SqlInfoMessageEventArgs e) { //Avanza la progressBar en la cantidad especificada por la //propiedad Step BarraProgreso.PerformStep(); }

En este caso hemos programado el evento click de un botn que dispara la ejecucin del procedimiento almacenado y un evento InfoMessage de la conexin que se lanza cuando un mensaje Raiserror es ejecutado dentro del procedimiento almacenado. En el InfoMessage simplemente llamamos al mtodo PerformStep de la barra para que incremente su valor en la cantidad especificada. El valor de Step esta definido en 10.

Captulo 4. Programacin en ADO.

Pgina 67

Ejercicios bsicos de ADO Conectado y ADO Desconectado: Ejercicio 1. Disear una aplicacin que tomando la base de datos de Empresa en Access, llene un DataGridView con la informacin del Nombre, Apellido y DNI. Usar ADO Desconectado. Ejercicio 2. Tomando las bases de datos de libros y telfonos, disear una aplicacin que permita mostrar los datos de cada unas de ellas en un DataGridView diferente. Usar Access con ADO Desconectado. Ejercicio 3. Idem que el anterior pero usando Sql Server. Ejercicio 4. Usar la base de datos Empresa. Realizar una aplicacin que manejando ADO Conectado, conecte con la Base de Datos de Empleados y permita modificar sus datos bsicos como son, Nombre, Apellidos y DNI. Realizarlo usando Access. Ejercicio 5. Usar la base de datos Empresa. Realizar una aplicacin que manejando ADO Conectado inserte y modifique empleados desde una ficha con textbox, en lugar de un DataGridView. Se incorporar un DataGridView donde se mostrarn todos los empleados de la base de datos Empresa, a peticin del usuario, por mediacin de un botn llamado CargarDatos. Ejercicio 6. Tomando como base el ejercicio 5, desarrollar una aplicacin que permita dar de alta los empleados, pero en lugar de introducir el Departamento y la Categora con un nmero, que su valor correspondiente sea seleccionado desde un ComboBox. Los ComboBox mostrarn el nombre del departamento o la categora segn corresponda. Adems, desde la aplicacin, podremos dar altas, modificaciones y eliminacin de categoras y departamentos. Hay que tener en cuenta que si se borra una categora o departamento que estn dados de alta en la tabla empleados, el SQL Server dar un error. Ejercicio 7. Realizar una aplicacin que permita mostrar en un DataGridView la informacin de la tabla empleados, seleccionando las columnas que queremos ver desde un panel con CheckBox. Cada CheckBox es una de las columnas de la tabla. Mostraremos las columnas que su CheckBox este activado. Ejercicio 8. Realizar una aplicacin que tomando como base la base de datos Librera, nos muestre en un DataGridView los libros comprados por cada cliente con toda la informacin del libro. Adems y en otro DataGridView mostrar toda la informacin de los libros que componen un tema. Tanto para el caso de clientes, como para el caso de temas, se introducir el Id correspondiente seleccionado desde un ComboBox.

Captulo 4. Programacin en ADO.

Pgina 68

También podría gustarte