Sie sind auf Seite 1von 242

Escuela de Informtica del Ejrcito

Contenido
1 INTRODUCCIN ............................................................................ 3
1.1
1.2

5.2.2
5.2.3
5.2.4
5.2.5
5.2.6
5.2.7

SIN NUEVO MODELO DE OBJETOS ............................................................... 3


MODELO DE OBJETOS DE ADO.NET.......................................................... 3
1.2.1
1.2.2
1.2.3
1.2.4
1.2.5
1.2.6
1.2.7

Proveedores de datos .NET ........................................................................... 4


Por qu usar clases y libreras separadas .................................................... 4
Cobertura de los proveedores de datos en este texto .................................... 5
Objetos conectados ........................................................................................ 5
Clases desconectadas .................................................................................... 7
Metadatos .................................................................................................... 10
Clases DataSet fuertemente tipadas ............................................................ 11

5.3

CREAR OBJETOS SQLCONNECTION ........................................................... 13


ABRIR OBJETOS SQLCONNECTION ........................................................... 13
CERRAR OBJETOS SQLCONNECTION......................................................... 13
HACER LIMPIEZA POR DONDE PASE .......................................................... 13
CADENAS DE CONEXIN ........................................................................... 14
2.5.1
2.5.2
2.5.3

2.6

AGRUPAMIENTO DE CONEXIONES............................................................. 18
2.6.1
2.6.2
2.6.3
2.6.4
2.6.5
2.6.6
2.6.7
2.6.8
2.6.9

2.7

Manipuladores de conexin y conexiones fsicas ........................................ 18


Qu es agrupamiento de conexiones ........................................................... 19
Cmo mejora su cdigo el agrupamiento.................................................... 19
Cundo se cierra una conexin agrupada .................................................. 19
Desactivar el agrupamiento de conexiones ................................................. 19
Controlar el agrupamiento .......................................................................... 20
Cmo determina ADO.NET si usar una conexin agrupada ...................... 20
Liberar manualmente conexiones agrupadas ............................................. 20
Otras opciones de agrupamiento................................................................. 21

6.1

INTRODUCCIN AL ACCESO A DATOS RELACIONAL ................................. 109


6.1.1
6.1.2
6.1.3

6.2

EXPLORADOR DE SERVIDORES ................................................................. 23


REFERENCIA DEL OBJETO SQLCONNECTION............................................. 24

6.3

2.9.1
2.9.2
2.9.3

6.4

3.1

USAR OBJETOS SQLCOMMAND EN EL CDIGO .......................................... 31


3.1.1
3.1.2
3.1.3
3.1.4
3.1.5
3.1.6
3.1.7
3.1.8

3.2

TRABAJAR CON SQLDATAREADER .......................................................... 38


3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.2.6
3.2.7
3.2.8
3.2.9

3.3

Examinar los resultados de su consulta ...................................................... 38


Cerrar el SqlDataReader ............................................................................ 38
Examinar el esquema de los resultados ...................................................... 39
Obtener datos ms deprisa con buscas basadas en ordinal........................ 40
Lectores fuertemente tipados ....................................................................... 41
Manejo de valores nulos .............................................................................. 41
Tipos SQL .................................................................................................... 43
Manejar mltiples conjuntos de resultados de una consulta ...................... 43
SQL Server 2005 y mltiples conjuntos de resultados activos .................... 44

7.1

REFERENCIA DEL OBJETO DATARELATION ............................................. 118

Propiedades de SqlCommand...................................................................... 49
Mtodos de SqlCommand ............................................................................ 50
Eventos de SqlCommand ............................................................................. 52
Propiedades de SqlDataReader .................................................................. 52
Mtodos de SqlDataReader ......................................................................... 53
Crear objetos SqlParameter ........................................................................ 55
Propiedades de la clase SqlParameter........................................................ 55

7.2
7.3

7.4
7.5

4.2

4.3

Crear un SqlDataAdapter............................................................................ 59
Recuperar resultados de una consulta ........................................................ 59
Hacer corresponder los resultados de la consulta con el DataSet ............. 62
Trabajar con lotes de consultas................................................................... 63
Obtener informacin de esquema ................................................................ 63

REFERENCIA DE LA CLASE SQLDATAADAPTER ........................................ 64


4.3.1
4.3.2
4.3.3

Propiedades ................................................................................................. 64
Mtodos ....................................................................................................... 66
Eventos ........................................................................................................ 67

8.1
8.2

CARACTERSTICAS DE LA CLASE DATASET .............................................. 71


5.1.1
5.1.2
5.1.3
5.1.4
5.1.5

5.2

Trabajar con datos desconectados .............................................................. 71


Recorrer, ordenar, buscar y filtrar.............................................................. 71
Trabajar con datos jerrquicos ................................................................... 71
Almacenar cambios ..................................................................................... 72
Integracin XML ......................................................................................... 72

USO DE OBJETOS DATASET ...................................................................... 72


5.2.1

Crear un objeto DataSet .............................................................................. 72

DATASET CON TIPO ................................................................................ 131


CREAR OBJETOS DATASET CON TIPO ...................................................... 131
8.2.1
8.2.2

8.3

8.4

Ventajas en tiempo de diseo .....................................................................136


Ventajas en tiempo de ejecucin ................................................................137
Consideraciones adicionales......................................................................137
Aadir manualmente tables y columnas.....................................................138
Mejorar los valores predeterminados ........................................................140

INTRODUCCIN A TABLEADAPTER ......................................................... 140


8.5.1
8.5.2
8.5.3
8.5.4
8.5.5

8.6

Aadir una fila ........................................................................................... 133


Localizar una fila ....................................................................................... 134
Editar una fila ............................................................................................ 134
Datos nulos.................................................................................................134
Datos jerrquicos .......................................................................................135
Otras caractersticas de DataSet, DataTable y DataRow .........................135
Aadir su propio cdigo.............................................................................135

CUNDO USAR DATASET CON TIPO ........................................................ 136


8.4.1
8.4.2
8.4.3
8.4.4
8.4.5

8.5

La forma difcil ........................................................................................... 131


La forma fcil .............................................................................................132

USAR DATASET CON TIPO ...................................................................... 133


8.3.1
8.3.2
8.3.3
8.3.4
8.3.5
8.3.6
8.3.7

5 TRABAJAR CON OBJETOS DATASET .................................... 71


5.1

Propiedades de la clase DataView.............................................................127


Mtodos de la clase DataView ...................................................................129
Eventos de la clase DataView ....................................................................129
Propiedades de la clase DataRowView .....................................................130
Mtodos de la clase DataRowView ............................................................130

8 DATASET CON TIPO Y TABLEADAPTER ............................. 131

Utilidad de la clase SqlDataAdapter ........................................................... 57


Anatoma de la clase SqlDataAdapter......................................................... 58

CREAR Y USAR OBJETOS SQLDATAADAPTER ........................................... 59


4.2.1
4.2.2
4.2.3
4.2.4
4.2.5

Crear objetos DataView .............................................................................124


Propiedad RowStateFilter ..........................................................................124
Clase DataRowView...................................................................................124
Examinar todas las filas mediante un DataView .......................................125
Buscar datos en un DataView ....................................................................125
Modificar objetos DataRowView ............................................................... 126
Usar un DataView para crear un nuevo DataTable ..................................126

CREAR OBJETOS DATAVIEW EN VISUAL STUDIO.................................... 127


REFERENCIA ........................................................................................... 127
7.5.1
7.5.2
7.5.3
7.5.4
7.5.5

QU ES UN SQLDATAADAPTER ................................................................ 57
4.1.1
4.1.2

Localizar una fila por su clave principal ...................................................121


Bsquedas ms dinmicas..........................................................................122
Bsquedas con comodines..........................................................................122
Delimitadores .............................................................................................122
Mtodos Select adicionales ........................................................................123

QUE ES UN OBJETO DATAVIEW .............................................................. 123


OBJETOS DATAVIEW EN CDIGO ............................................................ 124
7.3.1
7.3.2
7.3.3
7.3.4
7.3.5
7.3.6
7.3.7

4 RECUPERAR DATOS CON SQLDATAADAPTER ................... 57


4.1

Propiedades de la clase DataRelation .......................................................118

CARACTERSTICAS DE BSQUEDA Y FILTRADO DE DATATABLE ............. 121


7.1.1
7.1.2
7.1.3
7.1.4
7.1.5

Entrada de usuario en cadena de consulta ................................................. 46


Construccin de consultas e inyeccin SQL................................................ 46
Consultas parametrizadas ........................................................................... 47
Procedimientos almacenados ...................................................................... 48

REFERENCIA ............................................................................................ 49
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7

Aadir un DataRelation a un DataSet con tipo ......................................... 118


Aadir un DataRelation a un DataSet sin tipo .......................................... 118

7 ORDENAR, BUSCAR Y FILTRAR........................................... 121

CONSULTAS PARAMETRIZADAS ................................................................ 46


3.3.1
3.3.2
3.3.3
3.3.4

3.4

Crear un objeto SqlCommand ..................................................................... 31


Ejecutar una consulta que devuelve filas .................................................... 31
Recuperar un valor nico ............................................................................ 32
Ejecutar una consulta que no devuelve un conjunto de resultados............. 33
Ejecutar lotes de consultas de accin.......................................................... 33
Ejecutar consultas para recuperar datos XML ........................................... 34
Ejecutar una consulta en una transaccin .................................................. 34
Ejecutar una consulta asncronamente ....................................................... 35

Crear objetos DataRelation .......................................................................110


Localizar datos relacionados ..................................................................... 111
Validar datos con objetos DataRelation .................................................... 113
Objetos DataRelation autoreferencia ........................................................114
Relaciones varios a varios .........................................................................115
Objetos DataRelation en columnas basadas en expression .......................116
Propagar cambios ......................................................................................117
Apartarse de las consultas de unin ..........................................................118

CREAR OBJETOS DATARELATION EN VISUAL STUDIO ............................ 118


6.3.1
6.3.2
6.4.1

3 CONSULTAR LA BASE DE DATOS .......................................... 31

Consultas de unin ..................................................................................... 109


Consultas separadas .................................................................................. 109
Objetos DataRelation .................................................................................110

TRABAJAR CON OBJETOS DATARELATION EN EL CDIGO ....................... 110


6.2.1
6.2.2
6.2.3
6.2.4
6.2.5
6.2.6
6.2.7
6.2.8

Crear SqlCommand ..................................................................................... 21


Iniciar transacciones ................................................................................... 22
Recuperar informacin de esquema ............................................................ 22

Propiedades ................................................................................................. 24
Mtodos ....................................................................................................... 25
Eventos ........................................................................................................ 28

Propiedades de la clase DataSet..................................................................88


Mtodos de la clase DataSet ........................................................................90
Eventos de la clase DataSet .........................................................................93
Propiedades de la clase DataTable .............................................................94
Mtodos de la clase DataTable ....................................................................96
Eventos de la clase DataTable .....................................................................99
Propiedades de la clase DataColumn ........................................................100
Propiedades de la clase DataRow .............................................................103
Mtodo de la clase DataRow .....................................................................103
Propiedades de la clase UniqueConstraint................................................106
Propiedades de la clase ForeignKeyConstraint ........................................106

6 TRABAJO CON DATOS RELACIONALES ........................... 109

SQLCONNECTION COMO PUNTO INICIAL ................................................... 21


2.7.1
2.7.2
2.7.3

2.8
2.9

Qu es una cadena de conexin .................................................................. 14


Generadores de cadena de conexin ........................................................... 15
Seguridad de cadenas de conexin .............................................................. 17

Crear DataSet fuertemente tipados ..............................................................87


Crear un DataSet sin tipo............................................................................. 87

REFERENCIA DE CLASES ........................................................................... 88


5.4.1
5.4.2
5.4.3
5.4.4
5.4.5
5.4.6
5.4.7
5.4.8
5.4.9
5.4.10
5.4.11

2 CONECTAR CON SU BASE DE DATOS................................... 13


2.1
2.2
2.3
2.4
2.5

TRABAJAR CON OBJETOS DATASET EN VISUAL STUDIO ........................... 87


5.3.1
5.3.2

5.4

Examinar la estructura creada al llamar a Fill ........................................... 72


Examinar los datos que devuelve el SqlDataAdapter ..................................73
Validar datos en el DataSet .........................................................................73
Crear objetos DataTable en cdigo .............................................................75
Modificar el contenido de un DataTable .....................................................80
Opciones de serializacin y remoting de DataSet .......................................85

Crear un TableAdapter ..............................................................................140


Usar un DataAdapter .................................................................................141
Aadir ms consultas ................................................................................. 143
Aadir su propio cdigo.............................................................................143
Limitaciones de TableAdapter ...................................................................143

ELEGIR SU CAMINO ................................................................................. 144

9 ENVIAR CAMBIOS A LA BASE DE DATOS ......................... 145


9.1

ACTUALIZACIONES CON SQLCOMMAND PARAMETRIZADOS ................... 147


9.1.1
9.1.2
9.1.3
9.1.4

Enviar una nueva fila .................................................................................147


Actualizar una fila existente .......................................................................147
Borrar una fila existente ............................................................................150
Qu ms puede necesitar............................................................................151

ADO.NET 2.0
9.2
9.3

ACTUALIZACIONES CON DATAADAPTER ................................................ 151


CONFIGURACIN OBJETOS DATAADAPTER MANUALMENTE ................... 152
9.3.1
9.3.2
9.3.3

9.4

GENERAR LGICA DE ACTUALIZACIN CON SQLCOMMANDBUILDER .... 155


9.4.1
9.4.2
9.4.3

9.5

Examinar la lgica de actualizacin ......................................................... 156


Opciones para construir la lgica de actualizacin ................................. 156
Uso de procedimientos almacenados ........................................................ 156
Ventajas e inconvenientes.......................................................................... 157

EL REGRESO DE LOS DATAADAPTER ...................................................... 157


9.6.1
9.6.2

9.7
9.8
9.9
9.10

Cmo genera CommandBuilder la lgica de actualizacin...................... 155


Opciones de concurrencia ......................................................................... 155
Ventajas e inconvenientes.......................................................................... 155

GENERAR LA LGICA DE ACTUALIZACIN EN TIEMPO DE DISEO ........... 156


9.5.1
9.5.2
9.5.3
9.5.4

9.6

Parmetros vinculados .............................................................................. 153


Uso de procedimientos almacenados ........................................................ 153
Proporcionar su propia lgica .................................................................. 154

Llamar al asistente de configuracin de adaptador de datos ................... 157


Capa de acceso a datos con SqlDataAdapter ........................................... 158

ENVIAR CAMBIOS SOBRE SQLTRANSACTION .......................................... 160


USO DE LA COLECCIN TABLEMAPPINGS ............................................... 161
LA MEJOR FORMA DE ACTUALIZAR ........................................................ 162
REFERENCIA DEL OBJETO SQLCOMMANDBUILDER ................................ 162
9.10.1
9.10.2

Propiedades ............................................................................................... 162


Mtodos ..................................................................................................... 163

10 ESCENARIOS DE ACTUALIZACIN AVANZADOS .......... 165


10.1 ACTUALIZAR UNA FILA TRAS ENVIAR UN CAMBIO .................................. 165
10.1.1
10.1.2
10.1.3
10.1.4
10.1.5

Actualizar un DataRow tras enviar cambios............................................. 165


Consultas por lotes para actualizar tras enviar cambios ......................... 166
Recuperar datos con parmetros de salida ............................................... 167
Evento RowUpdated de la clase SqlDataAdapter ..................................... 168
Cdigo de ejemplo ..................................................................................... 168

10.2 RECUPERAR VALORES DE AUTO INCREMENTO ........................................ 169


10.2.1
10.2.2
10.2.3
10.2.4

Con SQL Server ......................................................................................... 169


Con Access................................................................................................. 169
Con secuencias Oracle .............................................................................. 170
Aplicaciones de ejemplo ............................................................................ 171

10.3 ENVIAR CAMBIOS JERRQUICOS ............................................................. 171


10.3.1
10.3.2

Enviar borrados e inserciones pendientes ................................................ 171


Valores auto incremento y datos relacionales .......................................... 172

10.4 AISLAR Y REINTEGRAR CAMBIOS ............................................................ 173


10.4.1
10.4.2

Ahorrar ancho de banda con GetChanges ................................................ 173


Cdigo de ejemplo ..................................................................................... 176

10.5 MANEJAR INTENTOS DE ACTUALIZACIN FALLIDOS ............................... 176


10.5.1
10.5.2
10.5.3
10.5.4
10.5.5

Planifique los conflictos por adelantado ................................................... 176


Informar al usuario de fallos..................................................................... 177
Obtener el contenido actual de filas en conflicto ...................................... 177
Si no se consigue a la primera .................................................................. 178
Aplicacin de ejemplo ............................................................................... 178

10.6 TRANSACCIONES DISTRIBUIDAS ............................................................. 179


10.6.1
10.6.2
10.6.3
10.6.4
10.6.5

Coordinadores de transacciones y administradores de recursos ............. 179


Transacciones distribuidas en .NET.......................................................... 180
Soporte de base de datos para transacciones distribuidas ....................... 180
Crear sus componentes.............................................................................. 180
Ejemplos .................................................................................................... 181

10.7 CONSULTAS POR LOTES .......................................................................... 181


10.7.1
10.7.2
10.7.3
10.7.4
10.7.5

Transacciones y actualizaciones por lotes ................................................ 182


Valor adecuado para UpdateBatchSize .................................................... 182
Eventos ...................................................................................................... 182
Refrescar filas............................................................................................ 183
Ejemplos .................................................................................................... 183

10.8 COPIA MASIVA SQL ............................................................................... 183


10.8.1
10.8.2
10.8.3
10.8.4
10.8.5

Crear un objeto SqlBulkCopy .................................................................... 183


Escribir datos en el servidor ..................................................................... 184
Correspondencia de datos con la tabla de destino ................................... 184
Enumeracin SqlBulkCopyOptions ........................................................... 184
Ejemplo ...................................................................................................... 185

10.9 OBJETOS DATASET Y TRANSACCIONES .................................................. 185


10.9.1

Ejemplo ...................................................................................................... 185

11 TRABAJAR CON DATOS XML ................................................ 187


11.1 LLENAR EL HUECO ENTRE XML Y ACCESO A DATOS ..............................187
11.2 LEER Y ESCRIBIR DATOS XML ................................................................187
11.2.1
11.2.2
11.2.3
11.2.4

Mtodos XML de la clase DataSet ............................................................. 187


Inferir esquemas ......................................................................................... 188
Propiedades que afectan al esquema ......................................................... 189
Almacenar en cache cambios en documentos XML ................................... 189

11.3 DATASET + XMLDOCUMENT = XMLDATADOCUMENT ..........................190


11.3.1
11.3.2
11.3.3

Clase XmlDataDocument ........................................................................... 190


Acceder a su DataSet como documento XML ............................................ 191
Almacenar actualizaciones al documento XML ......................................... 191

11.4 CARACTERSTICAS XML DE SQL SERVER 2005 ....................................191


11.4.1
11.4.2
11.4.3

Tipo XML de SQL Server 2005 .................................................................. 192


Ejecutar una consulta XPath para recuperar datos .................................. 193
Recuperar resultados XML usando XQuery .............................................. 195

11.5 RECUPERAR DATOS XML DESDE SQL SERVER 2000 .............................196


11.6 EL PROVEEDOR DE DATOS .NET SQL XML ...........................................197
11.6.1
11.6.2
11.6.3
11.6.4
11.6.5
11.6.6

SqlXmlCommand ........................................................................................ 198


SqlXmlAdapter ........................................................................................... 198
Consultas plantilla ..................................................................................... 198
Consultas XPath ......................................................................................... 199
Aplicar una transformacin XSL ............................................................... 201
Enviar actualizaciones ............................................................................... 201

11.7 UN EJEMPLO SENCILLO ...........................................................................201

12 APLICACIONES WINDOWS .................................................... 203


12.1 CREAR UN INTERFACE CON VINCULACIN DE DATOS ..............................203
12.1.1
12.1.2
12.1.3
12.1.4
12.1.5
12.1.6
12.1.7
12.1.8
12.1.9
12.1.10
12.1.11

Paso 1: Crear el DataSet con tipo ............................................................. 203


Paso 2: Aadir controles con vinculacin simple...................................... 203
Paso 3: Recuperar datos ............................................................................ 205
Paso 4: Recorrer los resultados................................................................. 206
Paso 5: Aadir y borrar elementos ............................................................ 207
Paso 6: Enviar cambios ............................................................................. 207
Paso 7: Aadir botones para Editar, Aceptar y Rechazar......................... 208
Paso 8: Ver datos hijos .............................................................................. 209
Paso 9: Enlazar un segundo formulario al mismo origen de datos ........... 211
Paso 10: Mejorar el interface .................................................................... 211
Paso 11: Si quiere que salga bien ............................................................. 213

12.2 CONSIDERACIONES DE DISEO DE APLICACIONES ...................................214


12.2.1
12.2.2
12.2.3
12.2.4

Recupere solo los datos que necesita ......................................................... 214


Estrategias de actualizacin ...................................................................... 214
Estrategias de conexin ............................................................................. 216
Trabajar con datos BLOB .......................................................................... 217

13 OTROS PROVEEDORES DE DATOS .NET ........................... 221


13.1 EL MODELO DE FACTORA DE PROVEEDOR ..............................................221
13.1.1
13.1.2
13.1.3
13.1.4

Limitaciones de los interfaces comunes de ADO.NET............................... 221


Solucin mediante el modelo de factora de proveedor ............................. 222
Limitaciones del modelo de factora .......................................................... 224
Descubrimiento de esquema de base de datos ........................................... 225

13.2 PROVEEDOR DE DATOS ODBC ...............................................................227


13.2.1
13.2.2
13.2.3
13.2.4

Conectar con la base de datos ................................................................... 227


Ejecutar consultas ...................................................................................... 227
Recuperar los resultados de una consulta ................................................. 229
Recuperar informacin de esquema........................................................... 229

13.3 PROVEEDOR DE DATOS OLE DB ............................................................230


13.3.1
13.3.2
13.3.3
13.3.4

Conectar con su base de datos ................................................................... 230


Ejecutar consultas ...................................................................................... 231
Recuperar resultados de una consulta ....................................................... 232
Recuperar informacin de esquema........................................................... 232

13.4 PROVEEDOR DE DATOS .NET PARA ORACLE...........................................233


13.4.1
13.4.2
13.4.3
13.4.4

Conectar con su base de datos ................................................................... 233


Ejecutar consultas ...................................................................................... 234
Clase OracleDataAdapter.......................................................................... 237
Recuperar informacin de esquema........................................................... 238

14 SIGLAS .......................................................................................... 241

Escuela de Informtica del Ejrcito

Introduccin

ADO.NET es un conjunto de libreras incluidas con el marco de trabajo .NET que le ayudan a
comunicarse con diferentes almacenes de datos desde aplicaciones .NET. Las libreras ADO.NET incluyen
clases para conectar con un origen de datos, enviar consultas y procesar los resultados. Tambin puede
usar ADO.NET como un cach de datos desconectado robusto y jerrquico para trabajas con los datos
fuera de lnea. El objeto desconectado central, DataSet, permite ordenar, buscar, filtrar, almacenar cambios
pendientes y navegar datos jerrquicos. DataSet tambin incluye una serie de caractersticas que llenan el
hueco entre el acceso a datos tradicional y el desarrollo XML. Los desarrolladores ahora pueden trabajar
con datos XML por medio de interfaces de acceso a datos tradicionales y viceversa.
En resumen, si est creando una aplicacin .NET que accede a datos necesitar ADO.NET.
Microsoft Visual Studio incluye un conjunto de caractersticas de acceso a datos en tiempo de diseo que
pueden ayudarle a crear aplicaciones de acceso a datos de una forma ms eficiente. Muchas de estas
caractersticas pueden ahorrarle tiempo durante el proceso de desarrollo generando grandes cantidades de
cdigo montono por Vd. Otras caractersticas mejoran el rendimiento de las aplicaciones creadas mediante
el almacenamiento de metadatos y lgica de actualizacin en el cdigo en lugar de obtener esta informacin
en tiempo de ejecucin. Lo crea o no, las caractersticas de acceso a datos de Visual Studio logran las dos
cosas.
Segn examinemos ADO.NET veremos tambin las caractersticas de Visual Studio que puede usar
para ahorrar tiempo y esfuerzo.

1.1 Sin nuevo modelo de objetos


Visual Studio 2005 abre un nuevo terreno para Microsoft al ser la primera gran revisin de Visual Studio
que no introduce un nuevo modelo de objetos de acceso a datos (Visual Studio 2003 no cuenta porque solo
contena pequeas mejoras respecto a Visual Studio 2002). Los desarrolladores que han afilado sus
capacidades ADO.NET con las versiones 1.0 y 1.1 del marco de trabajo .NET pueden seguir mejorndolas
con la versin 2.0.
Muchos desarrolladores nuevos en el marco de trabajo .NET pueden tener experiencia con la tecnologa
de acceso a datos previa de Microsoft ADO. ASO sirvi bien a muchos desarrolladores, pero carece de
caractersticas clave necesarias para crear aplicaciones ms potentes. Por ejemplo, cada vez ms
desarrolladores trabajan con datos XML. Aunque las ltimas versiones de ADO aadan caractersticas
XML, ADO no se cre par trabajar con estos datos. Por ejemplo, no permite separar la informacin de
esquema de los datos reales. Microsoft podra aadir ms caractersticas XML en versiones futuras de
ADO, pero ADO nunca manejar los datos XML de modo tan eficiente como ADO.NET porque ADO.NET se
dise pensando en XML y ADO no. El motor de cursor de ADPO permite pasar objetos Recordset
desconectados entre diferentes capas de la aplicacin, pero no es posible combinar el contenido de varios
objetos Recordset. ADO permite enviar cambios almacenados en cach a la base de datos, pero no es
posible controlar la lgica utilizada para este envo. Adems, el motor de cursor ADO, por ejemplo, no
proporciona una forma de enviar cambios pendientes a la base de datos por medio de procedimientos
almacenados. Como muchos administradores de base de datos slo permiten la modificacin por medio de
procedimientos almacenados, en muchos casos no es posible hacer cambios usando objetos Recordset.
Microsoft cre ADO.NET para enfrentarse a estos escenarios clave, junto con otros que se vern a lo
largo del texto.
ADO.NET est diseado para combinar las mejores caractersticas de sus predecesores, a la vez que
aade caractersticas pedidas por los desarrolladores mejor soporte para XML, acceso a datos
desconectados ms sencillo, ms control sobre las actualizaciones, y mayor flexibilidad de actualizaciones.

1.2 Modelo de objetos de ADO.NET


Ahora que comprende el objetivo de ADO.NET y dnde encaja en el conjunto de la arquitectura de Visual
Studio, es el momento de examinar ms de cerca esta tecnologa. En este captulo veremos brevemente el
modelo de objetos de ADO.NET y cmo se diferencia de tecnologas de acceso a datos previas de
Microsoft.
ADO.NET est diseado para ayudar a los desarrolladores a crear aplicaciones de base de datos
multicapa eficientes sobre intranets e Internet, y el modelo de objetos de ADO.NET proporciona los medios.
La figura 1.1 muestra las clases que forman este modelo de objetos. Una lnea de puntos parte el modelo en
dos. Los objetos a la izquierda de la lnea son los objetos conectados; estos objetos se comunican
directamente con la base de datos para gestionar la conexin y transacciones as como para recuperar
datos y enviar cambios. Los objetos a la derecha de la lnea son objetos desconectados que permiten a un
usuario trabajar con datos fuera de lnea.
3

ADO.NET 2.0

Los objetos que forman la parte desconectada del


modelo de objetos de ADO.NET no se comunican
directamente con los objetos conectados. Este es un
cambio importante respecto a los modelos de acceso a
datos previos. En ADO el objeto Recordset almacena
los resultados de las consultas. Puede llamar a su
mtodo Open para obtener los resultados de una
consulta y a su mtodo Update o UpdateBatch para
enviar los cambios almacenados en el Recordset a la
base de datos.

Fig.1. 1: Modelo de objetos ADO.NET

El DataSet de ADO.NET, que describiremos en breve, es comparable en funcionalidad al Recordset de


ADO. Salvo que DataSet no se comunica con la base de datos. Para cargar datos de la base de datos en un
DataSet se pasa el DataSet al mtodo Fill de un objeto ADO.NET conectado el DataAdapter. Igualmente,
para enviar los cambios pendientes que almacena el DataSet a la base de datos se pasa el DataSet al
mtodo Update del DataAdapter.

1.2.1

Proveedores de datos .NET

Un proveedor de datos .NET es una coleccin de clases diseadas para permitir la comunicacin con un
tipo concreto de almacn de datos. El marco de trabajo .NET incluye cuatro de estos proveedores: cliente
SQL, cliente Oracle, ODBC y OLE DB. Los proveedores de datos SQL y Oracle estn diseados para
comunicarse con bases de datos concretas: SQL Server y Oracle respectivamente. Los proveedores de
datos ODBC y OLE DB se denominan a menudo componentes puente porque sirven como puente con
tecnologas heredadas ODBC y OLE DB. Estos proveedores permiten comunicarse con varios almacenes
de datos por medio de controladores ODBC y proveedores OLE DB respectivamente.
Cada proveedor de datos .NET implementa las mismas clases bsicas ProviderFactory, Connection,
ConnectionStringBuilder, Command, DataReader, Parameter y Transaction aunque los nombres reales
dependen de cada proveedor. Por ejemplo, el proveedor de datos .NET para cliente SQL incluye una clase
SqlConnection y el proveedor ODBC una clase OdbcConnection. Independientemente del proveedor de
datos que se use, la clase Connection del proveedor implementa las mismas caractersticas bsicas por
medio de los mismos interfaces bsicos. Para abrir una conexin con un almacn de datos se crea una
instancia de la clase conexin del proveedor, se configura su propiedad ConnectionString y se llama a su
mtodo Open.
Cada proveedor de datos .NET tiene su propio espacio de nombres. Los cuatro proveedores incluidos en
el marco de trabajo .NET son subconjuntos del espacio de nombres System.Data en el que residen los
objetos desconectados. El proveedor de datos .NET para cliente SQL reside en el espacio de nombres
System.Data.SqlClient, el proveedor de datos ODBC reside en System.Data.Odbc, el proveedor de datos
OLE DB en System.Data.OleDb y el proveedor de datos para cliente Oracle en System.Data.OracleClient.
Como todos los proveedores de datos .NET implementan las mismas caractersticas bsicas el cdigo
escrito es muy similar independientemente del proveedor que se use. Como puede ver en los siguientes
fragmentos todo lo que necesita para cambiar de usar el proveedor de datos .NET ODBC a usar el
proveedor de datos .NET para cliente SQL es cambiar la clase que instancie y los contenidos de la cadena
de conexin para adaptarse a los estndares del proveedor.
//Abre y cierra un OdbcConnection
OdbcConnection conOdbc = new OdbcConnection();
conOdbc.ConnectionString = @Driver={SQL Server};Server=.\SQLExpress;
+ Database=Northwind;
conOdbc.Open();
...
conOdbc.Close();
//Abre y cierra un SqlConnection
SqlConnection conSQL =new SqlConnection();
conSQL.ConnectionString = @DataSource=.\SQLExpress;Initial Catalog=Northwind;
conSQL.Open();
...
conSQL.Close();

1.2.2

Por qu usar clases y libreras separadas

Ninguna tecnologa de acceso a datos previa de Microsoft utiliz libreras y clases separadas para
almacenes de datos diferentes. Muchos desarrolladores se preguntan por qu Microsoft ha hecho un
cambio tan importante; las tres razones principales son rendimiento, extensibilidad y proliferacin.

Escuela de Informtica del Ejrcito

Mejor rendimiento
Cmo mejora el rendimiento el paso a proveedores de datos .NET?. Cuando se escribe cdigo ADO
esencialmente se estn usando los interfaces ADO como intermediarios para comunicarse con su almacn
de datos. Se le dice a ADO que proveedor se quiere usar y ADO remite las llamadas al proveedor
adecuado. El proveedor ejecuta la accin solicitada y devuelve el resultado por medio de la librera ADO
adecuada.
Los proveedores de datos .NET no implican una capa intermedia. El programador se comunica
directamente con el proveedor de datos, que se comunica con el almacn de datos usando los interfaces de
programacin de bajo nivel del almacn de datos. La comunicacin con SQL Server usando el proveedor de
datos .NET para cliente SQL de ADO.NET es ms rpida que usando ADO y el proveedor OLE DB para
SQL Server porque implica una capa menos.

Mayor extensibilidad
Cuando SQL Server introdujo caractersticas XML el equipo de desarrollo de ADO se enfrent a un
interesante reto. Para aadir a ADO caractersticas que permitieran a los desarrolladores recuperar datos
XML desde SQL Server 2000 era necesario aadir nuevos interfaces al API OLE DB y al proveedor OLE DB
para SQL Server.
Los proveedores de datos .NET son ms fciles de ampliar. Slo necesitan dar soporte a los mismos
interfaces bsicos y pueden proporcionar caractersticas propias especficas del proveedor cuando sea
adecuado. El objeto SqlCommand expone los mismos mtodos y propiedades que OleDbCommand, pero
adems aade un mtodo para obtener los resultados de una consulta como XML.
SQL Server 2005 incluye una serie de nuevas caractersticas, incluyendo la posibilidad de que las
aplicaciones usen los servicios de notificacin cuando los resultados de una consulta cambien en el
servidor. En lugar de cambiar el funcionamiento interno de las clases comunes de ADO.NET simplemente
se han introducido dos nuevas clases en el proveedor de datos .NET para cliente SQL para aprovechar
estas caractersticas.

Proliferacin
Microsoft distribuy por primera vez los proveedores OLE DB para SQL Server, Access y Oracle con la
versin 2.0 de MDAC en Julio de 1998. Microsoft y otros desarrolladores han creado proveedores OLE DB
nativos para comunicarse con otros almacenes de datos, pero no hay un conjunto completo de proveedores
OLE DB. Si est usando ADO pero no un proveedor OLE DB creado por Microsoft hay una alta probabilidad
de que est usando en su lugar un controlador ODBC, un predecesor de OLE DB. Hay disponibles muchos
ms controladores ODBC, principalmente porque eran ms fciles de desarrollar. Muchos desarrolladores
sencillamente encontraron demasiado difcil crear sus propios proveedores OLE DB.
En comparacin, un proveedor de datos .NET es simple de escribir. Hay muchos menos interfaces que
implementar. Microsoft ha simplificado el proceso de creacin de proveedores para ADO.NET de modo que
los desarrolladores pueden crear proveedores de datos .NET con mucha ms facilidad. Mientras ms
proveedores de datos .NET haya, a ms orgenes de datos diferentes podr acceder mediante ADO.NET.

1.2.3

Cobertura de los proveedores de datos en este texto

Como todos los proveedores de datos .NET implementan los mismos interfaces base, no es necesario
explicar el uso de todos ellos. Nos centraremos en uno: el proveedor de datos .NET para cliente SQL.
El Captulo 13 discute las caractersticas de los otros tres proveedores de datos .NET, as como el
modelo comn de proveedor. El Captulo 11 trata el proveedor de datos .NET para SQL XML para mostrar
el uso de algunas de las caractersticas relacionadas con XML de ADO.NET. Como el proveedor de datos
.NET las SQL XML no ofrece nuevas caractersticas y omite muchas clases incluidas en otros proveedores
por lo general no se considera un proveedor completo.
Cuando se discuta una clase comn a todos los proveedores de datos .NET a menudo se usar su
nombre independiente de proveedor, por ejemplo, DataAdapter en lugar de OdbcDataAdapter o
SqlDataAdapter.

1.2.4

Objetos conectados

El modelo de objetos ADO.NET incluye clases diseadas para ayudarle a comunicarse directamente con
su origen de datos. Denominar a estas clases, que aparecen en el lado izquierdo de la figura 1.1, como
clases conectadas de ADO.NET. La mayora de estas clases representan conceptos bsicos de acceso a
datos como la conexin fsica, una consulta y los resultados de una consulta.

ADO.NET 2.0

Clase ProviderFactory
La clase ProviderFactory es una novedad de ADO.NET 2.0 y acta como una factora de objetos,
permitiendo crear instancias de otras clases. Cada clase ProviderFactory ofrece un mtodo Create que crea
objetos Connection, ConnectionStringBuilder, Command, Parameter, DataAdapter y CommandBuilder.

Clase Connection
Un objeto Connection representa una conexin con un origen de datos. Puede especificar el tipo de
origen de datos, su ubicacin y otros atributos por medio de las propiedades de esta clase. Un objeto
Connection acta como un conducto mediante el cual otros objetos, como DataAdapter y Command, se
comunican con la base de datos para enviar consultas y recuperar resultados.

Clase ConnectionStringBuilder
La clase ConnectionStringBuilder es nueva en ADO.NET 2.0 y simplifica el proceso de creacin de
cadenas de conexin para un proveedor de datos .NET. Cada clase ConnectionStringBuilder expone
propiedades que corresponden con opciones disponibles en la cadena de conexin del proveedor de datos
.NET. Una vez creada la cadena de conexin usando esta clase se puede acceder a ella mediante la
propiedad ConnectionString.

Clase Command
Los objetos Command representan una consulta contra una base de datos, una llamada a un
procedimiento almacenado o una peticin directa para recuperar los contenidos de una tabla concreta.
Las bases de datos soportan muchos tipos de consultas. Algunas consultas recuperan filas de datos
haciendo referencia a una o ms tablas o vistas o llamando a un procedimiento almacenado. Otras
consultas modifican filas de datos, y otras manipulan la estructura de la base de datos creando o
modificando objetos como tablas, vistas o procedimientos almacenados. Puede usar un objeto Command
para ejecutar cualquiera de estos tipos de consultas contra una base de datos.
El uso de un objeto Command para consultar una base de datos es bastante directo. Se asigna a la
propiedad Connection un objeto conexin que se conecta con la base de datos y se especifica el texto de la
consulta en la propiedad CommandText.
El texto de la consulta puede ser una consulta SQL estndar, el nombre de una tabla o vista o un
procedimiento almacenado. La propiedad CommandType del objeto Command indica el tipo de consulta. La
clase Command ofrece varios mtodos para ejecutar la consulta. Si la consulta no retorna filas llame a
ExecuteNonQuery. El mtodo ExecuteReader devuelve un DataReader que puede usar para examinar las
filas devueltas por la consulta. Si solo quiere recuperar la primera fila de la primera columna devuelta por la
consulta, puede ahorrar algunas lneas de cdigo empleando el mtodo ExecuteScalar. El objeto
SqlCommand incluye un cuarto mtodo de ejecucin, ExecuteXmlReader, similar a ExecuteReader pero
diseado para manejar consultas que devuelven resultados en formato XML.

Clase DataReader
La clase DataReader est diseada para ayudarle a recuperar y examinar las filas devueltas por su
consulta lo ms rpidamente posible. Puede usar la clase DataReader para examinar los resultados de una
consulta fila a fila. Cuando pasa a la fila siguiente se descartan los contenidos de la anterior. El objeto
DataReader no permite actualizaciones; los datos que contiene son de solo lectura. Como la clase
DataReader soporta un conjunto de caractersticas mnimo es extremadamente rpido y ligero.

Clase Transaction
En ocasiones querr agrupar un conjunto de cambios en una base de datos y tratarlos como una nica
unidad de trabajo. En programacin de bases de datos esta unidad de trabajo se denomina transaccin.
La clase Connection tiene un mtodo BeginTransaction que puede usar que puede usar para crear
objeto Transaction. Un objeto Transaction se usa para consignar o cancelar los cambios hechos en una
base de datos durante el tiempo de vida del objeto.

Clase Parameter
Supongamos que quiere pedir a la tabla Orders todos los pedidos de un cliente concreto. Su consulta
sera algo as:
SELECT OrderID, OrderDate FROM Orders
WHERE CustomerID = ALFKI

El valor usado para la columna CustomerID en la clusula WHERE depende del cliente cuyos pedidos
quiere examinar. Pero si usar este tipo de consulta, debe modificar su texto cada vez que quiere examinar
los pedidos de un cliente diferente.
Para simplificar el proceso de ejecutar este tipo de consultas puede reemplazar el valor por un indicador
de parmetro:
6

Escuela de Informtica del Ejrcito

SELECT OrderID, OrderDate FROM Orders


WHERE CustomerID = @CustomerID

Entonces, antes de ejecutar la consulta se proporciona un valor para el parmetro. Muchos


desarrolladores confan fuertemente en las consultas parametrizadas porque pueden ayudar a simplificar la
programacin y crear un cdigo ms eficiente.
Para usar un objeto Command parametrizado se crea un objeto Parameter para cada uno de los
parmetros de la consulta y se aaden a la coleccin Parameters del objeto Command. La clase Parameter
de ADO.NET expone propiedades y mtodos que permiten definir el tipo de datos y valor de los parmetros.
Para trabajar con un procedimiento almacenado que devuelva datos por medio de parmetros de salida
asigne el valor adecuado de la enumeracin ParameterDirection a la propiedad Direction del objeto
Parameter.

Clase DataAdapter
La clase DataAdapter representa un nuevo concepto para los modelos de acceso a datos de Microsoft;
no tiene un equivalente en ADO o DAO.
Los objetos DataAdapter actan como un pueden entre la base de datos y los objetos desconectados en
el modelo de objetos ADO.NET. El mtodo Fill, que es parte de la clase DataAdapter, proporciona un
mecanismo eficiente para recuperar los resultados de una consulta en un DataSet o DataTable de forma
que se pueda trabajar con los datos fuera de lnea. Tambin puede usar objetos DataAdapter para enviar
los cambios pendientes almacenados en objetos DataSet a la base de datos.
La clase DataAdapter de ADO.NET expone una serie de propiedades que son realmente objetos
Command. Por ejemplo, la propiedad SelectCommand contiene un objeto Command que representa la
consulta que se usa para rellenar el objeto DataSet. Tambin existen propiedades UpdateCommand,
InsertCommand y DeleteCommand que se corresponden con objetos Command que se usan para enviar
filas modificadas, nuevas o borradas a su base de datos respectivamente.
Estos objetos Command proporcionan funcionalidad de actualizacin que en mecanismos anteriores era
automtica (o automgica" segn el punto de vista). Por ejemplo, cuando se ejecuta una consulta en ADO
para generar un Recordset, el motor de cursor pide a la base de datos metadatos sobre la consulta para
determinar de donde proceden los resultados. ADO usa estos metadatos para crear la lgica de
actualizacin para traducir los cambios en el Recordset a cambios en la base de datos.
Entonces para qu cuatro propiedades separadas en la clase DataAdapter?. Para permitirle definir su
propia lgica de actualizacin. La funcionalidad de actualizacin en ADO y DAO es muy limitada porque
ambos modelos traducen los cambios en los Recordset en consultas de accin que hacen referencia
directamente a tablas de la base de datos. Para mantener la seguridad e integridad de los datos muchos
administradores de base de datos restringen el acceso a las tablas de modo que la nica forma de cambiar
los contenidos sea llamar a procedimientos almacenados. ADO y DAO no saben cmo enviar cambios
usando procedimientos almacenados ni proporcionan mecanismos que permitan especificar su propia lgica
de actualizacin. DataAdapter de ADO.NET si.
Con un objeto DataAdapter puede configurar las propiedades UpdateCommand, InsertCommand y
DeleteCommand para llamar a procedimientos almacenados que modificarn, aadirn o eliminarn filas de
la tabla adecuada en la base de datos. Entonces podr llamar simplemente al mtodo Update del objeto
DataAdapter y ADO.NET usar los objetos Command creados para enviar los cambios almacenados en el
DataSet a la base de datos.
Como ya se ha indicado, la clase DataAdapter rellena tablas del objeto DataSet y lee los cambios
almacenados y los enva a la base de datos. Para hacer un seguimiento de qu va donde un DataSet tiene
algunas propiedades de soporte. La coleccin TableMappings es una propiedad que se usa para registrar
qu tabla de la base de datos se corresponde con cul del DataSet. Cada correspondencia entre tablas
tiene una propiedad similar para hacer corresponder columnas, adecuadamente denominada coleccin
ColumnMappings.

1.2.5

Clases desconectadas

Ya ha visto que puede usar las clases conectadas de un proveedor de datos .NET para conectar con un
origen de datos, enviar consultas y examinar sus resultados. Sin embargo, estas clases conectadas
permiten examinar los datos solo en forma de un flujo de datos solo de avance y de solo lectura. Qu pasa
si quiere ordenar, buscar, filtrar o modificar los resultados?.
El modelo de objetos de ADO.NET incluye clases para proporcionar estas funcionalidades. Estas clases
actan como un cach de datos fuera de lnea. Una vez obtenidos los resultados de la consulta en un
DataTable puede cerrar la conexin con el origen de datos y seguir trabajando con los datos. Como se
mencion anteriormente, como estas clases no necesitan una conexin viva con la base de datos se
denominan clases desconectadas.
7

ADO.NET 2.0

Clase DataTable
La clase DataTable de ADO.NET permite examinar datos por medio de colecciones de filas y columnas.
Puede almacenar los resultados de una consulta en un DataTable llamando al mtodo Fill de un objeto
DataAdapter:
string cadcon, consulta;
cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind;
+ Integrated Security=true;
consulta = SELECT CustomerID, CompanyName FROM Customers;
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tabla = new DataTable();
adaptador.Fill(tabla);

Una vez obtenidos los datos de la base de datos y almacenados en un objeto DataTable este objeto est
desconectado del servidor. Puede examinar los contenidos del DataTable sin crear ningn trfico entre la
aplicacin y la base de datos. Trabajando con los datos fuera de lnea no es necesario tener una conexin
viva, pero recuerde que no ver los cambios hechos por otros usuarios despus de ejecutar la consulta.
La clase DataTable contiene colecciones de otros objetos desconectaos que veremos poco ms
adelante. Se accede a los contenidos de un DataTable por medio de su propiedad Rows, que devuelve una
coleccin de objetos DataRow. Si quiere examinar la estructura se usa la propiedad Columns para
recuperar una coleccin de objetos DataColumn. La clase DataTable tambin permite definir limitaciones,
como clave principal, sobre los datos almacenados. Puede acceder a estas limitaciones por medio de la
propiedad Constraints.

Clase DataColumn
Cada DataTable tiene una coleccin Columns, que es un contenedor para objetos DataColumn. Como
implica su nombre, un objeto DataColumn se corresponde con una columna de la tabla. Sin embargo, un
objeto DataColumn no contiene realmente datos; almacena informacin sobre la estructura de la columna.
Este tipo de informacin, datos sobre datos, se denomina habitualmente metadatos. Por ejemplo,
DataColumn expone una propiedad DataType que describe el tipo de datos que almacena la columna. La
clase DataColumn tiene otras propiedades, como ReadOnly, AllowDBNull, Unique, Default y AutoIncrement
que permiten controlar si los datos en la columna se pueden actualizar, restringen el contenido de la
columna o indican cmo se deben generar valores para nuevas filas.
La clase DataColumn expone tambin una propiedad Expression que puede usar para definir cmo se
calculan los datos en la columna. Es habitual que una columna se base en una expresin en lugar de los
contenidos de una columna de la base de datos. Por ejemplo, en la base de datos de ejemplo Northwind
cada fila de la tabla Order Details contiene una columna UnitPrice y una columna Quantity.
Tradicionalmente si se quera examinar el coste total el elemento de pedido se aada una columna
calculada a la consulta. El siguiente ejemplo de SQL define una columna calculada llamada TotalElemento:
SELECT OrderID, ProductID, Quantity, UnitPrice, Quantity * UnitPrice AS TotalElemento
FROM [Order Details]

El inconveniente de esta tcnica es que el motor de base de datos realiza los clculos solo en el
momento de la consulta. Si modifica los contenidos de UnitPrice o Quantity en el objeto DataTable, el valor
de TotalElemento no cambia.
La clase DataColumn de ADO.NET define una propiedad Expression para manejar este escenario de
una forma ms elegante. Cuando se comprueba el valor de un objeto DataColumn en base a una expresin,
ADO.NET evala la expresin y devuelve un valor recin calculado. De esta forma, si actualiza el valor de
cualquiera de las columnas que intervienen en la expresin, el valor de la columna calculada se mantiene
preciso. Esta es la forma de usar la propiedad Expression:
DataColumn columna
columna.ColumnName
columna.DataType =
columna.Expression

= new DataColumn();
= TotalElemento;
typeof(Decimal);
= UnitPrice * Quantity;

Clase Constraint
La clase DataTable tambin proporciona un modo de colocar limitaciones sobre los datos almacenados
en local dentro de un objeto de este tipo. Por ejemplo, puede crear un objeto Constraint que asegure que los
valores de una columna, o de varias, son nicos en el DataTable. Los objetos Constraint se mantienen en la
coleccin Constraints del DataTable.

Clase DataRow
Para acceder a los valores reales almacenados en un objeto DataTable se usa la coleccin Rows, que
contiene objetos DataRow. Para examinar los datos almacenados en una columna concreta de una fila
8

Escuela de Informtica del Ejrcito

especfica se usa la propiedad Item del objeto DataRow. La clase DataRow proporciona varias sobrecargas
de la propiedad Item; puede especificar la columna por su nombre, ndice u objeto DataColumn asociado.
En C# esta propiedad es el indexador de la clase, por lo que se puede usar de la siguiente manera:
DataRow fila = tabla.Rows[0];
Console.WriteLine(fila[0]);
Console.WriteLine(fila[CustomerID]);
Console.WriteLine(fila[tabla.Columns[CustomerID]]);

En lugar de devolver datos solo de la fila actual, DataTable hace que todas las filas estn disponibles por
medio de una coleccin de DataRow. Para examinar los contenidos de un DataTable se recorre esta
coleccin por medio de la propiedad Rows:
string consulta, cadcon;
cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind;
+ Integrated Security=True;
Consulta = SELECT CustomerID, CompanyName FROM Customers;
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tabla = new DataTable();
adaptador.Fill(tabla);
foreach(DataRow fila in tabla.Rows)
Console.WriteLine(row[CustomerID]);

La clase DataRow es tambin el punto inicial para sus actualizaciones. Por ejemplo, puede llamar al
mtodo BeginEdit de un objeto DataRow, cambiar el valor de algunas columnas en la fila por medio de la
propiedad Item y llamar al mtodo EndEdit para guardar los cambios. Al llamar al mtodo CancelEdit de un
objeto DataRow se cancelan los cambios hechos en la edicin actual. La clase DataRow tambin expone
mtodos para borrar o eliminar un elemento de la coleccin de DataRows.
Cuando se cambian los contenidos de una fila el DataRow almacena estos cambios de modo que se
pueden enviar posteriormente a la base de datos. As, cuando se cambia el valor de una columna en una
fila el DataRow mantiene el valor original adems del nuevo para actualizar la base de datos. La propiedad
Item de un objeto DataRow tambin permite examinar el valor original de una columna cuando la fila tiene
un cambio pendiente.

Clase DataSet
Un objeto DataSet, como su nombre indica, contiene un conjunto de datos. Puede imaginarlo como un
contenedor para una serie de objetos DataTable almacenados en su coleccin Tables. Recuerde que
ADO.NET se cre para ayudar a los desarrolladores a crear gantes aplicaciones de base de datos
multicapa. En ocasiones querr acceder a un componente que se ejecuta en un servidor de capa media
para recuperar el contenido de mltiples tablas. En lugar de tener que llamar repetidamente al servidor para
obtener los datos tabla a tabla puede empaquetar todos los datos en un nico objeto DataSet y devolverlo
en una nica llamada. Pero un objeto DataSet hace bastante ms que actuar como contenedor para objetos
DataTable.
Los datos almacenados en un objeto DataSet estn desconectados de la base de datos. Cualquier
cambio hecho en los datos se almacena en su DataRow. Cuando es el momento de enviar estos cambios a
la base de datos puede que no sea eficiente enviar el DataSet completo al servidor. Puede usar el mtodo
GetChanges para extraer solo las filas modificadas en el DataSet. De esta forma se mueven menos datos
entre diferentes procesos o servidores.
La clase DataSet expone tambin un mtodo Merge que puede actuar como complemento de
GetChanges. El servidor de capa media que use para enviar cambios a la base de datos usando el DataSet
ms pequeo obtenido por el mtodo GetChanges puede devolver un DataSet que contenga datos recin
recuperados. Puede usar el mtodo Merge para combinar los contenidos de dos objetos DataSet en uno
solo. Este es otro ejemplo que muestra que ADO.NET se dise pensando en aplicaciones multicapa.
Puede crear un objeto DataSet y cargar su coleccin Tablas con informacin mediante programacin sin
tener que comunicarse con una base de datos. No necesita comunicarse con la base de datos hasta que no
est listo para enviar las filas nuevas.
La clase DataSet tiene caractersticas que le permiten escribir en y leer de un archivo o un rea de
memoria. Puede guardar solo el contenido del DataSet, solo su estructura o ambos. ADO.NET almacena
estos datos como documento XML. Como ADO.NET y XML estn tan relacionados, el movimiento de datos
entre objetos DataSet y documentos XML es inmediato. De esta forma puede aprovechar una de las ms
potentes caractersticas de XML: su capacidad para transformar fcilmente la estructura de los datos. Por
ejemplo, puede usar una plantilla XSL para convertir datos exportados a un documentos XML en HTML.

ADO.NET 2.0

Clase DataRelation
Las tablas de una base de datos por lo general estn relacionadas de alguna manera. Por ejemplo, en la
base de datos Northwind cada entrada de la tabla Orders se relaciona con una entrada de la tabla
Customers, de modo que se puede determinar qu cliente hizo qu pedido. Probablemente querr usar
datos relacionados de mltiples tablas en su aplicacin. La clase DataSet maneja datos de objetos
DataTable relacionados con ayuda de la clase DataRelation.
La clase DataSet expone una propiedad Relations que es una coleccin de objetos DataRelation. Puede
usar un objeto DataRelation para indicar una relacin entre diferentes objetos DataTable de un DataSet.
Una vez creado un objeto DataRelation se usa cdigo como el siguiente para recuperar un array de pedidos
que corresponden a un cliente concreto.
DataSet ds;
DataTable tblClientes, tblPedidos;
DataRelation relacin;
//Crea e inicializa el DataSet
Relacin = ds.Relations.Add(Clientes_Pedidos, tblClientes.Columns[CustomerID],
tblPedidos.Columns[CustomerID]);
foreach(DataRow filaCliente in tblClientes.Rows){
Console.WriteLine(filaCliente[CompanyName]);
foreach(DataRow filaPedido in filaCliente.GetChildRows(relacin))
Console.WriteLine(
{0}, filaPedido[OrderID]);
Console.WriteLine();
}

Los objetos DataRelation exponen tambin propiedades que permiten imponer la integridad referencial.
Por ejemplo, puede configurar un objeto DataRelation de modo que si modifica el valor del campo de clave
principal en la fila madre el cambio se propague en cascada automticamente a las filas hijas. Tambin
puede configurar su objeto DataRelation de modo que si borra una fila en un DataTable las filas
correspondientes de cualquier objeto DataTable hijo, tal como lo defina la relacin, se borran
automticamente.

Clase DataView
Una vez recuperados los resultados de una consulta en un objeto DataTable puede usar un objeto
DataView para ver los datos de formas diferentes. Si quiere ordenar los contenidos de un objeto DataTable
en base a una columna sencillamente configure la propiedad Sort del objeto DataView con el nombre de la
columna. Tambin puede configurar la propiedad Filter de un DataView de modo que solo sean visibles las
filas que cumplen ciertos criterios.
Puede usar varios objetos DataView a la vez para examinar el mismo DataTable. Por ejemplo puede
tener dos rejillas en un formulario, una que muestre todos los clientes en orden alfabtico y la otra
mostrando las filas ordenadas por un campo diferente, como el pas. Para mostrar cada vista se vincula
cada objeto DataView con una rejilla. Esta caracterstica hace innecesario manejar dos copias separadas de
los datos en estructuras separadas.

1.2.6

Metadatos

ADO.NET permite elegir entre usar un par de lneas de cdigo y dejar que ADO.NET determine
automticamente la estructura de los resultados, o usar ms cdigo que incluye metadatos sobre la
estructura de los resultados de la consulta.
Por qu elegir la opcin que implica escribir ms cdigo?. Las ventajas principales son un incremento
en la funcionalidad y un mayor rendimiento. Pero cmo puede aumentar el rendimiento tener que escribir
ms cdigo?. Esto no parece lgico no?.
Salvo que est escribiendo una herramienta de consulta a medida por lo general conocer la estructura
de los resultados de la consulta. Para recuperar los resultados de la consulta y almacenar los datos,
ADO.NET necesita conocer la estructura de estos datos, nmero de columnas, sus tipos, nombres, etc.
Puede proporcionar esta informacin mediante programacin u obligar a ADO.NET a pedrsela al
proveedor. El cdigo ser ms rpido usando la primera opcin porque pedir la informacin al proveedor en
tiempo de ejecucin puede provocar una penalizacin importante.
Escribir cdigo para preparar la estructura del DataSet puede ser tedioso. Afortunadamente, Visual
Studio incluye caractersticas de acceso a datos en tiempo de diseo que ofrecen lo mejor de ambos
mundos. Por ejemplo, puede crear un objeto DataSet basado en una consulta, un nombre de tabla o un
procedimiento almacenado y un asistente de configuracin generar cdigo para ejecutar la consulta y dar
soporte a las actualizaciones en la base de datos.
10

Escuela de Informtica del Ejrcito

1.2.7

Clases DataSet fuertemente tipadas

Visual tambin le ayuda a simplificar el proceso de creacin de aplicaciones de acceso a datos


generando DataSet fuertemente tipados. Suponga que tiene una tabla simple llamada Clientes que contiene
dos columnas, IdCliente y NombreCia. No ser necesario escribir cdigo como este:
DataSet ds;
//Crea y rellena el DataSet
Console.WriteLine(ds.Tables[Clientes].Rows[0][IdCliente]);

Si no que su cdigo ser como este:


DataSet ds;
//Crea y rellena el DataSet
Console.WriteLine(ds.Clientes[0].IdCliente]);

El DataSet fuertemente tipado es simplemente una clase que genera Visual Studio con toda la
informacin de tablas y columnas disponibles por medio de propiedades. Los DataSet fuertemente tipados
tambin exponen mtodos modificados para caractersticas como crear nuevas filas. En lugar de cdigo
como este:
DataSet ds;
//Cdigo para crear el DataSet y el DataTable de clientes
DataRow filaNuevoCliente;
filaNuevoCliente = ds.Tables[Clientes].NewRow();
filaNuevoCliente[IdCliente] = ALFKI;
filaNuevoCliente[NombreCia] = Alfredo Futterkiste;
ds.Tables[Clientes].Rows.Add(filaNuevoCliente);

Podemos crear y aadir una nueva fila a la tabla con una sola lnea de cdigo:
ds.Clientes.AddClientesRow(ALFKI, Alfredo Futterkiste);

11

ADO.NET 2.0

12

Escuela de Informtica del Ejrcito

Conectar con su base de datos

Parte de la creacin de una aplicacin de base de datos es conectar con su origen de datos y gestionar
la conexin. En el modelo de objetos ADO.NET un objeto Connection representa una conexin con su
origen de datos. Los objetos conexin tambin sirven como punto inicial para crear consultas y
transacciones.
Este captulo ser su gua para el uso de objetos conexin, explicando las caractersticas principales
disponibles en ADO.NET. Discutiremos cmo abrir y cerrar conexiones, agrupamiento de conexiones y
algunas caractersticas nuevas disponibles en ADO.NET 2.0 que permite cmo intenta ADO.NET controlar
la forma de conectar con el origen de datos seguridad de cadena de conexin y generadores de cadena
de conexin. Tambin examinaremos cmo usar otra nueva caracterstica de ADO.NET, el mtodo
GetSchema, que permite descubrir el esquema disponible en una base de datos.
En el modelo de objetos de ADO.NET todas las clases de conexin derivan de la clase DbConnection del
espacio de nombres System.Data.Common. A lo largo de este captulo nos centraremos en la clase
diseada especficamente para comunicar con bases de datos SQL Server, la clase SqlConnection. Si no se
indica otra cosa, las caractersticas expuestas son genricas y se aplican a todas las clases conexin.
Los fragmentos de cdigo asumen que se usan los espacios de nombres System.Data y
System.Data.SqlClient.

2.1 Crear objetos SqlConnection


Hay dos formas de crear objetos SqlConnection en tiempo de ejecucin. Puede simplemente crear un
objeto SqlConnection sin inicializar usando el constructor sin parmetros, o puede usar el constructor que
acepta una cadena de conexin.
string cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind;
+ Integrated Security=True;
SqlConnection conexin = SqlConnection(cadcon);

Una cadena de conexin consiste en pares clave/valor que describe cmo debe ADO.NET intentar
conectar con el origen de datos. Esta cadena concreta indica que se debe buscar una instancia de SQL
Server llamada SQLExpress en la mquina local, buscar un catlogo llamado Northwind e intentar
acceder mediante una conexin de confianza usando credenciales Microsoft Windows. Ms adelante
veremos ms opciones permitidas en una cadena de conexin.

2.2 Abrir objetos SqlConnection


Cuando se crea un objeto conexin se inicializa en estado cerrado. En otras palabras, a pesar de su
nombre, no est conectada con el almacn de datos. Si intenta ejecutar una consulta sobre un
SqlConnection sin abrir primero la conexin, recibir una InvalidOperationException que indica que el
mtodo necesita una conexin abierta y disponible. El estado actual de la conexin es cerrada.
Para conectar con el almacn de datos proporcione una cadena de conexin vlida, ya sea mediante el
constructor o asignando a la propiedad ConnectionString y llame al mtodo Open.
Al llamar al mtodo Open de un objeto SqlConnection que ya est abierto se causa una
InvalidOperationException. Si no est seguro del estado de la conexin compruebe su propiedad State, que
se describe ms adelante en la referencia de SqlConnection.

2.3 Cerrar objetos SqlConnection


Cerrar un objeto SqlConnection es incluso ms fcil que abrirlo. Simplemente llame al mtodo Close. Si
usa agrupamiento de conexiones, que trataremos ms adelante en este captulo, la conexin fsica se
devuelve al agrupamiento en lugar de cerrarla, y se puede reutilizar. Si no est usando agrupamiento de
conexiones, se cierra la conexin fsica con la base de datos.
Si se llama al mtodo Close de un objeto SqlConnection que ya est cerrado no se produce ninguna
excepcin.

2.4 Hacer limpieza por donde pase


Como ya es sabido, el marco de trabajo .NET hace recogida de basura, pero el mecanismo que sigue
esta recogida hace que no se sepa cual ser el momento en que se elimine un objeto. Entonces surge una
pregunta qu sucede si se crea y abre un SqlConnection en el evento de pulsacin de un botn y se sale
del mtodo sin cerrarlo?.
Cuando el recolector de basura de .NET elimina el objeto SqlConnection implcitamente llama al mtodo
Close. Pero cundo se producir esta llamada?. La respuesta correcta, pero indeterminada, es luego.

13

ADO.NET 2.0

Imagine un usuario impaciente que pulsa el botn, por ejemplo, 10 veces en cinco segundos. Cada vez
que se completa el evento Click el nuevo SqlConnection cae fuera de alcance. Cuando, eventualmente, el
recolector de basura elimine el SqlConnection se llamar a su mtodo Close y la conexin fsica se enviar
de vuelta al agrupamiento. Como el usuario pulsa el botn repetidamente antes de que el recolector de
basura elimine los objetos fuera de alcance la aplicacin abre 10 conexiones separadas con la base de
datos.
Lo importante es aprender que hay que hacer limpieza por donde se pase. No confe en la recogida de
basura de .NET para gestionar el estado de sus conexiones con base de datos. En el ejemplo anterior, la
llamada al mtodo Close dentro del mtodo nos asegura que la aplicacin use solo una conexin fsica con
la base de datos SQL Server.
Muchas clases del modelo de objetos ADO.NET, como SqlConnection, exponen un mtodo Dispose. Por
lo general, una llamada a este mtodo libera, o restablece, los recursos no administrados. Al llamar a este
mtodo en un objeto SqlConnection se llama implcitamente al mtodo Close. Como ya se ha dicho, llamar
a Close sobre una conexin no cerrada no provoca ninguna excepcin.
Una forma sencilla de asegurarse de que ha limpiado sus recursos es abrir los recursos de uso breve
dentro de un bloque using. En el ejemplo, podemos abrir la conexin dentro de un bloque using para
asegurarnos de que se llama a Close implcitamente al final del bloque:
private void button1_Click(object sender, EventArgs e){
string cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind;
+ Integrated Security=True;
using(SqlConnection conexin = new SqlConnection(cadcon)){
conexin.Open();
//Usa la conexin
}
}

Ahora no es necesario llamar explcitamente a Close sobre el objeto SqlConnection. El bloque using
asegura que se llamar a Dispose al final, incluso si se produce una excepcin no controlada.

2.5 Cadenas de conexin


En los ejemplos anteriores se ha utilizado una cadena de conexin, pero an no sabemos lo que es.

2.5.1

Qu es una cadena de conexin

Una cadena de conexin se compone de pares clave/valor separados por punto y coma. Las opciones y
valores dependen del origen de datos con el que quiere conectar y de la tecnologa que est usando para
conectar.
El proveedor de datos .NET para cliente SQL es extremadamente flexible en lo que respecta a conectar
con bases de datos, y proporciona una variedad de formas de crear una cadena de conexin. Puede usar
palabras clave actuales como Initial Catalog o Data Source o trminos ms antiguos como Server o
Database.

Conectar con la instancia predeterminada de SQL Server


Puede usar varios valores especiales para indicar que est conectando con la mquina local (local),
localhost o .. Para conectar con el servidor predeterminado simplemente especifique el nombre de
mquina a la que quiere acceder en Data Source:
Data Source=.;

Conectar con una instancia con nombre


En una mquina puede haber instaladas varias instancias de SQL Server. Si quiere acceder a una
instancia con nombre aada una barra invertida (\) y el nombre de instancia tras el nombre de mquina. El
siguiente cdigo accede a una instancia llamada SQLExpress en la mquina local:
Data Source=.\SQLExpress;

El carcter de barra invertida tiene un significado especial en C#, por lo que deber usar una barra
invertida doble (Data Source=.\\SQLExpress;) o anteceder la cadena por una arroba (@Data
Source=.\SQLExpress;).

Especificar un catlogo inicial


Cualquier instancia de SQL Server puede tener instaladas mltiples bases de datos. Cuando se conecte
con una instancia de SQL Server puede especificar la base de de datos por medio de la palabra clave Initial
Catalog. Si quiere acceder a la base de datos Northwind aada el siguiente fragmento a su cadena de
conexin:
Initial Catalog=Northwind;

14

Escuela de Informtica del Ejrcito

Tambin puede usar la antigua palabra clave Database en lugar de Initial Catalog.

Conectar con un nombre de usuario y contrasea especficos


Muchas bases de datos permiten iniciar sesin con el origen de datos proporcionando un nombre de
usuario y una contrasea en la cadena de conexin. Puede usar estas opciones para un SqlConnection por
medio de las palabras clave de cadena de conexin User ID y Password. Estas palabras clave se pueden
sustituir respectivamente por las antiguas UID y PWD.

Conectar usando seguridad integrada


Otra opcin de conexin es hacer que SQL Server autentique al usuario con sus credenciales de
Windows en lugar de especificar un nombre de usuario y una contrasea en la cadena de conexin. Para
especificar esta opcin aada el siguiente fragmento a la cadena de conexin:
Integrated Security=True;

Puede reemplazar la palabra clave Integrated Security por la antigua Trusted_Connection.

2.5.2

Generadores de cadena de conexin

La creacin de cadenas de conexin en tiempo de ejecucin puede ser difcil. Tal vez tenga problemas
para recordar el nombre de la opcin de cadena de conexin que quiere usar. Puede que no est seguro de
cmo delimitar el valor. Quizs busque ayuda para asegurar que la entrada que recibe del usuario no puede
cambiar las intenciones de su cadena de conexin. ADO.NET 2.0 incluye clases generadoras de cadena de
conexin para ayudarle con estos problemas.

Usar un generador de cadenas de conexin


Los generadores de cadena de conexin son fciles de usar. Puede configurar o examinar valores por
medio del indexador de la clase. Una vez proporcionados los valores deseados use la propiedad
ConnectionString del generador para acceder a la cadena de conexin resultante.
En este ejemplo queremos generar una cadena de conexin basada en varias opciones de cadena de
conexin usando un SqlConnectionStringBuilder, que es el generadote de cadenas de conexin para
SqlConnection. Simplemente asignamos valores para las diferentes opciones por medio del indexador. Una
vez generada la cadena de conexin la propiedad ConnectionString contiene el valor que necesita
SqlConnection:
SqlConnectionStringBuilder generador = new SqlConnectionStringBuilder();
generador[Data Source] = @.\SQLExpress;
generador[Initial Catalog] = Northwind;
generador[Integrated Security] = true;
//Escribe la cadena de conexin resultante en la consola
Console.WriteLine(Cadena de conexin: {0}, generador.ConnectionString);
//Utiliza la cadena resultante en una conexin
SqlConnection conexin = new SqlConnection(generador.ConnectionString);
conexin.Open();

La cadena de conexin generada es:


Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True;

Pero esta cadena de conexin ya sabemos construirla, o sea, que an no hemos ganado nada.

Cadenas de conexin e Intellisense


Normalmente es difcil recordar las palabras clave correctas para utilizar en una cadena de conexin,
sobre todo cuando se utilizan varias tecnologas de acceso a datos, cada una de ellas con sus propias
opciones. Los generadores de cadena de conexin exponiendo las opciones ms usadas como
propiedades. Las clases generadoras de cadena de conexin disponibles en ADO.NET 2.0 incluyen
propiedades fuertemente tipadas que corresponden a muchas opciones de cadena de conexin. En el
ejemplo anterior asignamos valores a las palabras clave Data Source, Initial Catalog e Integrated Security
por medio del indexador. En este otro hacemos lo mismo usando propiedades de la clase generador:
SqlConnectionStringBuilder generador = new SqlConnectionStringBuilder();
generador.DataSource = @.\SQLExpress;
generador.InitialCatalog = Northwind;
generador.IntegratedSecurity = true;

Este cdigo genera la misma cadena de conexin que el anterior, pero es ms fcil de escribir. Adems,
si comete un error de tipografa obtendr un error en compilacin. Si utiliza Visual Studio y tiene problemas
para recordar las opciones disponibles, podr comprobarlas por medio de los mens desplegables de
Intellisense
15

ADO.NET 2.0

Valores complejos de cadena de conexin


Una ventaja secundaria del uso de generadores de cadena de conexin es que evitan la necesidad de
analizar, escapar o delimitar valores. Suponga que tiene que utilizar una cadena de conexin que incluye un
espacio. Alrededor del espacio hay que poner comillas sencillas, o llaves? o las dos cosas? o nada?.
Si est creando la cadena de conexin a mano, la respuesta no es sencilla. La respuesta puede variar
segn el proveedor de datos .NET que est utilizando. Si utiliza un generador de cadena de conexin no
tiene que preocuparse por este asunto. Simplemente proporcione como valor de la opcin la cadena que
debe emplear, y el generador se encargar de darle el formato adecuado.

Inyeccin de cdigo en cadenas de conexin


Una de las reglas ms importantes al escribir cdigo seguro es nunca confe en la entrada de usuario.
Las consultas parametrizadas, que se tratarn en su momento, son tiles por muchas razones, pero una de
las principales es que le protegen contra la inyeccin SQL. Puede construir una consulta parametrizada y
asignar la entrada del usuario a parmetros sin tener que preocuparse de que modifique la estructura de su
consulta. Cuando se construyen cadenas de conexin hay problemas similares.
Puede decidir preguntar al usuario sus credenciales y construir una cadena de conexin en base a esta
entrada:
string cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind; +
User ID= + txtIdUsuario.Text + ; +
Password= + txtContrasea.Text + ;
Console.WriteLine(Cadena de conexin resultante: {0}, cadcon);

A primera vista puede parecer una forma segura y lgica de hacer las cosas. Pero imagine un usuario
malvolo que quiere cambiar la cadena de conexin. Cuando se le pida su nombre de usuario puede
escribir MiNombreUsuario;Data Source=NombreOtroServidor. A falta de un nombre apropiado,
llamaremos a esto inyeccin de cadena de conexin. Si esta entrada llega a nuestro cdigo, la cadena
resultante ser:
Data Source=.\SQLExpress;Initial Catalog=Northwind;User ID=MiNombreUsuario;
Data Source=NombreOtroServidor;Password=contrasea;

Como puede ver, la clave Data Source aparece dos veces. Qu valor tiene prioridad, el primero o el
ltimo?.Hay alguna forma de inspeccionar la entrada del usuario para identificar problemas como ste?.
Son coherentes las respuestas de un proveedor de datos a otro?. Qu puede hacer un pobre
programador?.
Afortunadamente, los generadores de cadena de conexin pueden ayudarnos. Vamos a crear una
cadena de conexin en base a entrada del usuario usando un generador:
SqlConnectionStringBuilder generador = new SqlConnectionStringBuilder();
generador.DataSource = @.\SQLExpress;
generador.InitialCatalog = Northwind;
generador.UserID = txtIdUsuario.Text;
generador.Password = txtContrasea.Text;

Si el usuario escribe la misma entrada, la cadena generada ser:


Data Source=.\SQLExpress;Initial Catalog=Northwind;User ID=MiNombreUsuario;
Data Source=NombreOtroServidor;Password=contrasea;

Ver que el valor introducido por el usuario est entre comillas, lo que har que se intente la conexin
con toda la cadena como nombre de usuario, por lo que fracasar.

Alias de palabras clave


Como SqlConnectionStringBuilder est creada especficamente para SQL Server reconoce los alias de
palabra clave antiguos. Puede proporcionar a SqlConnectionStringBuilder las palabras clave antiguas o las
actuales, y en ambos casos el resultado ser el mismo.

Dilogo de cadena de conexin con generador


No existe un control o dilogo para generar cadenas de conexin ADO.NET. Pero puede crear el suyo
propio para mostrar opciones de conexin de una forma muy fcil.
Si quiere crear un interface de usuario que muestre todas las opciones de cadenas de conexin puede
lograrlo gracias al control PropertyGrid del marco de trabajo .NET. Simplemente asigne un
SqlConnectionStringBuilder a la propiedad SelectedObject de PropertyGrid. La nica tarea restante es
capturar el evento PropertyValueChanged del control para actualizar una caja de texto con el valor actual de
la propiedad ConnectionString de SqlConnectionStringBuilder.
16

Escuela de Informtica del Ejrcito

2.5.3

Seguridad de cadenas de conexin

Supongamos que quiere usar un dilogo de cadena de conexin como el que acabamos de discutir, pero
solo quiere permitir ciertas cadenas de conexin. Por ejemplo, puede que quiera restringir la cadena de
conexin a un catlogo inicial concreto en un servidor especfico, a la vez que permite cualquier valor el ID
de usuario y la contrasea, pero sin atributos adicionales. Cmo puede escribir este cdigo?.
Una posibilidad es comenzad con un SqlConnectionStringBuilder cuyas propiedades DataSource e
InitialCatalog tengan los valores deseados. A continuacin puede escribir su propio dilogo, permitiendo que
el usuario escriba valores para el ID y la contrasea. Entonces podra asignar estos valores a su
SqlConnectionStringBuilder para evitar la inyeccin de cadena de conexin.
En un ejemplo tan sencillo como este, esta solucin puede ser suficiente. Pero que pasa con opciones
ms complejas?. Supongamos que el usuario se puede conectar al ServidorA o al ServidorB, pero una
conexin con el ServidorA necesita seguridad integrada y una conexin con ServidorB utiliza un ID de
usuario y una contrasea. Mientras ms compleja sea la situacin, ms complicados se vuelven el interface
de usuario y la lgica de validacin. Un mtodo ms simple es validar la cadena de conexin es usar las
nuevas caractersticas de seguridad de cadena de conexin de ADO.NET.

Restringir el acceso a datos con la clase SqlClientPermission


La clase SqlClientPermission deriva de la clase CodeAccessPermission del espacio de nombres
System.Security. Puede usar esta clase para restringir el acceso a cadenas de conexin ya sea mediante
programacin, por medio de atributos y mtodos de clases, o por medio de seguridad de acceso de cdigo.
El siguiente ejemplo muestra cmo puede usar la clase SqlClientPermission para restringir el acceso a
un nombre de servidor y catlogo inicial especficos, a la vez que se permite cualquier valor para el ID de
usuario y la contrasea. El fragmento asume que existe un dilogo miDialogo que devuelve una cadena de
conexin creada usando un SqlConnectionStringBuilder y un PropertyGrid, y tambin asume referencias al
espacio de nombres System.Security.Permissions.
SqlClientPermission permiso = new SqlClientPermission(PermissionState.None);
permiso.add(@Data Source=.\SQLExpress;Initial Catalog=Northwind;,
User ID=;Password=;, KeyRestrictionBehavior.AllowOnly);
permiso.PermitOnly();
//Intenta usar la cadena de conexin del hipottico dilogo miDialogo
SqlConnection conexin = new SqlConnection(miDialogo.ConnectionString);
conexin.Open();
...

El cdigo comienza creando una nueva instancia de la clase SqlClientPermission. El valor


PermissionState.None en el constructor hace que comience en un estado limpio, que no permite ninguna
cadena de conexin. A continuacin se aaden las cadenas de conexin que se quieren permitir llamando
al mtodo Add. El primer argumento contiene un subconjunto de de cadenas de conexin que se quieren
permitir, indicando todas las claves y valores exigidos en este caso, el nombre de servidor y el catlogo
inicial deseados.
Las cadenas de conexin que deban pasar este control de seguridad deben tener los valores
especificados para Data Source e Initial Catalog. El segundo argumento es una lista separada por punto y
coma de atributos de cadena de conexin opcionales, en este caso User ID y Password. Las cadenas de
conexin pueden tener cualquier valor en estas opciones y pasarn el control de seguridad. Las cadenas de
conexin que omitan estas opciones tambin pasarn el control. El tercer y ltimo argumento controla si se
est concediendo o denegando el permiso para las cadenas de conexin que coincidan con este patrn.
Una vez que se llama al mtodo PermitOnly se impone el control de seguridad.
Si la cadena de conexin proporcionada pasa estos controles el SqlConnection intenta conectar con la
base de datos. Si la cadena de conexin no pasa la comprobacin, SqlConnection lanza una
SecurityException antes de ni siquiera intentar conectar con la base de datos.

Combinar permisos
SqlClientPermission no est restringida a una nica cadena de conexin; puede aadir mltiples
conexiones y comodines. La llamada a Open de SqlConnection no tendr xito hasta que el valor de
ConnectionString no supere al menos una de las comprobaciones.

Sinnimos para palabras clave de cadena de conexin


SqlClientPermission respeta los sinnimos de palabras clave de conexin. Puede usar, por ejemplo, las
palabras clave Data Source e Initial Catalog en la clase SqlClientPermission. El proveedor de datos .NET
para cliente SQL sabe que Server y Database son sinnimos para estos parmetros. Siempre que el resto
de la cadena de conexin pase el control, SqlClientPermission aceptar la cadena de conexin y permitir
que se intente la conexin con la base de datos SQL Server.
17

ADO.NET 2.0

Por qu no empezar con acceso no restringido


En teora, si quiere permitir conexiones a cualquier base de datos SQL Server excepto Northwind de la
instalacin local de SQL Server Express podra empezar con permisos no restringidos y denegar una
cadena de conexin usando el siguiente cdigo.
SqlClientPermission permiso = new SqlClientPermission(PermissionState.Unrestricted);
permiso.add(@Data Source=.\SQLExpress;Initial Catalog=Northwind;,
User ID=;Password=;, KeyRestrictionBehavior.AllowOnly);
permiso.Deny();
string cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind; +
User ID=...;Password=...;
SqlConnection conexin = new SqlConnection(cadcon);
conexin.Open();

...
Este cdigo, como es de esperar lanza una SecurityException. Problema resuelto?. En absoluto.
Empezar con permisos sin restringir e intentar denegar el acceso a un nico recurso es ms fcil de decir
que de hacer. Qu sucede si intentamos el mismo cdigo con la siguiente cadena de conexin?:
Data Source=(local)\SQLExpress;Initial Catalog=Northwind;User ID=MiUsuario;
Password=MiContrasea;

Desafortunadamente, SqlClientPermission no reconoce que .\SQLExpress y (local)\SQLExpress


hacen referencia al mismo servidor. Por tanto, esta cadena pasa el control de SqlClientPermission y el
SqlConnection intenta conectar con SQL Server usando esta cadena de conexin, que no es lo que
esperamos ni queremos. El SqlClientPermission tambin permitir conectar con la base de datos usando la
direccin IP del servidor. Hay muchas formas de crear una cadena de conexin para una misma base de
datos SQL Server, y la lgica necesaria para determinar si son equivalentes es abrumadora. Si
SqlClientPermission intentara incluir esta lgica supondra una penalizacin de rendimiento significativa,
adems de que existiran muchas posibilidades de que SqlClientPermission fracasara en el intento de
reconocer todas las cadenas de conexin equivalentes.
En otras palabras; comience sin ningn permiso y permita cadenas especificas en lugar de comenzar
con todos los permisos e intentar impedir el acceso a recursos concretos.

Alcance de permisos
Las restricciones tienen el mismo tiempo de vida que la funcin en la que se crean. Es decir, si
imponemos una restriccin en una Funcin1 y esta llama a Funcin2, la restriccin no est activa en
Funcin2, que podr acceder a bases de datos sin restricciones.

Imponer seguridad de cadena de conexin mediante atributos


El siguiente ejemplo aplica atributos a un procedimiento para imponer seguridad de cadena de conexin.
[SqlClientPermission(SecurityAction.PermitOnly, ConnectionString =
@Server=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True,
KeyRestrictions = , KeyRestrictionBehavior = KeyRestrictionBehavior.AllowOnly)]
private void Procedimiento(){
...
}

2.6 Agrupamiento de conexiones


ADO.NET proporciona soporte para agrupamiento de conexiones.

2.6.1

Manipuladores de conexin y conexiones fsicas

Si est trabajando con Visual Studio puede examinar algunas de las propiedades internas de objetos
usando las herramientas de depuracin. Por ejemplo, escriba cdigo para abrir un SqlConnection y
establezca un punto de interrupcin en la llamada al mtodo Open. Ejecute en modo de depuracin, y al
llegar al punto de interrupcin aada el objeto a la ventana de Inspeccin pulsando sobre l con el botn
derecho y pulsando Agregar inspeccin. En la ventana Inspeccin expanda el rea Miembros no pblicos.
Busque una propiedad llamada InnerConnection.
Los contenidos de la propiedad InnerConnection representan una capa muy fina sobre la conexin fsica
con la base de datos. Para los objetos de esta discusin, InnerConnection y la conexin fsica son
intercambiables. Al recorrer el cdigo ver que el valor de esta propiedad cambia cuando se abre y cierra la
conexin. Cuando se llama al mtodo Open el proveedor de datos .NET asocia el objeto SqlConnection con
una conexin fsica con la base de datos, de modo que puede ejecutar consultas y devolver resultados.
La apertura y cierre de una conexin con una base de datos es costosa. Para ahorrar recursos y mejorar
el rendimiento los proveedores de datos utilizan agrupamiento de conexiones de forma predeterminada.
18

Escuela de Informtica del Ejrcito

2.6.2

Qu es agrupamiento de conexiones

El agrupamiento de conexiones es un mecanismo para mejorar el rendimiento de aplicaciones cuando se


abren conexiones con su almacn de datos. Cuando se llama al mtodo Close del objeto SqlConnection el
proveedor de datos no cierra realmente la conexin interna. El proveedor almacena la conexin interna en
un agrupamiento de modo que se puede reutilizar posteriormente. La conexin interna permanece en el
agrupamiento incluso despus de la eliminacin del objeto SqlConnection. Si ms tarde llama al mtodo
Open de un objeto SqlConnection que utilice la misma cadena de conexin y credenciales reutilizar la
misma conexin interna para comunicar con la base de datos.
Si quiere confirmar que realmente est reutilizando la misma conexin interna puede usar las
funcionalidades de reflexin de .NET para acceder mediante programacin a los contenidos de la propiedad
privada InnerConnection. El siguiente cdigo, que necesita una referencia al espacio de nombres
System.Reflection, abre un SqlConnection en un bloque using y almacena el valor de su propiedad
InnerConnection. Al utilizar un bloque using eliminamos implcitamente el SqlConnection al final del bloque.
El cdigo abre entonces otro SqlConnection en un bloque using y almacena el valor de su propiedad
InnerConnection. Finalmente, el cdigo compara el contenido de las dos propiedades InnerConnection,
confirmando que de hecho son el mismo objeto.
string cadcon = @"Data Source=.\SQLExpress;Integrated Security=True";
PropertyInfo propConInterna = typeof(SqlConnection).GetProperty("InnerConnection",
BindingFlags.NonPublic | BindingFlags.Instance);
object objConInterna1, objConInterna2;
using(SqlConnection con = new SqlConnection(cadcon)) {
con.Open();
objConInterna1 = propConInterna.GetValue(con, null);
con.Close();
}
using(SqlConnection con = new SqlConnection(cadcon)) {
con.Open();
objConInterna2 = propConInterna.GetValue(con, null);
con.Close();
}
Console.WriteLine(objConInterna1 == objConInterna2);

Los dos objetos SqlConnection se crean en bloques using separados, de modo que sus recursos se
limpian al final de cada uno de los bloques. Los contenidos de la propiedad InnerConnection y la conexin
fsica que encapsula se almacenan en el agrupamiento en lugar de ser eliminados, por lo que se pueden
reutilizar.

2.6.3

Cmo mejora su cdigo el agrupamiento

Imagine una aplicacin ASP.NET o servicio Web tpico que accede a una base de datos SQL Server.
Cada vez que la aplicacin cliente necesita consultar la base de de datos hay un viaje de ida y vuelta al
cdigo de servidor que abre un SqlConnection par ejecutar la consulta. En muchas de estas aplicaciones
este cdigo conecta siempre con la misma base de datos, con las mismas credenciales, una y otra vez. En
teora esto significa que cada vez que una aplicacin cliente necesita ejecutar una consulta el cdigo de
servidor tiene que realizar tres operaciones iniciar sesin con la base de datos, lo que implica comprobar
las credenciales proporcionadas, ejecutar la consulta y cerrar la sesin.
El agrupamiento de conexiones puede mejorar realmente el rendimiento de este tipo de aplicaciones. Al
almacenar la conexin interna en un grupo y reutilizarla posteriormente, ya no se tiene la penalizacin de
rendimiento asociada con el inicio y cierre de la sesin con la base de datos. Las llamadas a los mtodos
Open y Close terminan en una fraccin del tiempo, mejorando as el rendimiento y capacidad de respuesta
del cdigo.

2.6.4

Cundo se cierra una conexin agrupada

Cuando se llama al mtodo Close el objeto SqlConnection devuelve la conexin al agrupamiento.


Asumiendo que la conexin no se reutilice, se eliminar del agrupamiento tras aproximadamente cinco
minutos. No se puede decir un nmero de segundos exacto. Desde luego, si la aplicacin termina mientras
hay conexiones abiertas en el agrupamiento, las conexiones se cierran y eliminan como parte de la limpieza
normal de la aplicacin.

2.6.5

Desactivar el agrupamiento de conexiones

Tal vez no quiera usar el agrupamiento de conexiones, por ejemplo, si est trabajando en una aplicacin
Windows simple que se comunica directamente con la base de datos, querr desactivar el agrupamiento de
conexiones. Con esta arquitectura las aplicaciones cliente individuales precisan sus propias conexiones.
Con el agrupamiento de conexiones activado la conexin de cada aplicacin se agrupa y reutiliza si se
19

ADO.NET 2.0

vuelve a abrir antes de que se limpie el agrupamiento. As, si la aplicacin reutiliza la conexin con
frecuencia el mtodo Open de SqlConnection termina ms rpidamente; pero este mtodo lleva a la
existencia de ms conexiones activas contra la base de datos en un momento dado. Desactivando el
agrupamiento de conexiones se reducir el nmero de conexiones activas contra la base de datos
simultneamente, pero obligar a que todas las llamadas a Open establezcan una nueva conexin.
Si quiere desactivar el agrupamiento de conexiones puede hacerlo conexin a conexin aadiendo
Pooling=False a la cadena de conexin.
Afortunadamente, en ADO.NET 2.0 no necesita memorizar atributos como ste. Cuando tenga dudas
puede comprobar las propiedades de la clase SqlConnectionStringBuilder. Entre ellas encontrar Pooling,
que acepta un valor booleano; de forma predeterminada su valor es True. Asignando a esta propiedad el
valor False se desactiva el agrupamiento para esta conexin; cuando llame al mtodo Close de
SqlConnection la conexin con la base de datos se cerrar realmente.

2.6.6

Controlar el agrupamiento

Mientras ms aprende un desarrollador sobre agrupamiento de conexiones, ms preguntas vienen a su


mente. Por ejemplo, la pregunta que ms frecuentemente he odo respecto a este tema es cmo puedo
averiguar si la conexin fsica con la base de datos se ha cerrado realmente o se ha agrupado?. Otra
pregunta habitual es cmo puedo saber si una conexin que acabo de abrir ha establecido una nueva
conexin fsica o ha reutilizado una de grupo?.
Hay muchas herramientas que le pueden ayudar a responder sus propias preguntas sobre
agrupamiento. Algunas son ms elegantes que otras. Con ADO.NET 2.0 una de estas herramientas es el
monitor de rendimiento de Windows.
El proveedor de datos .NET para cliente SQL de ADO.NET 2.0 incluye contadores de rendimiento para
agrupamiento de conexiones. Ahora puede usar herramientas como el monitor de rendimiento para
examinar el nmero de conexiones agrupadas, conexiones activas, agrupamientos de conexiones activos e
inactivos y grupos de agrupamientos de conexiones activos e inactivos. Tambin puede obtener informacin
sobre conexiones y desconexiones por segundo.
En algunos casos mantener contadores de rendimiento puede penalizar el rendimiento. Por este motivo
el proveedor de datos no mantiene contadores para el nmero de conexiones activas o libres o el nmero
de conexiones o desconexiones agrupadas por segundo. Puede activar estos contadores en su aplicacin
aadiendo una entrada al archivo de configuracin de su aplicacin. Para ms informacin sobre el uso de
estos contadores de rendimiento busque Contadores de rendimiento ADO.NET en MSDN.

2.6.7

Cmo determina ADO.NET si usar una conexin agrupada

De una forma simple, asumiendo que el agrupamiento no est desactivado, el proveedor de datos
examina la ConnectionString cuando se llama al mtodo Open de ConnectionString y determina si hay o no
una conexin disponible en el agrupamiento; si hay una conexin disponible, la usa; si no es as, abre una
nueva conexin con la base de datos.
En realidad, hay poco ms. Imagine una aplicacin ASP.NET con mltiples usuarios conectando con la
misma base de datos usando suplantacin, usando cada uno con sus propias credenciales para acceder a
la base de datos. Las cadenas de conexin son iguales para todos los usuarios, pero sus credenciales son
distintas. Por tanto, la lgica usada para determinar qu conexiones agrupadas se pueden usar es un poco
ms compleja, ya que el cliente SQL tiene en cuenta los permisos de usuario.

2.6.8

Liberar manualmente conexiones agrupadas

Hay ocasiones en las que puede decidir que no quiere seguir arrastrando las conexiones de un
agrupamiento antiguo y preferir establecer uno nuevo. En este caso el objetivo es modificar la cadena de
conexin de forma que afecte al agrupamiento, pero no al resto de la aplicacin. La forma ms sencilla se
aadir un nico espacio al final de la cadena.
El truco anterior era til con ADO.NET 1.x, porque ninguna caracterstica del API le ayudaba a liberar
conexiones agrupadas. En ADO.NET 2.0 la clase SqlConnection dispone de dos nuevos mtodos estticos
que le ayudarn ClearPool y ClearAllPools.
El mtodo ClearPool recibe un objeto SqlConnection y libera todas las conexiones agrupadas asociadas
con l. Este mtodo no afectar a las conexiones que se encuentren activas en el momento de la llamada,
es decir, aquellas para las que se haya llamado a Open pero no a Close.
El mtodo ClearAllPools no recibe argumentos y libera todas las conexiones libres en todos los
agrupamientos existentes en la aplicacin.

20

Escuela de Informtica del Ejrcito

2.6.9

Otras opciones de agrupamiento

El agrupamiento de conexiones dispone de otras opciones de uso habitual. Todas ellas estn disponibles
por medio de SqlConnectionStringBuilder y de las cadenas de conexin.

Restablecimiento de conexin
Limitarse a reutilizar una SqlConnection puede tener consecuencias inesperadas. A falta de un trmino
mejor, hay residuos asociados con una conexin agrupada. Los desarrolladores no siempre limpian sus
restos para devolver la conexin a su estado inicial al cerrarla. En el momento de la llamada a Close puede
haber cursores abiertos o transacciones asociadas con la conexin, o peor. Si se usa una consulta del tipo
USE otrabasedatos, la conexin puede estar asociada con una base de datos diferente que con aquella
con la que se abri. Si se asigna un rol a una conexin llamando a sp_setapprole estos privilegios seguirn
en vigor para la conexin hasta que se llame a sp_unsetapprole, o se llame a sp_reset_connection, o la
conexin se cierre realmente, en lugar de devolverla al agrupamiento.
El proveedor de datos mantiene el seguimiento de los SqlConnection que usan una conexin recuperada
de un agrupamiento de conexiones. En lugar de llamar al procedimiento almacenado sp_reset_connection
cuando llame al mtodo Open, SqlClient ejecuta esta consulta justo antes de la primera actividad de la
conexin.
Si est absolutamente seguro de que no est dejando ningn residuo en el servidor para sus
conexiones agrupadas, podra aadir Connection Reset=False a su cadena de conexin. Esta opcin indica
que no es necesario llamar al procedimiento almacenado sp_reset_connection cuando se reutilizan
conexiones agrupadas. No recomiendo utilizar esta opcin.

Tamao mnimo de grupo


El atributo Min Pool Size controla el nmero mnimo de conexiones en un grupo. De forma
predeterminada este valor es cero.
El atributo Min Pool Size puede ayudarle a preparar un agrupamiento de conexiones. Supongamos que
le da el valor 5; cuando se abra la primera conexin, se abrirn otras cuatro en una hebra de segundo
plano. Siempre habr por lo menos cinco conexiones en el agrupamiento. Supongamos que su cdigo crea
10 SqlConnection y los abre todos. Como antes, al abrir el primero se abrirn cuatro ms en una hebra en
segundo plano. Los siguientes cuatro objetos SqlConnection usarn las conexiones restantes del
agrupamiento, los restantes cinco establecern nuevas conexiones.
Supongamos que ahora cierra ocho de los diez SqlConnection. Las ocho conexiones se mantendrn
vivas en el agrupamiento. Cuando se haga la limpieza del agrupamiento, cuando transcurran
aproximadamente cinco minutos de inactividad, se mantendrn al menos cinco conexiones, incluyendo las
que estn actualmente en uso. Como hay dos conexiones en uso, se mantienen tres en el agrupamiento;
las cinco restantes se eliminan.
La desventaja principal de Min Pool Size es que siempre se mantendr al menos este nmero de
conexiones activas, y en una aplicacin ASP.NET estas conexiones pueden quedar muy desfasadas. Es
mejor dejar el valor en cero.

Tamao mximo de grupo


El atributo Max Pool Size es ms sencillo de comprender. Esta opcin impide que se abra un nmero de
conexiones mayor que el indicado en un mismo agrupamiento. El valor predeterminado es 100. Una vez que
se alcance el nmero mximo de conexiones en el grupo, el siguiente intento de abrir una conexin
esperar durante el tiempo mximo indicado en la opcin Connect Timeout antes de lanzar una
InvalidOperationException con el texto El tiempo de espera ha expirado. Se ha superado el tiempo lmite
antes de de obtener una conexin del agrupamiento. Puede ser debido a que todas las conexiones
agrupadas estn en uso y se ha alcanzado el tamao mximo del grupo. Si antes de superarse este tiempo
queda libre una conexin, el nuevo intento la utilizar y se evitar la excepcin.

2.7 SqlConnection como punto inicial


Una vez conectado con la base de datos el objeto Connection puede servir como punto inicial para una
serie de operaciones crear comandos, iniciar transacciones y recuperar esquema.

2.7.1

Crear SqlCommand

La clase SqlCommand, que veremos ms adelante, es la clase que se usa para ejecutar consultas
contra la base de datos. Para ejecutar una consulta debe asignar un objeto SqlConnection a la propiedad
Connection del objeto SqlCommand. La clase SqlConnection proporciona un mtodo CreateCommand que
se puede usar para simplificar el proceso. Este mtodo devuelve un nuevo objeto SqlCommand que ya est
inicializado para utilizar el objeto SqlConnection.

21

ADO.NET 2.0

Una ventaja del mtodo CreateCommand es que est disponible en todas las clases Connection del
marco de trabajo .NET. Por tanto, este mtodo permite crear un Command sobre un Connection dado de
una forma genrica. El siguiente cdigo usa el espacio de nombres System.Data.Common:
private void PreparaBD(DbConnection con){
using(DbCommand comando = con.CreateCommand()){
comando.CommandText = CREATE TABLE unaTabla ...;
comando.ExecuteNonQuery();
comando.CommandText = INSERT INTO unaTabla ...;
comando.ExecuteNonQuery();
}
}

2.7.2

Iniciar transacciones

Puede usar un objeto SqlConnection para iniciar transacciones. El mtodo BeginTransaction de la clase
SqlConnection devuelve un objeto SqlTransaction sobre la conexin. Este objeto se tratar ms adelante.

2.7.3

Recuperar informacin de esquema

En ADO.NET 2.0 se ha aadido a la clase SqlConnection un mtodo GetSchema que se puede usar
para recuperar informacin de esquema de la base de datos. Este mtodo est sobrecargado de una forma
que al principio puede parecer extraa, pero las sobrecargas proporcionan un medio simple y efectivo de
determinar exactamente qu esquemas y restricciones estn disponibles.
Supongamos que quiere obtener una lista de todas las tablas disponibles en una base de datos. Puede
usar cdigo como el siguiente:
string cadcon = @"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind; +
Integrated Security=True";
SqlConnection conexin = new SqlConnection(cadcon);
conexin.Open();
DataTable tabla = conexin.GetSchema("Tables");
conexin.Close();
foreach(DataRow fila in tabla.Rows)
Console.WriteLine(fila["TABLE_NAME"]);

El mtodo GetSchema contiene un DataTable con informacin. Despus de llamar a GetSchema el


cdigo recorre sus filas y muestra el valor de la columna TABLE_NAME.
Al ver este cdigo puede preguntarse cmo se que hay que pasar Tables al mtodo GetSchema?.
Buena pregunta. Hay una serie de esquemas soportados por todos los proveedores de datos .NET y que se
recogen como valores de la enumeracin DbMetaDataCollectionNames del espacio de nombres
System.Data.Common. El proveedor de datos .NET para cliente SQL indica los esquemas adicionales que
soporta en la enumeracin SqlClientMetaDataCollectionNames. En lugar de pasar la cadena literal Tables al
mtodo GetSchema puede pasar el valor SqlClientMetaDataCollectionNames.Tables. Pero hay otra forma
de determinar qu esquemas soporta un proveedor de datos concreto.
Cada clase Connection tiene su propia lista de esquemas disponibles por medio del mtodo
GetSchemas de la clase DbConnection. Puede acceder a esta lista llamando a la sobrecarga de
GetSchema que no recibe parmetros. El siguiente cdigo muestra los esquemas que soporta un
SqlConnection, que incluye entre otros tablas, columnas, vistas, procedimientos, ndices y columnas ndice.
string cadcon = @"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind; +
Integrated Security=True";
SqlConnection conexin = new SqlConnection(cadcon);
conexin.Open();
DataTable tabla = conexin.GetSchema();
conexin.Close();
foreach(DataRow fila in tabla.Rows)
Console.WriteLine(fila["CollectionName"]);

Puede usar cualquiera de los nombres de coleccin que devuelve este mtodo como parmetro de
GetSchema para comprender qu esquemas hay disponibles en la base de datos. Sin embargo, hay
ocasiones en las que no querr examinar toda la informacin de un tipo de esquema concreto. Por ejemplo,
puede ver informacin sobre las columnas de la base de datos, pero solo de las columnas de una cierta
tabla.
Existe otra sobrecarga de GetSchema diseada con este objeto. Puede pasar un nombre de esquema y
un array de cadenas; el array de cadenas se usa como restriccin o filtro en la consulta de informacin de
esquema. El siguiente cdigo usa este mtodo para mostrar los nombres de columna de la tabla Customers.
22

Escuela de Informtica del Ejrcito

string cadcon = @"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind; +


Integrated Security=True";
SqlConnection conexin = new SqlConnection(cadcon);
string[] restricciones = new string[] { "Northwind", "dbo", "Customers", null};
conexin.Open();
DataTable tabla = conexin.GetSchema("Columns", restricciones);
conexin.Close();
foreach(DataRow fila in tabla.Rows)
Console.WriteLine(fila["COLUMN_NAME"]);

Otra vez, al mirar este cdigo se preguntar cmo averiguar cmo debe ser al array de restricciones. De
nuevo, puede usar otras sobrecargas de GetSchema para saber cmo escribir su cdigo.
Existe un esquema llamado Restrictions que puede usar para examinar las restricciones disponibles en
todos los esquemas. El siguiente cdigo muestra en la ventana de consola el nombre de coleccin, nombre
de restriccin y nmero de restriccin para todas las restricciones disponibles. Este cdigo usa algunos
trucos de formato de Console.WriteLine para hacer que los datos sean algo ms legibles.
string cadcon = @"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind; +
Integrated Security=True";
SqlConnection conexin = new SqlConnection(cadcon);
conexin.Open();
DataTable tabla = conexin.GetSchema("Restrictions");
conexin.Close();
foreach(DataRow fila in tabla.Rows)
Console.WriteLine("{0, -25}{1, -18}{2}", fila["CollectionName"],
fila["RestrictionName"], fila["RestrictionNumber"]);

La salida de este programa muestra cuatro entradas con el CollectionName Columns. Esto significa
que hay cuatro parmetros disponibles para restringir la informacin devuelta en una llamada para obtener
informacin de esquema de columna. Esto explica porqu utilizamos un array de cadenas con cuatro
elementos en la llamada a GetSchema del fragmento anterior. Si examina la salida ver que la tercera
restriccin para Columns corresponde al nombre de tabla; por eso colocamos el valor Customers en el
tercer elemento del array. Cuando no quiera aplicar una restriccin utilice null en su puesto del array.
El mtodo GetSchema devuelve informacin slida hay ms que nombres de tabla y columna. Por
ejemplo, el esquema de columna incluye tambin ordinales de columna, tipos de datos, tamaos y valores
predeterminados. Si necesita recuperar informacin de su base de datos en tiempo de ejecucin,
GetSchema proporciona una gran variedad.

2.8 Explorador de servidores


El Explorador de servidores de Visual Studio permite examinar varios servicios del sistema operativo e
integrarlos en sus aplicaciones. El Explorador de servidores tiene soporte adicional para conectividad con
bases de datos.
Puede usar el Explorador de servidores para conectar con SQL Server, as como con otras bases de
datos, desde Visual Studio. Una vez creada una conexin con una base de datos el Explorador de
servidores permite examinar el esquema de esta base de datos, incluyendo tablas y columnas, vistas,
procedimientos almacenados, etc. Si la base de datos es SQL Server 2005 podr ver informacin adicional,
en concreto tipos y ensamblados.
Si quiere ejecutar una consulta sobre una conexin concreta pulse con el botn derecho sobre esta
conexin en el Explorador de servidores y seleccione Nueva consulta, lo que mostrar la herramienta
grfica de consultas. Para ejecutar la consulta y ver los resultados seleccione Ejecutar SQL en el men,
men de contexto o barra de herramientas.
Si est trabajando con una base de datos SQL Server puede usar el Explorador de servidores para
cambiar el esquema de la base de datos. El Explorador de servidores permite crear, modificar o eliminar
tablas, vistas, procedimientos almacenados y ms en la base de datos SQL Server. No es necesario crear
consultas DDL (Data Definition Language, Lenguaje de definicin de datos) como CREATE TABLE.
Puede aadir una conexin al Explorador de servidores pulsando el botn de la parte superior que
muestra un cilindro amarillo con una cruz verde y un cable elctrico. Esto lanzar el dilogo Agregar
conexin, que permite especificar la informacin necesaria para conectar con una base de datos SQL
Server.
Si quiere conectar con una base de datos diferente pulse el botn Cambiar junto a origen de datos. El
dilogo Cambiar origen de datos le primee seleccionar un tipo de origen de datos diferente.
23

ADO.NET 2.0

2.9 Referencia del objeto SqlConnection


Ahora ya comprende la funcin de la clase SqlConnection en aplicaciones de bases de datos y est
cmodo con el uso de la mayor parte de sus caractersticas. La siguiente seccin est pensada para
rellenar algunos de los blancos y cubrir caractersticas menos utilizadas pero no menos importantes de la
clase.

2.9.1

Propiedades

La tabla 3.1 contiene las propiedades que usar con mayor frecuencia cuando trabaje con
SqlConnection. La mayoras slo se pueden configurar por medio de la propiedad ConnectionString y no se
pueden modificar directamente.
Tabla 3. 1: Propiedades de uso habitual de la clase SqlConnection

Propiedad
ConnectionString

Tipo
String

ConnectionTimeout

Int32

Database
DataSource

String
String

FireInfoMessageEventOnUserErrors

Boolean

PacketSize

Int32

ServerVersion
State
StatisticsEnabled

String
ConnectionState
Boolean

WorkstationId

String

Descripcin
Controla cmo se conecta el SqlConnection con la base de
datos.
Especifica el tiempo en segundos que el SqlConnection
espera cuando intenta conectar con la base de datos. Solo
lectura.
Nombre de la base de datos con la que conecta. Slo lectura.
Ubicacin de la base de datos con la que conecta. Slo
lectura.
Controla si se lanza el evento InfoMessage en los errores de
usuario. De forma predeterminada su valor es false. Se
puede cambiar con el SqlConnection abierto o cerrado.
Devuelve el tamao de paquete usado al comunicar con SQL
Server (slo lectura).
Versin del origen de datos (slo lectura).
Indica el estado actual del objeto SqlConnection (slo lectura)
Controla si estn activadas las estadsticas para la conexin.
De forma predeterminada el valor es false. Esta propiedad se
puede modificar con el SqlConnection abierto o cerrado.
Devuelve el nombre del cliente de la base de datos. De forma
predeterminada su valor es el nombre de mquina (slo
lectura).

ConnectionString
Esta propiedad controla cmo intentar el objeto SqlConnection conectar con el origen de datos. Slo
puede configurar esta propiedad cuando el SqlConnection no est conectado con la base de datos. Cuando
est conectado con la base de datos esta propiedad es de solo lectura.

ConnectionTimeout
Indica el tiempo en segundos que el proveedor de datos espera cuando intenta conectar con el origen de
datos antes de considerar que ha superado el tiempo lmite.
Esta propiedad es de slo lectura y solo se puede configurar por medio de la propiedad
ConnectionString. Hay dos opciones para configurar el tiempo lmite en una cadena de conexin usar un
SqlConnectionStringBuilder con el valor adecuado en su propiedad ConnectTimeout o usar la palabra clave
Connect Timeout en la cadena de conexin.

Database y DataSource
Los trminos base de datos y origen de datos a menudo se usan de forma intercambiable, pero la clase
SqlConnection los expone como propiedades separadas. En qu se diferencian?. Una instancia de SQL
Server es un origen de datos, que puede tener instaladas varias bases de datos.
Suponga que est usando una cadena de conexin como la siguiente:
Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True;

Con esta cadena de conexin la propiedad DataSource devolver .\SQLExpress y la propiedad


Database Northwind. Ambas propiedades son de slo lectura y solamente se pueden configurar por medio
de la cadena de conexin.
La propiedad DataSource se puede configurar por medio de la propiedad del mismo nombre de
SqlConnectionStringBuilder o las palabras clave Data Source o Server en una cadena de conexin.
La propiedad Database se puede configurar por medio de la propiedad InitialCatalog de
SqlConnectionStringBuilder o las palabras clave Initial Catalog o Database en una cadena de conexin.

24

Escuela de Informtica del Ejrcito

FireInfoMessageEventOnUserErrors
Si intenta consultar una tabla que no existe generar una SqlException. Si asigna True a la propiedad
FireInfoMessageEventOnUserErrors se informar de los errores de usuario por medio del evento
InfoMessageEvent de SqlConnection, que se tratar ms adelante.
Esta propiedad es de lectura y escritura independientemente de si la conexin est abierta o cerrada.

PacketSize
Devuelve el tamao en bytes de los paquetes de red usados para comunicar con SQL Server. Esta
propiedad es de slo lectura y solamente se puede configurar por medio de la propiedad ConnectionString,
ya sea usando la propiedad PacketSize de SqlConnectionStringBuilder o la palabra clave Packet Size de
una cadena de conexin.
SqlConnection soporta tamaos de paquete entre 512 y 32.768 bytes. Si se intenta asignar un tamao
fuera de estos lmites se obtendr una ArgumentException.

ServerVersion
La mayor parte de los sistemas de base de datos introducen nuevas caractersticas en cada versin
sucesiva. Por ejemplo, SQL Server 2005 soporta muchas caractersticas que SQL Server 2000 no tiene,
como mltiples conjuntos de resultados activos en una nica conexin y el tipo de datos XML. Puede
comprobar la propiedad ServerVersion para asegurarse de que no hace llamadas que el servidor no
soporta.
La propiedad ServerVersion devuelve una cadena que contiene la versin de la base de datos con la que
se ha conectado. Los desarrolladores con experiencia en SQL Server pueden estar familiarizados con la
consulta SELECT @@Version. La propiedad ServerVersion devuelve un subconjunto de la informacin que
proporciona esta consulta el nmero de versin del servidor de base de datos.
Supongamos que su aplicacin espera comunicarse con un servidor SQL Server 2005, pero necesita
confirmar en tiempo de ejecucin que es as. La cadena devuelta por la propiedad ServerVersion
comenzar por 09 si la base de de datos es SQL Server 2005, por lo que puede usar el cdigo siguiente:
string cadcon = @Data Source=.\SQLExpress;Integrated Security=True;
SqlConnection conexin = new SqlConnection(cadcon);
conexin.Open();
if(string.Compare(conexin.ServerVersion, 09) >= 0){
//Esta trabajando con SQL Server 2005 o posterior
}else{
//Esta trabajando con una versin anterior de SQL Server
}

La propiedad ServerVersion solo est disponible sobre conexiones abiertas. El intento de acceder a esta
propiedad con la conexin cerrada provocar una InvalidOperationException.

State
Esta propiedad devuelve el estado actual de la conexin en forma de un miembro de la enumeracin
ConnectionState del espacio de nombres System.Data. En ADO.NET 2.0 esta propiedad devolver Open o
Closed; el resto de valores se podrn utilizar en versiones futuras.
Puede usar el evento StateChange de la clase SqlConnection para determinar cundo cambia el valor de
la propiedad State.

WorkstationId
Si ha utilizado caractersticas de traza de SQL Server en el pasado puede que haya observado en la
traza una columna llamada HostName que por lo general contiene el nombre de la mquina cliente
conectada a SQL Server. La propiedad WorkstationId de SqlConnection devuelve la misma informacin.
Esta propiedad es de solo lectura y de forma predeterminada contiene el nombre de mquina. Puede
controlar el valor que devuelve esta propiedad usando la palabra clave Workstation ID en la cadena de
conexin o la propiedad WorkstationId de SqlConnectionStringBuilder.

2.9.2

Mtodos

La tabla 3.2 muestra los mtodos de la clase SqlConnection. Se omiten mtodos como GetType o
ToString comunes a todos los objetos del marco de trabajo .NET. Examine la tabla para familiarizarse con
estos mtodos.
Tabla 3. 2: Mtodos de uso habitual de la clase SqlConnection

Mtodo
BeginTransaction
ChangeDatabase

Descripcin
Comienza una transaccin sobre una conexin
Cambia la base de datos actual en una conexin abierta

25

ADO.NET 2.0

Tabla 3. 2: Mtodos de uso habitual de la clase SqlConnection

Mtodo
ClearAllPools
ClearPool
Close
CreateCommand
EnlistDistributedTransaction
EnlistTransaction
GetSchema
Open
ResetStatistics
RetrieveStatistics

Descripcin
Limpia todas las conexiones libres en todos los agrupamientos de SqlConnection.
Esttico.
Limpia todas las conexiones libres en el agrupamiento de conexiones asociado con el
SqlConnection que se le suministra. Esttico.
Cierra la conexin.
Crea un SqlCommand para la conexin actual.
Enrola manualmente la conexin en una transaccin distribuida COM+
Enrola manualmente la conexin en una transaccin System.Transactions.
Devuelve informacin de esquema para la conexin.
Abre la conexin.
Restablece las estadsticas para la conexin actual.
Devuelve las estadsticas para la conexin actual.

BeginTransaction
Si quiere comenzar una transaccin sobre una conexin para bloquear datos o asegurar que puede
consignar o anular una serie de cambios al almacn de datos llame al mtodo BeginTransaction del objeto
SqlConnection. Este mtodo devuelve una instancia de la clase SqlTransaction, que se tratar ms
adelante.
Como BeginTransaction crea una nueva transaccin, la asocia con la conexin que la ha creado, e
inicializa la transaccin, el uso de este mtodo puede simplificar algo su cdigo. Los dos siguientes
fragmentos son equivalentes:
SqlTransaction trn =
conexin.BeginTransaction();

SqlTransaction trn = new SqlTransaction();


trn.Connection = conexin;
trn.Begin();

ChangeDatabase
En este captulo se ha hablado de la capacidad de SQL Server para soportar varias bases de datos en
un nico servidor. Puede cambiar la base de datos con la que se est comunicando ejecutando una
consulta de la forma USE Northwind.
ADO.NET proporciona un mtodo para cambiar de base de datos. La clase SqlConnection tiene un
mtodo ChangeDatabase que simplifica el proceso. Los dos fragmentos siguientes son equivalentes:
SqlConnection conexin = new
SqlConnection(cadcon);
conexin.Open();
...
conexin.ChangeDatabase(Northwind);

SqlConnection conexin = new


SqlConnection(cadcon);
conexin.Open();
...
SqlCommand comando = conexin.CreateCommand();
comando.CommandText = USE Northwind;
comando.ExecuteNonQuery();

ClearPool y ClearAllPools
Para limpiar manualmente un agrupamiento de conexiones llame al mtodo esttico ClearPool de la
clase SqlConnection y psele un objeto SqlConnection. Para limpiar todos los agrupamientos llame al
mtodo ClearAllPools.
Recuerde que solo se liberan las conexiones que se encuentran actualmente en el agrupamiento; las
conexiones en uso no se vern afectadas por la llamada a ninguno de estos mtodos.

Close
Para cerrar un objeto SqlConnection llame al mtodo Close. Recuerde que si est usando agrupamiento
de conexiones estar enviando la conexin fsica con el origen de datos al agrupamiento.
Llamar a Close sobre un objeto SqlConnection que ya est marcado como cerrado no genera una
excepcin. Una llamada a Dispose sobre un objeto SqlConnection llama implcitamente a su mtodo Close.

CreateCommand
Puede crear objetos SqlCommand usando el mtodo CreateCommand de la clase SqlConnection. Este
mtodo no recibe ningn argumento y devuelve un nuevo objeto SqlCommand cuya propiedad Connection
est configurada con el SqlConnection que lo ha creado.
Los dos siguientes fragmentos son equivalentes:
SqlConnection conexin = new
SqlConnection(cadcon);
SqlCommand comando =
conexin.CreateCommand();

26

SqlConnection conexin = new


SqlConnection(cadcon);
SqlCommand comando = new SqlCommand();
comando.Connection = conexin;

Escuela de Informtica del Ejrcito

EnlistDistributedTransaction
Los objetos SqlConnection de forma predeterminada se enrolan automticamente en transacciones
distribuidas COM+. Pero si necesita enrolar un objeto SqlConnection en una transaccin distribuida COM+
de forma manual use el mtodo EnlistDistributedTransaction. Moldee la propiedad Transaction del contexto
actual al interface ITransaction y psela al mtodo EnlistDistributedTransaction de SqlConnection, como se
muestra en el siguiente cdigo, que asume una referencia a System.EnterpriseServices. Este cdigo no
crea una transaccin COM+, por lo que no se ejecutar.
string cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind;
+ Integrated Security=True;;
using(SqlConnection conexin = new SqlConnection(cadcon)){
conexin.Open();
ITransaction transaccin = (ITransaction)ContextUtil.Transaction;
try{
conexin.EnlistDistributedTransaction(transaccin);
//Hace su trabajo y consigna la transaccin
ContextUtil.SetComplete();
}catch(Exception ex){
//Maneja la excepcin y anula la transaccin
ContextUtil.SetAbort();
}finally{
//Libera la conexin de la transaccin distribuida
conexin.EnlistDistributedTransaction(null);
}
}

Recuerde hacer limpieza. Una vez consignada o anulada la transaccin distribuida libere el
SqlConnection de la transaccin distribuida llamando a EnlistDistributedTransaction pasando null.

EnlistTransaction
Este mtodo es casi idntico al anterior, salvo que est diseado para trabajar con transacciones
System.Transactions, nuevas en la versin 2.0 del marco de trabajo .NET. El siguiente fragmento asume
una referencia al espacio de nombres System.Transactions:
string cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind;
+ Integrated Security=True;
using(SqlConnection conexin = new SqlConnection(cadcon)){
conexin.Open();
using(CommittableTransaction transaccin = new CommittableTransaction()){
try{
conexin.EnlistTransaction(transaccin);
//Realiza el trabajo y consigna la transaccin
transaccin.Commit();
}catch(Exception ex){
//Maneja la excepcin y anula la transaccin
transaccin.Rollback();
}finally{
//Libera la conexin de la transaccin distribuida
conexin.EnlistDistributedTransaction(null);
}
}
}

Como en el caso de EnlistDistributedTransaction, recuerde hacer limpieza. Si enrola manualmente el


SqlConnection en una transaccin System.Transactions debe desconectar el SqlConnection de la
transaccin distribuida cuando termine llamando a EnlistTransaction con el parmetro null.

GetSchema
Este mtodo puede ayudarle a examinar mediante programacin el esquema de la base de datos.
Devuelve un DataTable con informacin de esquema y est sobrecargado. Use la sobrecarga sin
parmetros para recuperar una lista de los parmetros disponibles. Para recuperar un esquema especfico,
como una lista de columnas, use la sobrecarga que recibe un parmetro de cadena con el nombre del
esquema. Para aplicar restricciones de modo que obtenga, por ejemplo, solo las columnas de una tabla
concreta utilice la sobrecarga que recibe una cadena para el nombre del esquema y un array de cadenas
para los valores de restriccin.

Open
Para abrir una conexin con su origen de datos llame al mtodo Open de SqlConnection. El objeto
SqlConnection intentar conectar con el origen de datos en base a la informacin proporcionada por la
propiedad ConnectionString. Si el intento falla se lanzar una excepcin.
27

ADO.NET 2.0

Si se llama a Open
InvalidOperationException.

sobre

un

objeto

SqlConnection

ya

abierto

se

obtendr

una

RetrieveStatistics y ResetStatistics
La clase SqlConnection permite recuperar estadsticas sobre la conexin actual. Puede que quiera saber
cuanto tiempo ha estado conectado, cuantos bytes se han enviado al servidor, cuantos se han recibido, etc.
El mtodo RetrieveStatistics devuelve un conjunto de claves y valores. Aunque la firma del mtodo dice
que devuelve un IDictionary en realidad devuelve un Hashtable. Estas colecciones son parte del espacio de
nombres System.Collections y el cdigo debe incluir referencia a este espacio de nombres. Puede usar el
siguiente cdigo para recuperar y mostrar estas estadsticas:
string cadcon = @"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;" +
"Integrated Security=True";
SqlConnection conexin = new SqlConnection(cadcon);
conexin.Open();
conexin.StatisticsEnabled = true;
//Trabaja con la base de datos
IDictionary estadstica = conexin.RetrieveStatistics();
foreach(object clave in estadstica.Keys)
Console.WriteLine("{0,-20}: {1}", clave, estadstica[clave]);
conexin.Close();

2.9.3

Eventos

La clase SqlConnection expone los dos eventos que muestra la siguiente tabla:
Tabla 3. 3: Eventos de la clase SqlConnection

Evento
InfoMessage
StateChange

Descripcin
Se lanza cuando la conexin recibe un mensaje informativo desde el origen de datos
Se lanza cuando cambia la propiedad State de la conexin

InfoMessage
Algunos sistemas de base de datos, como SQL Server, soportan mensajes informativos. SQL Server
permite enviar mensajes al cliente por medio del comando PRINT. Estos mensajes no se devuelven como
errores, ni se incluyen como resultado en la consulta.
Puede usar el evento InfoMessage de la clase SqlConnection para capturar estos mensajes. El siguiente
fragmento de cdigo muestra cmo puede registrar mensajes informativos. Tambin puede forzar a la
conexin a indicar los errores en la consulta, como intentar consultar una tabla que no existe, por medio del
evento InfoMessage en lugar de lanzar una excepcin asignando el valor True a la propiedad
FireInfoMessageEventOnUserErrors de SqlConnection.
static void Main(string[] args) {
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
SqlConnection conexin = new SqlConnection(cadcon);
conexin.InfoMessage += new SqlInfoMessageEventHandler(conexin_InfoMessage);
SqlCommand comando = conexin.CreateCommand();
comando.CommandText = "PRINT 'Bienvenido a ADO.NET'";
conexin.Open();
comando.ExecuteNonQuery();
conexin.Close();
}
static void conexin_InfoMessage(object sender, SqlInfoMessageEventArgs e) {
Console.WriteLine("Se ha producido un evento InfoMessage");
Console.WriteLine("
Mensaje recibido: {0}", e.Message);
}

SQL Server tambin soporta generar mensajes informativos por medio del comando RAISERROR. Los
errores creados con este comando se tratan como mensajes informativos si la severidad de error es 10 o
menor. Para ms informacin vea la documentacin de SQL Server.

StateChange
El evento StateChange de la clase SqlConnection se lanza siempre que cambia el valor de la propiedad
State. Este evento se puede mostrar til si quiere mostrar el estado actual de una conexin, por ejemplo, en
una barra de estado en el formulario principal de la aplicacin.

28

Escuela de Informtica del Ejrcito

static void Main(string[] args) {


string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
SqlConnection conexin = new SqlConnection(cadcon);
conexin.StateChange +=
new System.Data.StateChangeEventHandler(conexin_StateChange);
conexin.Open();
conexin.Close();
}
static void conexin_StateChange(object sender, System.Data.StateChangeEventArgs e) {
Console.WriteLine("El estado ha cambiado de {0} a {1}",
e.OriginalState, e.CurrentState);
}

29

ADO.NET 2.0

30

Escuela de Informtica del Ejrcito

Consultar la base de datos

En el Captulo 2 aprendi a conectar con una base de datos usando la clase SqlConnection de
ADO.NET. Ahora es el momento de ejecutar consultas contra la base de datos. En el modelo de objetos
ADO.NET las consultas se ejecutan usando las clases Command.
En este captulo nos centraremos primero en tareas especficas que implican la clase SqlCommand.
Examinaremos la forma de ejecutar consultas que recuperan resultados y consultas que modifican, en lugar
de recuperar, los datos. Veremos las consultas parametrizadas, que pueden ayudar a simplificar el cdigo y
a impedir que los usuarios cambien la intencin de la consulta. Este captulo tambin explora algunas
nuevas caractersticas de ADO.NET y SQL Server mltiples conjuntos de resultados activos (MARS) y
consultas asncronas.
Tambin presentaremos otras dos clases ADO.NET: SqlDataReader y SqlParameter. Los objetos
SqlDataReader permiten examinar los resultados de sus consultas y los objetos SqlParameter permiten
ejecutar consultas parametrizadas. Finalmente examinaremos las propiedades, mtodos y eventos de las
clases SqlCommand, SqlDataReader y SqlParameter.

3.1 Usar objetos SqlCommand en el cdigo


Los objetos SqlCommand permiten ejecutar muchos tipos de consulta diferentes. Algunos objetos
SqlCommand recuperan datos en forma de un conjunto de resultados, y otros modifican el contenido o
estructura del almacn de datos. Vamos a ver cmo crear objetos SqlCommand y usarlos para realizar
diferentes tareas.

3.1.1

Crear un objeto SqlCommand

Hay tres formas de crear un objeto SqlCommand. La primera es simplemente crear una instancia de la
clase usando la palabra clave new y configurar las propiedades adecuadas. La segunda es usar uno de los
constructores disponibles para especificar la cadena de consulta y un objeto SqlConnection. La tercera
consiste en llamar al mtodo CreateCommand de SqlConnection, como se vio en el Captulo 2.
El siguiente fragmento de cdigo muestra las tres formas:
string cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind; +
Integrated Security=True;
string consulta = SELECT CustomerID, CompanyName FROM Customers;
SqlConnection conexin = new SqlConnection(cadcon);
conexin.Open();
SqlCommand comando;
//Constructor sin parmetros
comando = new SqlCommand();
comando.Connection = conexin;
comando.CommandText = consulta;
//Constructor con parmetros
comando = new SqlCommand(consulta, conexin);
//Mtodo CreateCommand de SqlConnection
comando = conexin.CreateCommand;
comando.CommandText = consulta;
conexin.Close();

3.1.2

Ejecutar una consulta que devuelve filas

El uso ms habitual para un SqlCommand es ejecutar una consulta que devuelve resultados, ejecutando
una consulta SELECT de SQL.
Para ejecutar este tipo de consulta lo primero que necesita es asignar a la propiedad CommandText del
objeto SqlCommand una cadena que contenga el texto de la consulta. Despus llame al mtodo
ExecuteReader:
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated security=True";
string consulta = "SELECT OrderID, CustomerID, OrderDate, ShippedDate, ShipCity " +
"FROM Orders WHERE ShipCountry = 'Spain'";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(cadcon, conexin);
comando.CommandText = consulta;
conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
conexin.Close();

31

ADO.NET 2.0

Como puede ver, el mtodo ExecuteReader devuelve un objeto SqlDataReader. Este objeto es el que le
permitir examinar los resultados de la consulta. Discutiremos SqlDataReader en profundidad dentro de
poco, pero en lugar de dejar las cosas colgando u obligarle a saltar por el libro, explicaremos lo bsico aqu.
SqlDataReader permite examinar los resultados de la consulta usando una aproximacin basada en
flujo. Puede examinar los resultados fila por fila. Una vez que pasa a la siguiente fila los contenidos de la
anterior dejan de estar disponibles. Existen varias formas de acceder al valor de una columna concreta
bsquedas basadas en columna o en ordinal, y accesores con y sin tipo. Por ahora, usaremos la forma ms
sencilla.
El siguiente cdigo recorre las filas devueltas por la consulta y escribe los valores de las columnas
OrderID, CustomerID y OrderDate en la ventana de consola antes de cerrar el SqlDataReader. Las fechas
almacenadas en la base de datos no incluyen la informacin de hora del da. El cdigo especifica :d en la
llamada a Console.WriteLine para usar el formato fecha corta, simplificando la salida y hacindola ms
fcil de leer.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated security=True";
string consulta = "SELECT OrderID, CustomerID, OrderDate, ShippedDate, ShipCity " +
"FROM Orders WHERE ShipCountry = 'Spain'";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(cadcon, conexin);
comando.CommandText = consulta;
conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
while(lector.Read())
Console.WriteLine("{0} {1} {2:d}", lector["OrderID"], lector["CustomerID"],
lector["OrderDate"]);
lector.Close();
conexin.Close();

En el fragmento anterior la llamada al mtodo Read cumple dos tareas. Primero, coloca el
SqlDataReader en la siguiente fila del conjunto de resultados. Segundo, devuelve un valor boolean que
indica si hay una fila disponible. De esta forma el cdigo llama continuamente al mtodo Read en un bucle,
mostrando los resultados siempre que Read devuelva True. Una vez que Read devuelve False el cdigo
sale del bucle y cierra el lector y la conexin.
Para mostrar el contenido de la fila de datos disponible actualmente el cdigo usa el indexador
predeterminado de la clase SqlDataReader. Pase el nombre de la columna al indexador predeterminado y el
SqlDataReader devolver el contenido de la columna usando el tipo de datos genrico Object.

3.1.3

Recuperar un valor nico

Hay ocasiones en que una consulta devuelve un nico valor. Por ejemplo, una consulta puede
determinar cuntas filas existen en una tabla o algo ms complejo como el total de todos los pedidos
hechos por un cliente concreto con una consulta similar a esta:
SELECT SUM([Order Details].UnitPrice * [Order Details].Quantity)
FROM Orders INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID
WHERE Orders.CustomerID = ALFKI

Consultas como esta son habituales en muchas aplicaciones. El siguiente fragmento ejecuta la consulta
y escribe el total de pedidos en la ventana de consola. Crear un SqlDataReader, recuperar el contenido de
la primera columna de la primera fila y cerrar el lector, todo para obtener un nico valor, parece mucha
sobrecarga.
Para ayudar a simplificar estos escenarios la clase SqlCommand expone el mtodo ExecuteScalar. En
lugar de devolver un DataReader este mtodo devuelve el primer valor de la primera fila con el tipo de datos
genrico Object.
Este cdigo tambin incluye un truco de formato. Como la consulta devuelve un total de pedidos, el
cdigo usa :c en la llamada a Console.WriteLine para que la salida tenga el formato de moneda del
sistema.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated security=True";
string consulta = "SELECT SUM([Order Details].UnitPrice * [Order Details].Quantity) "
+ "FROM Orders INNER JOIN [Order Details] "
+ "ON Orders.OrderID = [Order Details].OrderID "
+ "WHERE Orders.CustomerID = 'ALFKI'";

32

Escuela de Informtica del Ejrcito

SqlConnection conexin = new SqlConnection(cadcon);


SqlCommand comando = new SqlCommand(cadcon, conexin);
comando.CommandText = consulta;
conexin.Open();
Console.WriteLine("Total de pedidos: {0:c}", comando.ExecuteScalar());
conexin.Close();

3.1.4

Ejecutar una consulta que no devuelve un conjunto de resultados

Las consultas que no devuelven resultados por lo general son consultas de accin. Existen dos
categoras principales de consultas de accin:
Consultas de lenguaje de manipulacin de datos (DML): Tambin conocidas como actualizacin
basada en consulta (QBU), modifican el contenido de la base de datos.
Consultas de lenguaje de definicin de datos (DDL): Modifican la estructura de la base de datos.
Podra ejecutar estas consultas llamando al mtodo ExecuteReader de SqlCommand. Sin embargo,
como estas consultas no devuelven filas, este medio parece una sobrecarga innecesaria. Afortunadamente,
hay una forma ms sencilla. La clase SqlCommand expone un mtodo ExecuteNonQuery que ejecuta la
consulta sin devolver un objeto SqlDataReader. Por ejemplo:
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated security=True";
string consulta = "UPDATE Customers SET CompanyName = ValorNuevo " +
WHERE CustomerID = ALFKI;
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(cadcon, conexin);
comando.CommandText = consulta;
conexin.Open();
comando.ExecuteNonQuery();
conexin.Close();

A pesar de lo que parece indicar su nombre, el mtodo ExecuteNonQuery ejecuta una consulta
perfectamente vlida, solo que es un tipo de consulta que no devuelve filas.
Sin embargo, ejecutar una consulta de accin a menudo es solo la mitad de la tarea.
Cuando se ejecuta la siguiente consulta, de tipo DDL, hay dos posibles resultados, xito o fracaso:
CREATE TABLE TablaNueva(IdTblNueva int NOT NULL CONSTRAINT PK_TablaNueva PRIMARY KEY,
OtroCampo varchar(32))

La consulta puede crear con xito la nueva tabla o fallar. Algunos posibles motivos de fallos son que ya
exista una tabla con el mismo nombre, que no se use la sintaxis correcta para la consulta, o que no exista
una conexin abierta con la base de datos. Lo que importa es que su ejecuta la consulta y no genera un
error, la tabla se ha creado correctamente.
Con consultas de accin diseadas para modificar o borrar una fila existente es necesario algo ms que
ejecutar la consulta con xito. Vamos a examinar una consulta que cambia el nombre de compaa de un
cliente concreto.
UPDATE Customers SET CompanyName = ValorNuevo
WHERE CustomerID = ALFKI

Aqu hay una complicacin especfica: ejecutar esta consulta modifica la fila correspondiente en la tabla
Customers. En algunos casos la ejecucin de la consulta podra no modificar el nombre de compaa del
cliente. Por ejemplo, otro usuario podra haber borrado esta fila de la tabla, o cambiado el valor de la
columna CustomerID. En estos casos la base de datos ejecutar la consulta, pero como no existe una fila
que cumpla el criterio de la clusula WHERE la consulta no modificar ninguna fila.
Para la base de datos este resultado no constituye un error; la base de datos ha hecho lo que se le ha
pedido sin ningn verdadero error.
Por tanto puede afirmar que ha modificado alguna fila?. Si ejecuta la consulta usando el interface de
SQL Server, le responder con el nmero de filas afectado. SqlCommand le permite recuperar esta
informacin devolviendo el nmero de filas afectado como valor de retorno de ExecuteNonQuery.
Comprobando este valor, y sabiendo cuantas filas espera que se vean afectadas por la consulta, puede
saber si todo ha ido bien. Si ejecuta alguna consulta que no sea ExecuteNonQuery devuelve -1.

3.1.5

Ejecutar lotes de consultas de accin

Suponga que quiere ejecutar una serie de consultas de accin en un lote. Por ejemplo, suponga que
tiene una tabla Products y quiere cambiar el precio por unidad de algunos productos segn la categora del
33

ADO.NET 2.0

producto, subiendo unos y bajando otros. Podra agrupar en un lote las siguientes consultas para
ejecutarlas como una sola:
UPDATE Products SET UnitPrice = UnitPrice * 0.85 WHERE CategoryID = 3;
UPDATE Products SET UnitPrice = UnitPrice * 1.15 WHERE CategoryID = 4;
UPDATE Products SET UnitPrice = UnitPrice * 0.75 WHERE CategoryID = 5;

Qu pasa si quiere saber cuntas filas se ven afectadas por cada consulta?. Lo ideal sera llamar a un
mtodo simple como ExecuteNonQuery y que este mtodo devolviera un array de valores en el que cada
entrada correspondiera al nmero de filas modificada por cada consulta del lote.
Ni ExecuteReader ni ExecuteNonQuery por si solos pueden hacer todo el trabajo. Ambos mtodos
permite ejecutar el lote de consultas, pero ninguno le dice cuantas filas modifica cada consulta. El valor
devuelto por el mtodo ExecuteNonQuery dice cuantas filas ha modificado el lote completo, pero esta
informacin puede no ser suficiente para sus necesidades.
En ADO.NET 2.0 SqlCommand incluye un evento StatementCompleted que puede usar para obtener
esta informacin. El argumento principal de este evento tiene una nica propiedad interesante:
RecordCount, que contiene la informacin que estamos buscando.
El siguiente fragmento muestra cmo puede usar este evento para determinar el nmero de filas
modificadas por consultas individuales de un lote. El cdigo incluye un procedimiento para manejar el
evento StatementCompleted.
static void Main(string[] args) {
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta =
"UPDATE Products SET UnitPrice = UnitPrice * 0.85 WHERE CategoryID = 3;" +
"UPDATE Products SET UnitPrice = UnitPrice * 1.15 WHERE CategoryID = 4;" +
"UPDATE Products SET UnitPrice = UnitPrice * 0.75 WHERE CategoryID = 5;";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
comando.StatementCompleted +=
new System.Data.StatementCompletedEventHandler(comando_StatementCompleted);
conexin.Open();
int totalFilasAfectadas = comando.ExecuteNonQuery();
conexin.Close();
Console.WriteLine("Total de filas afectadas: {0}", totalFilasAfectadas);
}
static void comando_StatementCompleted(object sender,
System.Data.StatementCompletedEventArgs e) {
Console.WriteLine("{0} filas afectadas por la sentencia", e.RecordCount);
}

3.1.6

Ejecutar consultas para recuperar datos XML

SQL Server soporta consultas que devuelven datos en un flujo de XML. Si est trabajando con este tipo
de consulta puede llamar al mtodo ExecuteXmlReader de SqlCommand para obtener los resultados por
medio de un XmlReader en lugar de SqlDataReader. Examinaremos este escenario ms adelante.

3.1.7

Ejecutar una consulta en una transaccin

La clase SqlCommand tiene una propiedad Transaction que debe configurar para ejecutar su
SqlCommand dentro de un SqlTransaction. En el captulo anterior vio cmo crear un objeto SqlTransaction
usando el mtodo BeginTransaction de SqlConnection.
Si inicia una SqlTransaction sobre su SqlConnection debe asociar todas las consultas con esta
transaccin, o recibir una InvalidOperationException con un mensaje similar a ExecuteReader requiere
que el comando tenga una transaccin cuando la conexin asignada al mismo est en una transaccin local
pendiente. No se ha inicializado la propiedad Transaction del comando.
Hay dos formas de asociar un SqlCommand con un SqlTransaction. Puede asignar el SqlTransaction a la
propiedad Transaction del SqlCommand, o puede pasar el SqlTransaction al constructor del SqlCommand.
El siguiente cdigo utiliza el segundo mtodo. Tras ejecutar la consulta y determinar a cuntas filas ha
afectado el cdigo llama al mtodo de cancelacin de la transaccin para impedir que los cambios se hagan
permanentes.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "UPDATE Products SET UnitPrice = UnitPrice * .7 " +
"WHERE CategoryID = 1";

34

Escuela de Informtica del Ejrcito

SqlConnection conexin = new SqlConnection(cadcon);


conexin.Open();
using(SqlTransaction transaccin = conexin.BeginTransaction()) {
SqlCommand comando = new SqlCommand(consulta, conexin, transaccin);
int registrosAfectados = comando.ExecuteNonQuery();
Console.WriteLine("{0} registros afectados", registrosAfectados);
transaccin.Rollback();
}
conexin.Close();

Al usar la transaccin dentro de un bloque using como en el ejemplo anterior se asegura de que no
queda abierta durante un tiempo excesivo. Mientras ms tiempo se mantenga la transaccin abierta, ms
tiempo debe mantener la base de datos los bloqueos para la transaccin, y ms posibilidades existen de
que varios usuarios intenten bloquear las mismas filas. Si el SqlTransaction no se ha consignado o
cancelado al final del bloque using se llama implcitamente al mtodo Rollback.

3.1.8

Ejecutar una consulta asncronamente

En ADO.NET 2.0 SqlCommand soporta consultas asncronas. Algunas consultas terminan casi
inmediatamente, otras necesitan un cierto tiempo. Si est trabajando con consultas que necesitan tiempo y
quiere realizar otras tareas mientras SQL Server procesa el resultado utilice consultas asncronas. Esta
caracterstica funciona con SQL Server 2005, SQL Server 2000 y SQL Server 7.0.
Cuando se ejecuta una consulta usando el modo sncrono estndar la llamada no se completar hasta
que SQL Server haya procesado la consulta y devuelva la primera fila disponible del conjunto de datos.
Puede tener otro trabajo que quiera realizar mientras espera que SQL Server responda, como ejecutar una
consulta contra otra base de datos o llamar a un servicio Web para recuperar otra informacin.
Si ha trabajado con otras caractersticas asncronas del marco de trabajo .NET el soporte para consultas
asncronas de SqlCommand le ser familiar. La clase SqlCommand expone mtodos Begin y End para los
mtodos ExecuteReader, ExecuteNonQuery y ExecuteXmlReader. Por ejemplo, existe un mtodo
BeginExecuteReader y un mtodo EndExecuteReader.
Cada mtodo Begin devuelve un objeto que implementa el interface IAsyncResult. Este objeto es un
manipulador del estado de la consulta. El interface IAsyncResult es parte del patrn del marco de trabajo
.NET para mtodos asncronos. Est diseado para ayudarle a determinar si se ha completado la
operacin, bloquear la hebra actual si necesita esperar que la operacin se complete y servir como
manipulador de los resultados de la llamada al mtodo. En los prximos ejemplos que muestran cmo
puede ejecutar consultas asncronamente usando SqlCommand tambin se muestran estas caractersticas
del interface IAsyncResult.
Para simular una consulta de larga duracin en nuestros ejemplos precederemos una consulta sencilla
por una sentencia WAITFOR DELAY de SQL Server, que indica un tiempo de espera en segundos.
La caracterstica de procesamiento asncrono no est activada de forma predeterminada. Para activar las
consultas asncronas en su SqlConnection aada Asynchronous Processing=True; como otras opciones de
la cadena de conexin, esta tiene su propiedad equivalente en SqlConnectionStringBuilder.

Ejecutar una consulta asncronamente y esperar los resultados


Supongamos que quiere ejecutar una consulta asncronamente, realizando una serie de otras
operaciones. Una vez que estas operaciones se han completado esperaremos hasta que los resultados
estn disponibles. Los ejemplos asumen una referencia al espacio de nombres System.Threading.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True;Asynchronous Processing=True";
string consulta = "WAITFOR DELAY '00:00:10'; SELECT * FROM Customers";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
conexin.Open();
IAsyncResult resultado = comando.BeginExecuteReader();
//Hace otros trabajos
SqlDataReader lector = comando.EndExecuteReader(resultado);
while(lector.Read())
Console.WriteLine(lector["CustomerID"]);
lector.Close();
conexin.Close();

35

ADO.NET 2.0

Podramos haber consultado la propiedad IsCompleted del interface IAsyncResult para ver si se ha
completado la tarea de BeginExecuteReader. Esta comprobacin puede ser til si ha hecho varias
peticiones asncronas y quiere ver cules se han completado para manejar sus resultados primero. En este
caso, si la llamada no se ha completado no hay otra tarea que ejecutar, por lo que tendramos que esperar
que la tarea se completara de todas formas, y este es el comportamiento predeterminado de los mtodos
End. Si llama a un mtodo End no terminar hasta que el mtodo Begin no se complete.
Tambin puede usar el interface IAsyncResult para esperar a que se complete la llamada llamando al
mtodo IAsyncResult.AsyncWaitHandle.WaitOne. La sobrecarga de este mtodo que acepta parmetros
permite especificar el tiempo, en milisegundos, tras el cual se puede comprobar el mtodo IsCompleted
para determinar si la llamada se ha completado. Con el mtodo WaitOne puede implementar lgica de tipo
hemos llegado ya? en la que el cdigo pregunta si la operacin se ha completado cada cierto tiempo; en
el siguiente ejemplo el tiempo son tres segundos:
...
IAsyncResult resultado = comando.BeginExecuteReader();
Console.WriteLine("Hemos llegado ya?");
while(!resultado.IsCompleted) {
Console.WriteLine("No!!!!");
resultado.AsyncWaitHandle.WaitOne(3000, true);
Console.WriteLine("Hemos llegado ya?");
}
Console.WriteLine("Si");

Ejecutar varias consultas asncronamente y esperar que termine una


Un escenario de consulta asncrona an ms til implica pginas ASP.NET que deben ejecutar consultas
contra varias bases de datos. Usando los mtodos asncronos sobre varios SqlCommand puede lanzar una
serie de consultas asncronamente. Puede usar el mtodo WaitOne para esperar a que una de ellas se
complete.
Pero cmo saber cul se completa primero?. Puede procesar los resultados en el orden en que ejecut
las consultas, pero nada garantiza que la primera consulta ejecutada sea la primera en completarse. Si
procesa los resultados en este orden y la segunda consulta se completa antes que la primera, no estar
sirviendo las pginas con toda la rapidez posible.
Una opcin incluso ms potente es usar las caractersticas de hebras del marco de trabajo .NET y
esperar a que cualquiera de las consultas se complete. La clase WaitHandle del espacio de nombres
System.Threading expone los mtodos estticos WaitAll y WaitAny como ayuda para un escenario de este
tipo. Estos mtodos aceptan un array de WaitHandle. El mtodo WaitAny sale tan pronto como cualquiera
de las operaciones asncronas asociadas con los WaitHandle se completa. El mtodo devuelve el ndice del
WaitHandle cuya operacin se ha completado. El mtodo WaitAll es similar, salvo que espera a que se
completen todas las operaciones asncronas.
Usaremos WaitAny en un ejemplo poco ms adelante.

Pasar estado adicional al mtodo Begin


El mtodo Begin incluye sobrecargas que permiten pasar un objeto de estado. Este parmetro acepta
cualquier objeto y lo asignar a la propiedad AsyncState del interface IAsyncResult.
El siguiente fragmento muestra cmo puede usar esta caracterstica en su cdigo. El fragmento abre dos
conexiones una para recuperar informacin de cliente y otra para recuperar informacin de pedido. Ambas
usan la misma cadena de conexin para simplificar el escenario. En una aplicacin real podran trabajar con
el mismo servidor o con servidores diferentes. El cdigo realiza una consulta asncrona sobre cada
conexin; aqu es donde empieza a ponerse interesante.
Una vez que el cdigo ejecuta las dos consultas asncronamente usa el mtodo WaitAny sobre la clase
WaitHandle para esperar a que se complete alguna de las consultas. El mtodo WaitAny acepta un array de
WaitHandle y devuelve el ndice del WaitHandle correspondiente a la operacin que se ha completado. El
reto en este punto es determinar lo que significa este ndice. Qu operacin se ha completado?.
Una forma puede ser codificar que el primer elemento del array corresponde a la consulta de clientes y el
segundo a la de pedidos. Entonces podra comprobar el valor de retorno de WaitAny en una sentencia
switch para determinar el SqlCommand a cuyo mtodo EndExecuteReader se llamar.
Un segundo mtodo consiste en pasar informacin de estado adicional al mtodo BeginExecuteReader.
Puede pasar cadenas como Clientes y Pedidos; en este ejemplo pasamos los objetos SqlCommand. De
esta forma, el objeto SqlCommand est disponible por medio de la propiedad AsyncState del objeto
IAsyncResult. As, cuando WaitAny termina, accedemos al objeto IAsyncResult y simplemente moldeamos
la propiedad AsyncState a un SqlCommand, procesamos el resultado y cerramos su conexin.
36

Escuela de Informtica del Ejrcito

string cadcon, consulta;


SqlConnection conClientes, conPedidos;
SqlCommand comanClientes, comanPedidos;
IAsyncResult[] resultados = new IAsyncResult[2];
WaitHandle[] esperas = new WaitHandle[2];
cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True;Asynchronous Processing=True;";
conClientes = new SqlConnection(cadcon);
conPedidos = new SqlConnection(cadcon);
consulta = "WAITFOR DELAY '00:00:10';SELECT TOP 10 CustomerID FROM Customers";
comanClientes = new SqlCommand(consulta, conClientes);
conClientes.Open();
resultados[0] = comanClientes.BeginExecuteReader(
null, comanClientes, CommandBehavior.CloseConnection);
esperas[0] = resultados[0].AsyncWaitHandle;
consulta = "WAITFOR DELAY '00:00:05';SELECT TOP 10 OrderID FROM Orders";
comanPedidos = new SqlCommand(consulta, conPedidos);
conPedidos.Open();
resultados[1] = comanPedidos.BeginExecuteReader(
null, comanPedidos, CommandBehavior.CloseConnection);
esperas[1] = resultados[1].AsyncWaitHandle;
for(int contador = 0; contador < esperas.Length; contador++) {
int ndice = WaitHandle.WaitAny(esperas);
SqlCommand comando = (SqlCommand)resultados[ndice].AsyncState;
Console.WriteLine(comando.CommandText);
using(SqlDataReader lector = comando.EndExecuteReader(resultados[ndice])) {
while(lector.Read())
Console.WriteLine(lector[0]);
lector.Close();
}
}

El fragmento anterior usa CommandBehavior.CloseConnection cuando ejecuta las consultas. Cuando se


especifica esta opcin en la llamada a ExecuteReader (o BeginExecuteReader), al cerrar el SqlDataReader
se cierra tambin la conexin correspondiente. Esta opcin puede ayudarle a escribir cdigo ms fiable,
especialmente si el cdigo que consume el SqlDataReader no tiene acceso al SqlConnection.

Ejecutar una consulta asncronamente con una funcin de devolucin de llamada


Habr observado en el fragmento anterior que no hemos pasado datos en el primer argumento de
BeginExecuteReader. El primer parmetro de esta sobrecarga acepta una funcin de devolucin de
llamada. Si pasa esta funcin al mtodo, cuando la operacin asncrona se complete se llamar a esta
funcin. Entonces podr llamar a EndExecuteReader desde esta funcin.
El siguiente ejemplo muestra el uso de una funcin de devolucin de llamada. A diferencia de otros
ejemplos, este no est pensado como aplicacin de consola. Si lo ejecuta dentro de una aplicacin de
consola el bloque de cdigo Main se completar antes de que la consulta termine y la aplicacin terminar
antes de llamar a la funcin de devolucin de llamada. Para comprender mejor este escenario cree una
aplicacin Windows, aada un botn al formulario, copie el cdigo principal en el evento de pulsacin del
botn y coloque un punto de interrupcin en la llamada a BeginExecuteReader. Copie la funcin
DevolucinLlamada por separado y coloque un punto de interrupcin en la llamada a EndExecuteReader.
Ejecute el cdigo. Ver que se llama a la funcin de devolucin de llamada aproximadamente 10 segundos
despus de la llamada a BeginExecuteReader.
private void btEjecutar_Click(object sender, EventArgs e) {
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True;Asynchronous Processing=True;";
SqlConnection conexin = new SqlConnection(cadcon);
string consulta =
"WAITFOR DELAY '00:00:10';SELECT TOP 10 CustomerID FROM Customers";
SqlCommand comando = new SqlCommand(consulta, conexin);
AsyncCallback devolucin = new AsyncCallback(DevolucinLlamada);
conexin.Open();
comando.BeginExecuteReader(devolucin, comando, CommandBehavior.CloseConnection);
}

37

ADO.NET 2.0

private void DevolucinLlamada(IAsyncResult resultado) {


SqlCommand comando = (SqlCommand)resultado.AsyncState;
using(SqlDataReader lector = comando.EndExecuteReader(resultado)) {
while(lector.Read())
lbResultados.Items.Add(lector[0]);
lector.Close();
}
}

Las funciones de devolucin de llamada parecen inicialmente muy atractivas; pero en aplicaciones
Windows pueden provocar algunos problemas. La funcin de devolucin de llamada se ejecuta en una
hebra en segundo plano, lo que significa que tiene severas limitaciones al interactuar con el interface de
usuario. Por ejemplo, si carga los resultados de la consulta en un DataTable e intenta enlazar un
DataGridView con el DataTable recibir una InvalidOperationException indicando que no se puede acceder
desde una hebra a otra diferente de aquella sobre la que se cre.
Puede crear y llamar un delegado desde su funcin de devolucin de llamada para interactuar con los
controles. De hecho, hay un tema en el SDK del marco de trabajo .NET que muestra este mtodo y discute
los delegados en profundidad.
Un medio ms sencillo es usar una nueva caracterstica de la versin 2.0 de formularios Windows, la
clase BackgroundWorker. Esta clase se tratar cuando hablemos de aplicaciones Windows ms a fondo.

3.2 Trabajar con SqlDataReader


La clase SqlDataReader es similar a otras clases del marco de trabajo .NET, como XmlReader,
TextReader y StreamReader. Cada una de estas clases proporciona un objeto eficiente y ligero que permite
examinar, en solo lectura, los datos que expone el objeto. La clase TextReader, por ejemplo, tiene mtodos
que permiten leer los contenidos de un archivo de texto lnea a lnea. De forma similar, SqlDataReader
expone propiedades y mtodos que permiten recorrer los resultados de la consulta. SqlDataReader sacrifica
la funcionalidad a favor del rendimiento. Una vez que se lee una fila del conjunto de resultados y se pasa a
la siguiente, la anterior deja de estar disponible.

3.2.1

Examinar los resultados de su consulta

El siguiente fragmento muestra cmo examinar los resultados de una consulta simple usando
SqlDataReader.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
while(lector.Read())
Console.WriteLine("{0}: {1}", lector["CustomerID"], lector["CompanyName"]);
lector.Close();
conexin.Close();

Observe que el cdigo llama al mtodo Read antes de leer la primera fila del conjunto de resultados,
porque la primera fila no est disponible inmediatamente tras la llamada a ExecuteReader. El
SqlDataReader que devuelve el objeto SqlCommand no tiene la primera fila de datos disponible hasta que
no se llama al mtodo Read.
La primera vez que se llama al mtodo Read el SqlDataReader se mueve a la primera fila del conjunto
de resultados. Llamadas posteriores pasan a la fila siguiente. El mtodo devuelve un valor booleano que
indica si el SqlDataReader ha ledo con xito la fila. As, si Read devuelve True el SqlDataReader tiene
disponible la siguiente fila de resultados. Cuando Read devuelve False se ha llegado al final de los
resultados.
Cuando hay una fila de datos disponible puede simplemente llamar al indexador de SqlDataReader con
el nombre de la columna. El valor de la columna se obtiene en el tipo de datos genrico Object. Aunque esta
no es la forma ms eficiente de recuperar datos, es un punto de inicio sencillo.

3.2.2

Cerrar el SqlDataReader

En el Captulo 2 se mencion brevemente la importancia de cerrar los objetos SqlConnection; igual de


importante es cerrar los objetos SqlDataReader. Imagine que ha creado una aplicacin Windows en la que
la aplicacin abre una nica conexin y la usa durante todo su tiempo de vida.
En el evento de pulsacin de un botn ejecuta una consulta y recupera los resultados usando
SqlDataReader. Pero olvida cerrar el SqlDataReader. Si el usuario pulsa el botn ms de una vez se
38

Escuela de Informtica del Ejrcito

encontrar con una InvalidOperationException, cuyo texto le indicar que ya hay un DataReader abierto
asociado con este comando y que se debe cerrar primero. Como sugiere el mensaje, el SqlDataReader no
se ha cerrado nunca.
SQL Server 2005 soporta mltiples conjuntos de resultados activos, pero de forma predeterminada esta
caracterstica est desactivada. De forma predeterminada SQL Server solo soporta un nico conjunto de
resultados activo en una conexin. La conexin queda bloqueada hasta que se cierra el conjunto de
resultados. En este caso, el SqlDataReader abandonado pero abierto bloquea la conexin, impidiendo su
uso para ejecutar otras consultas.
El objetivo de SqlDataReader es el rendimiento. Independientemente de la restriccin de que un
SqlDataReader abierto bloquea una conexin, debe sacar los resultados de su consulta del cable tan
rpidamente como pueda despus de lanzar la consulta. Si necesita moverse hacia delante y atrs entre los
resultados de consultas separadas tendr que usar un DataSet o almacenar los resultados de su consulta
en algn tipo de objeto de negocio.
Afortunadamente, SqlDataReader implementa IDisposable, por lo que se puede hacer limpieza fcil y
automticamente con un bloque using. Al llamar a Dispose sobre un SqlDataReader se llama implcitamente
al mtodo Close del objeto.

3.2.3

Examinar el esquema de los resultados

Es posible que en el momento de escribir el cdigo no conozca el esquema del conjunto de resultados.
Por ejemplo, puede escribir una herramienta en la que el usuario defina la consulta, o puede estar usando
consultas SELECT * FROM y el administrador de la base de datos puede aadir columnas a la tabla.
Independientemente del motivo, si no conoce con anticipacin el esquema del conjunto de resultados
puede usar mtodos de SqlDataReader para determinarlo.

Nmero de campos disponible


La propiedad FieldCount contiene el nmero de campos devueltos por la consulta. Recuerde que
algunas consultas no devuelven ningn campo.

Nmero de filas devuelto


No existe una propiedad que indique el nmero de filas disponible en un SqlDataReader. Este objeto
representa un flujo de datos, y no tiene forma de saber por adelantado cuntas filas devuelve la consulta.

Nombre de campo
Si necesita determinar el nombre de un campo llame al mtodo GetName de SqlDataReader. Este
mtodo acepta un entero que especifica el nmero de orden del campo y devuelve el nombre como cadena.

Tipo de datos .NET de un campo


Para determinar el tipo de datos .NET usado para almacenar el contenido de un campo concreto llame al
mtodo GetFieldType de SqlDataReader. Como GetName, este mtodo acepta un enteros que especifica el
nmero de orden del campo. GetFieldType devuelve el tipo de datos en un objeto Type.

Tipo de datos en la base de datos de un campo


Si necesita determinar el tipo de datos de un campo en la base de datos llame al mtodo
GetDataTypeName de SqlDataReader. El mtodo acepta el nmero de orden como entero y devuelve una
cadena con el nombre del tipo de datos del campo en la base de datos.
El siguiente fragmento usa una sobrecarga de ExecuteReader que devuelve esquema pero no filas y
utiliza los mtodos GetName, GetFieldType y GetDataTypeName para mostrar informacin sobre el
esquema del conjunto de resultados.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT OrderID, CustomerID, OrderDate, Freight FROM Orders";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
conexin.Open();
SqlDataReader lector = comando.ExecuteReader(CommandBehavior.SchemaOnly);

39

ADO.NET 2.0

for(int campo = 0; campo < lector.FieldCount; campo++) {


Console.WriteLine("Campo nm. {0}", campo);
Console.WriteLine("
Nombre:
{0}", lector.GetName(campo));
Console.WriteLine("
Tipo de datos .NET:
{0}",
lector.GetFieldType(campo).Name);
Console.WriteLine("
Tipo en base de datos: {0}", lector.GetDataTypeName(campo));
Console.WriteLine();
}
lector.Close();
conexin.Close();

Nmero de orden de un campo


Si conoce el nombre del campo al que quiere acceder pero no su nmero de orden use el mtodo
GetOrdinal de SqlDataReader. Este mtodo acepta una cadena para el nombre del campo y devuelve el
nmero de orden de la columna.

Informacin adicional
Existe mucha ms informacin que puede ser necesaria sobre un campo, por ejemplo, si es de solo
lectura, su longitud, si puede contener nulos, si forma parte de la clave, etc.
SqlDataReader no tiene mtodos para tratar todas estas cuestiones. Sin embargo, permite recuperar
informacin adicional de esquema en un DataTable. Esta clase se discutir ms adelante; por ahora basta
saber que contiene filas y columnas de datos.
La forma ms sencilla de ver la informacin que devuelve GetSchemaTable es enlazar la DataTable
resultante con un control DataGridView de Windows.
Cuando examine la informacin que devuelve GetSchemaTable puede observar que algunas partes de
la informacin de esquema se omiten, como si es clave o la tabla de origen. La siguiente discusin de
CommandBehavior explica cmo controlar la tabla de esquema que se devuelve con el resultado.

Uso de CommandBehavior
El mtodo ExecuteReader de SqlCommand est sobrecargado. Puede pasar un valor de la enumeracin
CommandBehavior del espacio de nombres System.Data para controlar qu datos se devuelven. Si usa la
versin de ExecuteReader sin parmetros el SqlDataReader devuelto contendr los resultados de la
consulta y la informacin de esquema bsica nombres de campo y tipos de datos del conjunto de
resultados.
SchemaOnly

Si quiere recuperar informacin de esquema del conjunto de resultados, pero no filas, use esta opcin.
Toda la informacin de esquema estar disponible por medio del SqlDataReader, pero ste no
contendr filas de datos.

KeyInfo

De forma predeterminada SQL Server devuelve informacin de esquema bsica, con los nombres y
tipos de datos de las columnas del conjunto de resultados. SQL Server no indica qu columna, o
columnas, forman la clave del conjunto de resultados o la tabla o tablas base de la que se han
recuperado los datos. Si necesita esta informacin, utilice esta opcin.
Si solicita esta informacin adicional sobre claves SQL Server devolver el nombre de tabla y columna
de cada columna del conjunto de resultados, e indicar qu columna o columnas se pueden usar
como clave del conjunto de resultados. SQL Server no devolver toda la informacin de esquema. Por
ejemplo, si consulta una tabla que contiene una clave principal y tres claves nicas y el conjunto de
resultados contiene todas estas columnas, slo la columna de clave principal devolver True para
IsKey.

3.2.4

Obtener datos ms deprisa con buscas basadas en ordinal

Vamos a ver las formas en que se puede mejorar el rendimiento del fragmento de cdigo anterior.
En los ejemplos anteriores proporcionamos el nombre de cada columna. Para devolver el contenido
almacenado en la columna el SqlDataReader debe localizar la columna en su estructura interna en base a
la columna proporcionada. Recuerde que en nuestro ejemplo pedimos al SqlDataReader que realizara esta
bsqueda basada en cadena en cada fila del conjunto de resultados. Esto significa que estamos pagando
esta penalizacin en rendimiento en cada llamada para recuperar un valor del SqlDataReader. Hay formas
ms eficientes de hacerlo.
Podemos mejorar el rendimiento pasando el ndice, u ordinal, de la columna en lugar de su nombre,
aunque se pierde algo de flexibilidad. Esta tcnica se aplica a casi todos los objetos que exponen
colecciones.
En la mayora de aflicciones podemos codificar los valores de ndice de cada columna sin encontrar
problemas. El orden de las columnas en el conjunto de resultados no variar salvo que se cambie la cadena
40

Escuela de Informtica del Ejrcito

de consulta o se modifique la estructura del objeto de base de datos y se estn recuperando todas las
columnas.
Sin embargo, puede encontrar situaciones en las que sepa el nombre de la columna pero no su ndice.
Como indiqu anteriormente, el mtodo GetOrdinal del objeto SqlDataReader acepta una cadena que
representa el nombre de la columna y devuelve un entero que representa su ordinal. Este mtodo puede
ayudar a mejorar el rendimiento sin perder la flexibilidad de la bsqueda por cadenas.
El siguiente fragmento mejora el anterior. Usa el mtodo GetOrdinal para obtener los valores de ordinal
de las dos columnas que queremos examinar y despus usa estos valores para examinar el contenido de
cada fila. Esto mejora el rendimiento porque realizamos una bsqueda basada en cadena de la coleccin
una sola vez por columna. En el cdigo original se realiza esta bsqueda cada vez que leemos datos de una
columna.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
int indID = lector.GetOrdinal("CustomerID");
int indCia = lector.GetOrdinal("CompanyName");
while(lector.Read())
Console.WriteLine("{0}: {1}", lector[indID], lector[indCia]);
lector.Close();
conexin.Close();

Esto est bien, pero an puede ser ms rpido.

3.2.5

Lectores fuertemente tipados

SqlDataReader tambin expone una serie de mtodos que devuelven datos con diferentes tipos de datos
del marco de trabajo .NET. Actualmente nuestro cdigo usa implcitamente la propiedad Item, que devuelve
los contenidos de las columnas especificadas con el tipo de datos genrico Object. Este proceso se
denomina encajado; la extraccin del valor del tipo de datos Object se denomina desencajado. El encajado
y desencajado son operaciones costosas, y adems son innecesarias.
La clase SqlDataReader expone mtodos Get para diferentes tipos del marco de trabajo .NET. Estos
mtodos se suelen denominar lectores fuertemente tipados. Puede evitar la penalizacin de rendimiento
provocada por el encajado y desencajado llamando al mtodo Get adecuado. Por ejemplo, las columnas
CustomerID y CompanyName contienen datos de cadena, por lo que podemos usar el mtodo GetString de
SqlDataReader. Aunque la salida parece la misma, este cdigo es ms rpido que el anterior.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
int indID = lector.GetOrdinal("CustomerID");
int indCia = lector.GetOrdinal("CompanyName");
while(lector.Read())
Console.WriteLine("{0}: {1}", lector.GetString(indID), lector.GetString(indCia));
lector.Close();
conexin.Close();

Siempre debe usar el lector fuertemente tipado correspondiente a los datos devueltos por la columna del
conjunto de resultados. Como se indic anteriormente, estos lectores ofrecen mejor rendimiento, pero usar
el que no corresponde produce una InvalidCastException. En desarrollo, si no est seguro de que lector
usar intente usar el mtodo GetFieldType pasando el ordinal de la columna.

3.2.6

Manejo de valores nulos

Supongamos que est recuperando informacin sobre una serie de pedidos OrderID, CustomerID,
OrderDate y ShippedDate usando SqlDataReader y los mtodos de lectura fuertemente tipados. Su
cdigo ser como este:
41

ADO.NET 2.0

...
List<Pedido> pedidos = new List<Pedido>();
Pedido pedido;
conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
while(lector.Read()) {
pedido = new Pedido();
pedido.OrderID = lector.GetInt32(0);
pedido.CustomerID = lector.GetString(1);
pedido.OrderDate = lector.GetDateTime(2);
pedido.ShippedDate = lector.GetDateTime(3);
pedidos.Add(pedido);
}
lector.Close();
conexin.Close();

Cuando ejecute este cdigo se encontrar que la llamada para recuperar el contenido de ShippedDate
se obtiene una excepcin que dice Los datos tienen valor Null. No se puede llamar a este mtodo o
propiedad con valores Null. Qu pasa?.
Primero hablemos sobre el manejo de valores nulos en cdigo .NET. Digamos que quiere trabajar con el
contenido de una cadena, por ejemplo para averiguar su longitud, pero es posible que la cadena no se haya
inicializado. Debera usar cdigo como este para asegurarse de que la cadena est inicializada antes de
trabajar con su contenido.
string cadena;
...
if(cadena == null)
Console.WriteLine(cadena no inicializada);
else
longCadena = cadena.Length;

Las mismas reglas se aplican para cualquier dato por referencia en .NET. Antes de trabajar con la
variable asegrese de que se ha inicializado. Las bases de datos contemplan el mismo concepto. Si no
proporciona un valor para una columna de una nueva fila y la base de datos no se asigna uno
automticamente por medio de un predeterminado o algn otro mecanismo, el valor de la columna es nulo.
Dependiendo de la definicin de la tabla en la base de datos la columna puede o no aceptar nulos. Si est
recuperando datos de una columna que puede contener un valor nulo, compruebe el valor antes de acceder
a l.

DbNull.Value
Si el valor de base de datos para una columna es nulo y llama a uno de los lectores fuertemente tipados
de SqlDataReader obtendr una SqlNullValueException. De todas formas, puede seguir usando el
indexador predeterminado pasando el nombre de columna o el valor ordinal.
Si el valor de la columna en la base de datos es nulo, al comprobar el contenido de la variable objeto
ver que es una instancia del tipo DBNull. En concreto, es DbNull.Value. Puede seguir este proceso para
comprobar si el valor es nulo, pero hay una forma ms sencilla.

Mtodo IsDBNull
SqlDataReader expone un mtodo IsDBNull. Use este mtodo para determinar si una columna contiene
un valor nulo antes de obtener el contenido. El mtodo solo acepta un entero como ordinal de la columna y
devuelve true si el valor de la columna es nulo y false en caso contrario.
En el ejemplo inicial, la propiedad ShippedDate de la clase Pedido usa el tipo de datos DateTime. Como
la columna ShippedDate puede ser nula, para indicar un pedido que an no se ha enviado, una buena
opcin es usar el mtodo IsDBNull junto con la clase Nullable, que aparece en la versin 2.0 del marco de
trabajo .NET.
ADO.NET 2.0 no soporta directamente tipos Nullable. El SqlDataReader no tiene un mtodo que indique
que se quiere recuperar un tipo Nullable. Puede usar el mtodo IsDBNull junto con una clase Nullable.
Supongamos que crea una clase Pedido con propiedades OrderID, CustomerID, OrderDate y ShippedDate
de los tipos Int32, String, DateTime y Nullable DateTime. La propiedad ShippedDate es de tipo Nullable
DateTime para representar pedidos no enviados. Puede usar un SqlDataReader para crear instancias de la
clase Pedido y manejar los posibles valores nulos de la columna ShippedDate con un cdigo como este:
...
List<Pedido> pedidos = new List<Pedido>();
Pedido pedido;

42

Escuela de Informtica del Ejrcito

conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
while(lector.Read()) {
pedido = new Pedido();
pedido.OrderID = lector.GetInt32(0);
pedido.CustomerID = lector.GetString(1);
pedido.OrderDate = lector.GetDateTime(2);
if(lector.IsDBNull(3))
pedido.ShippedDate = null;
else
pedido.ShippedDate = lector.GetDateTime(3);
pedidos.Add(pedido);
}
lector.Close();
conexin.Close();

3.2.7

Tipos SQL

Cuando se recuperan los resultados de una consulta el SqlDataReader examina el tipo de datos de la
columna SQL Server y convierte los datos al tipo .NET apropiado. Sin embargo, hay algunas diferencias
entre estos tipos. Por ejemplo, el tipo Decimal de SQL Server se devuelve como un Decimal de .NET.
Aunque el nombre es el mismo, el Decimal de SQL Server puede contener ms datos que el Decimal de
.NET. Si llama al lector sin tipo o al mtodo GetDecimal para devolver estos datos como Decimal de .NET
recibir una OverflowException.
El proveedor de datos .NET para cliente SQL trata estas incompatibilidades entre tipos ofreciendo una
serie de clases especializadas diseadas para encajar con los tipos de datos SQL Server correspondientes.
Estas clases estn disponibles en el espacio de nombres System.Data.SqlTypes. Por ejemplo, un
SqlDecimal est diseado para contener todos los posibles valores del tipo Decimal de SQL Server. Esta
clase expone una propiedad Value que devuelve un Decimal de .NET. Hay otras formas de examinar el
contenido de SqlDecimal que son tiles cuando no se puede convertir los datos a Decimal de .NET. Puede
sencillamente convertir los datos en cadena usando el mtodo ToString. SqlDecimal tambin expone un
mtodo ToDouble que devuelve su contenido como un Double de .NET, que puede contener valores ms
all del rango de Decimal de .NET. Y si quiere ver los 1,s y 0,s tambin puede examinar el contenido de un
SqlDecimal como array de enteros usando la propiedad Data o como array de byte usando la propiedad
BinData.
Las clases SqlTypes tienen una variedad de constructores. Por ejemplo, puede crear un SqlDecimal
basado en un Decimal, double, int o long de .NET. Tambin puede usar el mtodo esttico Parse para crear
un SqlDecimal en base a una cadena.
SqlDataReader ofrece mtodos Get adicionales que pueden devolver SqlTypes. Si quiere recuperar los
contenidos de un Decimal de SQL Server como SqlDecimal simplemente use el mtodo GetSqlDecimal.
Tenga en cuenta que los SqlTypes son inmutables. Una vez creado uno, no puede cambiar su valor
interno. La propiedad Value es de solo lectura, pero puede crear nuevos SqlTypes basados en valores de
SqlTypes existentes. Por ejemplo, el siguiente cdigo crea un SqlInt32 como la suma de dos existentes:
SqlInt32 x = new SqlInt32(3);
SqlInt32 y = new SqlInt32(5);
SqlInt32 z = SqlInt32.Add(x, y);

Tenga en cuenta que todos los fragmentos que usan SqlTypes necesitan una referencia al espacio de
nombres System.Data.SqlTypes.
La otra ventaja de usar SqlTypes es que estn diseados para manejar valores nulos. No tiene que usar
el mtodo IsDBNull para comprobar que el valor es nulo antes de llamar a un mtodo como GetSqlInt32.
Cada clase del espacio SqlTypes tiene una propiedad IsNull que puede usar para comprobar si contiene un
valor nulo.

3.2.8

Manejar mltiples conjuntos de resultados de una consulta

Algunas bases de datos, como SQL Server, permiten ejecutar un lote de consultas que devuelven
mltiples resultados. Supongamos que quiere ejecutar la siguiente consulta contra la base de datos
Northwind.
SELECT CustomerID, CompanyName FROM Customers;
SELECT OrderID, OrderDate FROM Orders;
SELECT OrderID, ProductID FROM [Order Details]

En los ejemplos anteriores de SqlDataReader recorrimos los resultados de la consulta hasta que el
mtodo Read devolva False. Este cdigo recorre solamente el primer conjunto de resultados devuelto por
la consulta. Si usamos este mtodo con la consulta anterior, solo recorreremos datos de la tabla Customers.
43

ADO.NET 2.0

SqlDataReader expone un mtodo NextResult que permite pasar a los resultados de la siguiente
consulta. Este mtodo se parece a Read en que devuelve un valor booleano que indica si hay ms
conjuntos de resultados. Sin embargo, a diferencia del mtodo Read no es necesario hacer una llamada
inicial.
Cuando el mtodo Read devuelve False puede comprobar si hay resultados adicionales para obtener
llamando al mtodo NextResult. Cuando este mtodo devuelva False ya no hay ms conjuntos de
resultados. El siguiente fragmento muestra cmo usar el mtodo NextResult para obtener los resultados de
una consulta por lotes.
...
string consulta = "SELECT CustomerID, CompanyName FROM Customers;" +
"SELECT OrderID, OrderDate FROM Orders;" +
"SELECT OrderID, ProductID FROM [Order Details]";
SqlCommand comando = new SqlCommand(consulta, conexin);
conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
do {
while(lector.Read())
Console.WriteLine("{0} - {1}", lector[0], lector[1]);
Console.WriteLine("******************");
} while(lector.NextResult());

3.2.9

SQL Server 2005 y mltiples conjuntos de resultados activos

Antes de SQL Server 2005 las conexiones que tenan abierto un cursor, un DataReader en trminos de
ADO.NET, estaban bloqueadas. No se pueden ejecutar otras consultas sobre esta conexin hasta que no
se cierre el cursor abierto. En otras palabras, slo puede tener una peticin activa en cada sesin.
SQL Server 2005 introduce el soporte para mltiples conjuntos de resultados activos (a menudo
denominado MARS) o mltiples peticiones sobre una conexin. Si activa esta caracterstica en sus
conexiones con SQL Server 2005 su conexin ya no se bloquea si hay abierto un SqlDataReader asociado
con el SqlConnection. Puede ejecutar otras consultas de cualquier tipo sobre el SqlConnection aunque el
SqlDataReader permanezca abierto.
Primero establezcamos un escenario bsico. Ha lanzado una consulta de informacin de pedidos, por
ejemplo, los pedidos de un cierto cliente. Al examinar los resultados de la consulta necesita recuperar
informacin relacionada, por ejemplo los detalles de un pedido.

Vida antes de MARS


Antes de la aparicin de MARS existan varias formas de solucionar esta situacin.
Si est creando sus propias consultas para recuperar esta informacin podra consultar la informacin de
detalles de pedidos y almacenarla. Despus, al recuperar la informacin de pedidos puede consultar la
informacin almacenada para obtener los detalles del pedido.
Este mtodo no es vlido si no tiene forma de construir una consulta para recuperar los datos hijos por
adelantado, por ejemplo, si est accediendo a la base de datos por medio de procedimientos almacenados
y solo puede recuperar los detalles de un pedido cada vez.
Si es absolutamente necesario consultar la base de datos y la conexin tiene un cursor abierto, siempre
puede abrir otra conexin y trabajar sobre ella. Aunque abrir conexiones adicionales provoca alguna
sobrecarga, este mtodo permite paralelismo el servidor puede manejar las peticiones
independientemente.

Activar MARS
La funcionalidad MARS est desactivada de forma predeterminada. Se activa para una conexin
aadiendo MultipleActiveResultSets=True a la cadena de conexin. Como otras opciones, esta est
disponible como propiedad de SqlConnectionStringBuilder.
Cuando se conecta con un servidor SQL Server 2000 el uso de esta opcin no provoca una excepcin,
pero no tiene ningn efecto, es decir, la conexin se bloquea cuando hay un SqlDataReader abierto. Es
recomendable consultar la propiedad ServerVersion del objeto SqlConnection antes de abrir el segundo
SqlDataReader.
Cuando se ejecutan varias consultas usando MARS SQL Server no las maneja en paralelo, sino que
intercala la ejecucin, cambiando de una a otra. En trminos generales SQL Server ejecuta cada consulta
atmicamente. En otras palabras, si se ejecuta un UPDATE que necesite mucho tiempo e intenta ejecutar
una segunda consulta, SQL Server terminar con la primera antes de iniciar la segunda. Si embargo, puede
ejecutar una consulta SELECT y una segunda consulta adicional; SQL Server detiene temporalmente la
44

Escuela de Informtica del Ejrcito

ejecucin de la consulta SELECT para trabajar con la otra. Solo las consultas de lectura de datos (como
SELECT, READTEXT e insercin masiva) soportan el intercalado antes de completar la consulta.
Si est trabajando con consultas SELECT sencillas MARS puede ser una caracterstica potente y til. Sin
embargo, si est trabajando con operaciones ms complejas lotes de consultas, procedimientos
almacenados o transacciones puede encontrarse con resultados inesperados.
La forma en que SQL Server intercala la ejecucin de varias consultas en una conexin MARS puede
llevar a un comportamiento no determinista. Suponga que ejecuta dos peticiones separadas, ya sean lotes o
llamadas a procedimiento almacenado, sobre una conexin con MARS habilitado. Cada consulta devuelve
un conjunto de resultados, pero tambin ejecuta consultas adicionales. No puede saber con exactitud
cundo intercalar SQL Server la ejecucin entre las dos peticiones salvo que ejecute una y gestione todos
los resultados antes de ejecutar la otra.
Los escenarios transaccionales se vuelven ms complejos cuando se usa MARS. Puede ejecutar varias
peticiones dentro de una sola transaccin. Si inicia una transaccin y ejecuta varios lotes con una duracin
larga, un lote puede modificar datos que ya han sido vistos o modificados en otro lote porque se estn
ejecutando dentro de la misma transaccin, un comportamiento que puede no ser el esperado. SQL Server
devolver un error para llamadas para crear puntos de restauracin o volver a uno de ellos, as como
llamadas para consignar la transaccin si hay ms de una peticin activa en la misma.
Puede tener procedimientos almacenados que cambien de base de datos mediante la consulta USE.
Qu pasa si tiene varias peticiones activas con consultas de este tipo?.
Para decirlo rpidamente, las cosas son ms sencillas con conexiones que no tienen MARS habilitado, y
se vuelven ms complejas cuando se activa MARS.

Debe usarse MARS?


MARS es una caracterstica muy potente y muy til. Si est procesando el resultado de una sentencia
SELECT simple, o de un procedimiento almacenado que contiene una sentencia de este tipo, usando un
SqlDataReader y es absolutamente necesarios ejecutar una segunda consulta, el uso inteligente de MARS
puede simplificar su cdigo.
Tenga en cuenta que cuando llama a ExecuteReader sobre SqlCommand, SQL Server pone los
resultados de la consulta en buffers de servidor. Estos recursos estn comprometidos hasta que se
consume el contenido del SqlDataReader. Como regla general, debe recuperar los resultados lo ms
rpidamente posible para liberar estos recursos. Hacer una pausa para recuperar los resultados de una
segunda consulta posiblemente no cause problemas, pero hacer una pausa y esperar entrada del usuario
puede comprometer innecesariamente recursos del servidor. Si necesita pedir informacin al usuario, o
realizar otras tareas que puedan impedir examinar los resultados restantes, considere almacenar los
resultados en un cach fuera de lnea, como un DataTable, antes de ejecutar esta operacin.

Uso de MARS
Ahora que hemos hecho las advertencias relativas al uso de MARS, vamos a ver un breve fragmento
que muestra su uso. El cdigo recupera informacin de pedido para un cliente por medio de un
SqlDataReader. Tras examinar la informacin de cada pedido ejecuta una consulta parametrizada para
recuperar los detalles del pedido actual y muestra los resultados intercalados en consola.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True;MultipleActiveResultSets=True;";
SqlConnection conexin = new SqlConnection(cadcon);
string consulta =
"SELECT OrderID, OrderDate FROM Orders WHERE CustomerID = @CustomerID";
SqlCommand comandoPedido = new SqlCommand(consulta, conexin);
SqlParameter parCustID =
comandoPedido.Parameters.Add("@CustomerID", SqlDbType.NChar, 5);
consulta = "SELECT ProductID, Quantity, UnitPrice FROM [Order Details]" +
"WHERE OrderID = @OrderID";
SqlCommand comandoDetalles = new SqlCommand(consulta, conexin);
SqlParameter parOrderID = comandoDetalles.Parameters.Add("@OrderID", SqlDbType.Int);
parCustID.Value = "ALFKI";
Console.WriteLine("Pedidos para {0}", parCustID.Value);
Console.WriteLine("==================");
conexin.Open();
SqlDataReader lectorPedidos = comandoPedido.ExecuteReader();
SqlDataReader lectorDetalles = null;

45

ADO.NET 2.0

while(lectorPedidos.Read()) {
Console.WriteLine("ID Pedido: {0} Fecha Pedido: {1:d}",
lectorPedidos["OrderID"], lectorPedidos["OrderDate"]);
parOrderID.Value = lectorPedidos["OrderID"];
lectorDetalles = comandoDetalles.ExecuteReader();
while(lectorDetalles.Read())
Console.WriteLine("
ID Producto: {0}, Cantidad: {1} " +
"Precio Unidad: {2:c}", lectorDetalles["ProductID"],
lectorDetalles["Quantity"], lectorDetalles["UnitPrice"]);
lectorDetalles.Close();
Console.WriteLine();
}
lectorPedidos.Close();
conexin.Close();

3.3 Consultas parametrizadas


Hay muchos escenarios en los que querr usar consultas parametrizadas, pero el ms habitual se
produce cuando la consulta necesita entrada de usuario.

3.3.1

Entrada de usuario en cadena de consulta

Supongamos que esta creando una aplicacin que permite que un usuario examine los pedidos hechos
por un cliente concreto. El usuario especificar durante la ejecucin de la aplicacin la informacin de
cliente, que puede ser el nombre de compaa u otra informacin. Posiblemente crear una consulta base,
aplicar la entrada de usuario y ejecutar la consulta.
Los valores de cadena probablemente sean los que menos le preocupen desde el punto de vista del
formato. Dar el formato adecuado a los valores de fecha es un reto, en especial si est creando una
aplicacin multicultural. El formato de fecha depende de la configuracin regional de la base de datos, del
cliente, o se debe usar un formato estndar?.
En teora puede manejar todos los aspectos de formato mediante una comprensin profunda de la
sintaxis de consulta de la base o bases de datos que quiere consultar y un uso abundante de la funcin
String.Format del marco de trabajo .NET. Esto no quiere decir que no se pueda vencer en este reto, desde
un punto de vista tcnico. Sin embargo, mi opinin es que no vale la pena intentar construir consultas de
esta forma. El proceso consume mucho tiempo y es muy fcil cometer errores en algunos escenarios, y ni
siquiera darse cuenta hasta que no se ha distribuido la aplicacin. Ms importante, las consecuencias de
una mala manipulacin de la entrada del usuario puede ser grave mucho ms grave que no lograr la
ejecucin de la consulta.

3.3.2

Construccin de consultas e inyeccin SQL

El error ms habitual es olvidar buscar comillas simples en la entrada del usuario. El ejemplo clsico es
aquel en que el usuario almacena nombres de usuario y contraseas en una tabla y pide al usuario su
nombre y contrasea cuando se lanza la aplicacin. Entonces la aplicacin consulta el nombre de usuario y
contrasea en la base de datos. Si la consulta devuelve una fila la aplicacin asume que el usuario ha
proporcionado un nombre de usuario y contrasea vlidos.
Los desarrolladores parten de una consulta base similar a esta:
SELECT COUNT(*) FROM InfoUsuario WHERE Nombre = {0} AND Contrasea = {1}

Cuando se integra la informacin introducida por el usuario la consulta toma una forma como esta:
SELECT COUNT(*) FROM InfoUsuario WHERE Nombre = UnNombre
AND Contrasea = UnaContra

Pero qu sucede si el usuario conoce esta forma de trabajo e introduce como nombre NoUsuario OR 1
= 1--. La consulta resultante ahora es:
SELECT COUNT(*) FROM InfoUsuario WHERE Nombre = NoUsuario OR 1 = 1 --
AND Contrasea = UnaContra

El doble guin tiene un significado especial en la sintaxis de consultas de SQL Server. Significa que el
resto de la lnea es un comentario, es decir, se ignora. Ahora no existe ninguna entrada en la columna
Nombre para el valor NoUsuario, pero la otra mitad de la clusula WHERE, 1 = 1, es cierto para todas las
filas. Es decir, la consulta se ejecuta y devuelve todas las filas de la tabla. La aplicacin asume que el
usuario ha proporcionado un nombre de usuario y contrasea vlidos y contina.
Cierto que este es un ejemplo extremo, pero lo importante es que intentar dar el formato adecuado a la
entrada de usuario en una cadena de consulta es una tarea complicada y un solo error puede causar
46

Escuela de Informtica del Ejrcito

importantes problemas a la aplicacin. Afortunadamente, hay una solucin simple y efectiva consultas
parametrizadas.

3.3.3

Consultas parametrizadas

Vamos a repasar el escenario inicial, en el que se quiere recuperar informacin de pedidos de un cliente
concreto. Podemos construir una consulta contra la tabla Orders y especificar que la columna CustomerID
debe igualar el valor de un parmetro, para el cual el usuario proporcionar un valor en tiempo de ejecucin:
SELECT OrderID, CustomerID, OrderDate, EmployeeID FROM Orders
WHERE CustomerID = @CustomerID

El proveedor de datos .NET para cliente SQL soporta solo parmetros con nombre y no el marcador de
parmetro genrico (?). Los proveedores ODBC y OLE DB soportan solo el marcador genrico. El cliente
Oracle soporta parmetros con nombre, pero utilizan el carcter dos puntos (:) en lugar de la arroba (@).
Para ejecutar una consulta parametrizada en el modelo de objetos ADO.NET se aade un objeto
Parameter a la coleccin Parameters del objeto Command. Cuando se usa el proveedor de datos .NET para
cliente SQL la clase Parameter que se usa es SqlParameter. La forma ms sencilla de crear un
SqlParameter es llamar al mtodo AddWithValue de la coleccin Parameters.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT OrderID, CustomerID, OrderDate, EmployeeID " +
"FROM Orders WHERE CustomerID=@CustomerID";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
//Crea el nuevo parmetro
comando.Parameters.AddWithValue("@CustomerID", "ALFKI");
conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
while(lector.Read())
Console.WriteLine("ID de pedido: {0}, Fecha: {1:d}",
lector.GetInt32(0), lector.GetDateTime(2));
lector.Close();
conexin.Close();

El mtodo AddWithValue crea un nuevo objeto SqlParameter, configura sus


ParameterName y Value y lo aade a la conexin. Sera equivalente al siguiente cdigo:

propiedades

SqlParameter parmetro = new SqlParameter();


parmetro.ParameterName = @CustomerID;
parmetro.Value = ALFKI;
comando.Parameters.Add(parmetro);

Tipos de datos de parmetros


Puede usar la propiedad SqlDbType de SqlParameter para controlar el tipo de datos que se usa cuando
se pasa informacin de parmetro a la base de datos. Esta propiedad acepta valores de la enumeracin
SqlDbType. Si est familiarizado con los tipos de datos disponibles en las columnas de SQL Server los
valores de esta enumeracin le sern familiares: NVarChar, Int, DateTime, Bit, Money, Text, Image, etc.
Con algunos tipos de datos es necesario algo ms que indicar simplemente el tipo. Los tipos de datos
Decimal y Numeric de SQL Server, por ejemplo, soportan diferentes valores para escala y precisin. Los
tipos de datos string y binary soportan diferentes valores de longitud. Si est trabajando con estos tipos de
datos puede configurar los valores apropiados en el SqlParameter Size para controlar el tamaos de los
datos string o binary, y Precision y Scale para la precisin y escala de los datos Decimal o Numeric.
Cuando no se utilizan estas propiedades SqlParameter deduce el tipo de datos en base al contenido de
su propiedad Value. Como ya se ha visto, no es necesario confiar en este comportamiento, ya que se puede
indicar el tipo de datos y otros valores relacionados, ya sea asignando a propiedades o utilizando diferentes
sobrecargas del constructor:
SqlParameter parmetro = new SqlParameter(@CustomerID, SqlDbType.NVarChar, 5);
parmetro.Value = ALFKI;

Si quiere usar como valor del parmetro un objeto SqlTypes utilice la propiedad SqlValue de
SqlParameter en lugar de Value.

Direccin del parmetro


En los ejemplos anteriores hemos utilizado parmetros en la consulta para pasar datos a la base de
datos. Este es un ejemplo de parmetro de entrada. Tambin puede usar parmetros para recuperar datos
de la base de datos. Por ejemplo, puede recuperar datos de una sola fila de la base de datos por medio de
47

ADO.NET 2.0

parmetros de salida en lugar de examinar una fila por medio del SqlDataReader. La recuperacin de estos
datos por medio de parmetros de salida es ms rpida porque los parmetros provocan menos sobrecarga
que los conjuntos de resultados.
La siguiente consulta busca una fila en la tabla Products en base a la columna ProductName usando un
parmetro de entrada y devuelve los valores de UnitPrice y UnitsInStock por medio de parmetros de salida.
SELECT @PrecioUnidad = UnitPrice, @UnidadesStock = UnitsInStock
FROM Products WHERE ProductName = @NombreProducto

El siguiente cdigo usar esta consulta para recuperar el precio unitario y nmero de unidades en stock
de un producto concreto por medio de parmetros de salida.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT @PrecioUnidad = UnitPrice, @UnidadesStock = UnitsInStock " +
"FROM Products WHERE ProductName = @NombreProducto";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
SqlParameter pPrecio = comando.Parameters.Add("@PrecioUnidad", SqlDbType.Money);
pPrecio.Direction = ParameterDirection.Output;
SqlParameter pStock = comando.Parameters.Add(
"@UnidadesStock", SqlDbType.NVarChar, 20);
pStock.Direction = ParameterDirection.Output;
SqlParameter pNombre = comando.Parameters.Add(
"@NombreProducto", SqlDbType.NVarChar, 40);
pNombre.Value = "Chai";
conexin.Open();
comando.ExecuteNonQuery();
Console.WriteLine("Precio por unidad: {0:c}", pPrecio.Value);
Console.WriteLine("En Stock:
{0}", pStock.Value);
conexin.Close();

Si ninguna fila de la tabla Products cumple los criterios de la clusula WHERE la consulta se ejecuta con
xito, pero la propiedad Value de los parmetros de salida tiene el valor DBNull.Value. Por tanto, si quiere
determinar si los criterios corresponden con alguna fila de la tabla Products puede incluir una comprobacin
tras la llamada a ExecuteNonQuery:
...
comando.ExecuteNonQuery();
if(pPrecio.Value == DBNull.Value)
Console.WriteLine(No se encuentra un producto con el nombre {0}, pNombre.Value);
else{
Console.WriteLine("Precio por unidad: {0:c}", pPrecio.Value);
Console.WriteLine("En Stock:
{0}", pStock.Value);
}
...

3.3.4

Procedimientos almacenados

Vamos a ver cmo se puede llamar a un procedimiento almacenado usando herramientas de SQL
Server Analizador de consultas o Management Studio, segn la versin que est utilizando. La forma ms
sencilla es usar el comando EXEC de T-SQL seguido por el nombre del procedimiento:
EXEC Procedimiento

Si quiere pasar valores a parmetros del procedimiento puede pasarlos en el mismo orden que estn
declarados en el procedimiento, o puede usar sus nombres:
EXEC Procedimiento @Param2 = Valor2, @Param1 = Valor1

Si est interesado en recuperar valores de retorno o parmetros de salida la cosa es ms compleja.


Necesita declarar las variables para almacenar los valores de estos parmetros, llamar al procedimiento con
la sintaxis apropiada, usando la palabra clave OUT para parmetros de entrada/salida o de salida, y
despus consultar los valores de las variables deseadas:
DECLARE @valRetorno int, @valEntrada varchar(32), @valSalida int
SET @valEntrada = Valor de entrada
EXEC @valRetorno = Procedimiento @valEntrada, valSalida OUT
SELECT @valRetorno, @valSalida

Con objetos SqlCommand la llamada a procedimientos almacenados es simple. Asigne a la propiedad


CommandText; asigne a la propiedad CommandType el valor CommandType.StoredProcedure; aada a la
48

Escuela de Informtica del Ejrcito

coleccin Parameters los objetos SqlParameter necesarios, con el valor adecuado en la propiedad
ParameterDirection. A continuacin simplemente utilice el mtodo adecuado de SqlCommand
ExecuteReader si quiere examinar las filas devueltas, ExecuteScalar si quiere recuperar un nico valor o
ExecuteNonQuery si no est interesado en el valor devuelto.

Rellenar la propiedad Parameters


Puede pedir ayuda cuando cargue la propiedad Parameters para llamar a un procedimiento almacenado.
La clase SqlCommandBuilder, que se ver en profundidad ms adelante, expone un mtodo esttico
DeriveParameters. Puede llamar a este mtodo pasando un objeto SqlCommand cuya propiedad
CommandType tenga el valor CommandType.StoredProcedure, y el SqlCommandBuilder consultar a la
base de datos la informacin sobre los parmetros del procedimiento y cargar la propiedad Parameters en
base a esta informacin.
El mtodo DeriveParameter es extremadamente til cuando se est escribiendo cdigo pero se
desconocen los tipos de datos y tamaos de los parmetros. Sin embargo, se debe evitar su uso en tiempo
de ejecucin siempre que sea posible. En la mayora de los casos el procedimiento almacenado ya existe
en el momento de escribir la aplicacin, y se tiene cdigo para acceder a la coleccin de parmetros
proporcionando valores para parmetros de entrada y comprobando los valores de los parmetros de
salida. La consulta de informacin de esquema supone una penalizacin de rendimiento. No es necesario
pagar esta penalizacin cada vez que se quiere cargar la coleccin de parmetros. Aunque cargar la
coleccin de parmetros directamente, usando Parameters.Add, requiere mucho ms cdigo que llamar a
DeriveParameter, el rendimiento es muy superior.

3.4 Referencia
3.4.1

Propiedades de SqlCommand

La tabla 4.1 muestra las propiedades de la clase SqlCommand de uso ms probable.


Tabla 4. 1: Propiedades de uso habitual de la clase SqlCommand

Propiedad
CommandText
CommandTimeout

Tipo
String
Int32

CommandType
Connection

CommandType
SqlConnection

Notification
NotificationAutoEnlist

SqlNotificationRequest
Boolean

Parameters
Transaction
UpdatedRowSource

SqlParameterCollection
SqlTransaction
UpdatedRowSource

Descripcin
Texto de la consulta que quiere ejecutar
Tiempo en segundos que el adaptador espera que se ejecute la
consulta antes de fallar. Predeterminado 30 segundos.
Tipo de consulta que se quiere ejecutar. Predeterminado: Text
Conexin con el almacn de datos que el SqlCommand usar para
ejecutar la consulta.
Objeto SqlNotificationRequest enlazado con el SqlCommand.
Determina si la consulta recibir automticamente notificaciones SQL
desde el objeto SqlDependency. Predeterminado True.
Coleccin de parmetros para la consulta
Especifica la transaccin que usa la consulta.
Controla cmo afectan los resultados de la consulta al DataRow
actual si se usa el Command en la llamada a Update de un
SqlDataAdapter. Predeterminado: Both. Vea el Captulo 10.

CommandText
La propiedad CommandText contiene el texto de la consulta que el SqlCommand ejecutar cuando se
llame a uno de los mtodos disponibles para ejecucin.

CommandTimeout
La propiedad CommandTimeout determina el tiempo en segundos que esperar el Command para
obtener la primera fila de resultados antes de considerar que se ha superado el tiempo lmite. De forma
predeterminada el valor de esta propiedad es 30. Si no se obtiene el primer resultado de la consulta dentro
de este tipo, se lanza una excepcin. Tenga en mente que una vez que la consulta empieza a devolver
resultados, ya no hay tiempo lmite.

CommandType
De forma predeterminada la propiedad CommandType tiene el valor Text, lo que hace que el
SqlCommand ejecute la consulta especificada en la propiedad CommandText. Si intenta asignar a esta
propiedad el valor TableDirect en un SqlCommand recibir una ArgumentOutOfRangeException;
TableDirect era un concepto introducido por OLE DB para simplificar la recuperacin de todas las filas y
columnas de una tabla especificando solamente el nombre.
Asignar StoredProcedure como CommandType permite ejecutar procedimientos almacenados. Si quiere
llamar a un procedimiento almacenado dejando CommandType con su valor Text tendr que usar la sintaxis
T-SQL, lo que puede resultar muy complejo si va a utilizar parmetros de salida o valor de retorno.
Cuando se ejecuta un SqlCommand con el valor StoredProcedure en CommandType el comando asume
que su propiedad CommandText contiene el nombre de un procedimiento almacenado. Si este nombre
49

ADO.NET 2.0

contiene espacios o es una palabra reservada tendr que encerrarlo entre corchetes. Si el nombre contiene
un carcter de corchete de cierre tendr que sustituirlo por un doble corchete de cierre; slo se deben
duplicar los corchetes de cierre.

Connection
La propiedad Connection contiene el objeto SqlConnection que se usar para ejecutar la consulta

Notification
La propiedad Notification contiene el objeto SqlNotificationRequest vinculado con el SqlCommand. Esta
propiedad la usan las notificaciones de consulta de SQL Server 2005.

NotificationAutoEnlist
La propiedad NotificationAutoEnlist tambin est relacionada con las notificaciones de consulta de SQL
Server 2005. Esta propiedad controla si la consulta recibir automticamente notificaciones de consulta
desde un objeto SqlDependency.

Parameters
La propiedad Parameters devuelve una SqlParameterCollection, que contiene una coleccin de objetos
SqlParameter. Examinaremos las propiedades y mtodos de la clase SqlParameter ms adelante en este
captulo.

Transaction
Esta propiedad se utiliza para ejecutar el SqlCommand dentro de una transaccin. Si ha abierto una
transaccin sobre su SqlConnection e intenta ejecutar el SqlCommand si asociarlo con la SqlTransaction
usando esta propiedad, el mtodo de ejecucin lanzar una excepcin.

UpdatedRowSource
Esta propiedad est diseada como ayuda para volver a obtener datos para la fila que est actualizando
usando objetos SqlDataAdapter y SqlCommand que contienen lgica de actualizacin. La tabla 4.2 muestra
los valores que acepta esta propiedad. Trataremos el uso de esta propiedad en el Captulo 10.
Tabla 4. 2: Miembros de la enumeracin UpdatedRowSource

Constante
Both

Valor
3

FirstReturnedRecord
None
OutputParameters

2
0
1

3.4.2

Descripcin
El Command obtendr nuevos datos para la fila por medio tanto de la primera fila
devuelta como de los parmetros de salida.
El Command obtiene los datos para la fila por medio del primer registro devuelto
El Command no obtiene nuevos datos para la fila durante la ejecucin
El Command obtendr nuevos datos para la fila por medio de parmetros de salida

Mtodos de SqlCommand

La tabla 4.3 contiene los mtodos del objeto SqlCommand.


Tabla 4. 3: Mtodos de uso habitual de la clase SqlCommand

Mtodo
BeginExecuteNonQuery
BeginExecuteReader
BeginExecuteXmlReader
Cancel
Clone
CreateParameter
EndExecuteNonQuery
EndExecuteReader
EndExecuteXmlReader
ExecuteNonQuery
ExecuteReader
ExecuteScalar
Prepare
ResetCommandTimeout

Descripcin
Inician la ejecucin asncrona de una consulta
Cancela la ejecucin de la consulta
Devuelve una copia del SqlCommand
Crea un nuevo parmetro para la consulta
Finaliza la ejecucin asncrona de la consulta
Ejecuta la consulta, para consultas que no devuelven filas
Ejecuta la consulta y devuelve los resultados en un SqlDataReader
Ejecuta la consulta y devuelve la primera columna de la primera fila
Crea una versin preparada de la consulta en el origen de datos
Restablece la propiedad CommandTimeout a su valor predeterminado de 30 segundos.

BeginExecuteReader, BeginExecuteNonQuery, BeginExecuteXmlReader


Los mtodos Begin se pueden usar para comenzar la ejecucin asncrona de una consulta. Se
corresponden con los mtodos ExecuteReader, ExecuteNonQuery y ExecuteXmlReader, pero se ejecutan
asncronamente.

Cancel
Este mtodo se puede usar para cancelar la ejecucin de una consulta. Si se llama sobre un
SqlCommand que no est ejecutando una consulta, no hace nada.
El mtodo Cancel tambin hace que el objeto SqlCommand descarte las filas no ledas de un objeto
SqlDataReader. El siguiente fragmento obtiene los resultados de una consulta simple. El cdigo muestra los
50

Escuela de Informtica del Ejrcito

resultados seguidos por el nmero de filas recuperadas. En el cdigo hay una llamada al mtodo Cancel
comentado. Elimine los caracteres de comentario y vuelva a ejecutar el cdigo para comprobar que el
mtodo Cancel descarta los resultados de la consulta.

Clone
El mtodo Clone crea y devuelve una nueva copia de un objeto SqlCommand existente.

CreateParameter
El mtodo CreateParameter crea un nuevo objeto SqlParameter.

EndExecuteReader, EndExecuteNonQuery y EndExecuteXmlReader


Los mtodos End se pueden usar para completar la ejecucin asncrona de una consulta. Cada mtodo
se corresponde con uno de los mtodos de ejecucin de SqlCommand; por ejemplo, EndExecuteNonQuery
devuelve los mismos datos que ExecuteNonQuery, pero se usa para ejecucin de consulta asncrona en
conjuncin con BeginExecuteNonQuery.

ExecuteNonQuery
Ejecuta la consulta sin devolver datos. Utilice este mtodos si quiere lanzar una consulta de accin o no
est interesado en las filas que devuelve la consulta. Al terminar la llamada al mtodo estn disponibles los
parmetros de salida y valor de retorno.
ExecuteNonQuery devuelve un entero que representa el nmero de filas modificadas por la consulta
ejecutada. Si est usando consultas por lotes obtendr el total de las filas afectadas por el conjunto de las
consultas.

ExecuteReader
Si quiere examinar las filas devueltas por una consulta use el mtodo ExecuteReader del objeto
SqlCommand para obtener estos datos en un SqlDataReader, como se ha visto en este captulo. Este
mtodo est sobrecargado y puede aceptar un valor de la enumeracin CommandBehavior, descritos en la
tabla 4.4, que modifica su comportamiento.
Tabla 4. 4: Miembros de la enumeracin CommandBehavior

Constante
CloseConnection
KeyInfo

Valor
32
4

SchemaOnly

SequentialAccess

16

SingleResult

SingleRow

Descripcin
Al cerrar el SqlDataReader se cierra la conexin
El SqlCommand obtiene informacin de clave principal respecto a las columnas del
conjunto de resultados
El SqlDataReader contendr solamente informacin de columnas sin ejecutar realmente
la consulta.
Los valores de las columnas solo estarn disponibles secuencialmente. Por ejemplo,
despus de examinar el valor de la tercera columna no se podrn examinar los de las dos
primeras.
El SqlDataReader contendr solamente los resultados de la primera consulta que
devuelva filas.
El SqlDataReader contendr solamente la primera fila de la primera consulta que
devuelva filas.

Si utiliza el valor CloseConnection al llamar a ExecuteReader, al llamar al mtodo Close del


SqlDataReader, el SqlDataReader llamar al mtodo Close del SqlConnection con el que est asociado.
Esta caracterstica puede ser extremadamente til si est creando objetos de negocio y pasando datos
de un objeto a otro. Puede encontrarse situaciones en las que quiere que un objeto de negocio devuelva un
SqlDataReader al objeto que le llama en lugar de devolver los datos en un DataTable o alguna otra
estructura. En estos casos es posible que quiera que el objeto que hace la llamada sea capaz de cerrar el
objeto SqlConnection despus de terminar la lectura de los resultados de la consulta.
Pero y si no confa en el objeto que hace la llamada?. Puede que no quiera proporcionarle una
conexin directa con la base de datos. Usando CloseConnection puede simplificar este escenario sin
comprometer la seguridad y arquitectura de la aplicacin.
Las enumeraciones KeyInfo y SchemaOnly devuelve metadatos sobre las columnas del SqlDataReader
nombres de columna, tipos de datos, etc. Esta informacin puede ser til si est creando herramientas de
generacin de cdigo. Si va a usar el mtodo GetSchemaTable del objeto SqlDataReader debera examinar
las opciones KeyInfo y SchemaOnly del mtodo ExecuteReader de SqlCommand.
Si llama a ExecuteReader y usa el valor SchemaOnly en el parmetro Options recuperar informacin de
esquema sobre las columnas, pero en realidad no ejecutar la consulta.
Usando KeyInfo en el parmetro Options fuerza a SqlCommand a obtener informacin adicional de
esquema del origen de datos para indicar si las columnas del conjunto de resultados son parte de las
columnas clave de las tablas del origen de datos.
51

ADO.NET 2.0

Si utiliza la opcin SequentialAccess el SqlDataReader solo dar acceso secuencial a las columnas.
Por ejemplo, si examina el contenido de la segunda columna ya no estar disponible el contenido de la
primera. El uso de esta opcin podra incrementar el rendimiento del SqlDataReader ligeramente, segn el
origen de datos que est usando.
Si est interesado en examinar solo la primera fila o el primer conjunto de resultados que devuelve la
consulta puede usar las opciones SingleRow y SingleResult.
La opcin SingleRow en el parmetro Options crear un SqlDataReader que contiene, como mximo,
una fila de datos; todas las filas restantes se descartan. Igualmente, al usar SingleResult se descartarn
todos los conjuntos de resultados posteriores al primero.

ResetCommandTimeout
Al llamar al mtodo ResetCommandTimeout se restablece la propiedad CommandTimeout del objeto
Command a su valor predeterminado de 30 segundos. Si se pregunta para qu necesito un mtodo para
esto? no es el nico. El mtodo ResetCommandTimeout permite que Visual Studio restaure la propiedad
CommandTimeout de un Command desde el diseador.

ExecuteScalar
El mtodo ExecuteScalar es similar a ExecuteReader, salvo que devuelve la primera columna de la
primera fila del conjunto de resultados con el tipo de datos genrico Object. Si la consulta devuelve ms de
un elemento de datos, los datos adicionales se descartan. Si la consulta devuelve un nico elemento de
datos el uso de este mtodo mejora el rendimiento.

Prepare
Este mtodo es una herencia de das pasados, antes de que SQL Server almacenara planes de
consulta. En aquellos das, solicitar a SQL Server la preparacin y almacenamiento de un plan de consulta
antes de ejecutar la consulta repetidamente poda mejorar el rendimiento. Afortunadamente, estos das
pasaron. En escenarios de prueba, el uso del mtodo Prepare no mejor el rendimiento, ni siquiera
ejecutando repetidamente una consulta parametrizada.

3.4.3

Eventos de SqlCommand

La clase SqlCommand solo expone un evento de uso habitual, StatementCompleted. Este evento
aparece en la versin 2.0 de ADO.NET como ayuda para determinar el nmero de filas afectadas por cada
una de las consultas individuales incluidas en un lote.

3.4.4

Propiedades de SqlDataReader

La tabla 4.5 recoge las propiedades de SqlDataReader.


Tabla 4. 5: Propiedades de uso habitual de la clase SqlDataReader

Propiedad
Depth
FieldCount
HasRows
IsClosed
Item
RecordsAffected

Tipo
Int32
Int32
Boolean
Boolean
Object
Int32

Descripcin
Indica la profundidad de anidamiento de la fila actual. Slo lectura.
Devuelve el nmero de campos que contiene el DataReader. Solo lectura.
Indica si la consulta ha devuelto alguna fila. Slo lectura.
Indica si el DataReader est cerrado. Slo lectura.
Devuelve el contenido de una columna de la fila actual. Slo lectura. Indexador.
Indica el nmero de registros afectados por las consultas enviadas. Slo lectura.

Depth
La propiedad Depth y el mtodo GetData estn reservados para consultas que devuelven datos
jerrquicos. Estas caractersticas no estn soportadas en la versin actual de ADO.NET.

FieldCount
Devuelve un entero que indica el nmero de campos de datos en cada fila del conjunto de resultados.

HasRows
Puede comprobar esta propiedad para determinar si la consulta ha devuelto filas. Es til cuando se debe
ejecutar cdigo diferente segn la consulta devuelva filas o no.

IsClosed
Devuelve un valor boolean para indicar si el objeto SqlDataReader est cerrado.

Item
Esta propiedad es un indexador que recibe el nombre o posicin entera de un campo y devuelve el valor
almacenado en la columna con el tipo de datos genrico Object.

52

Escuela de Informtica del Ejrcito

RecordsAffected
Puede usar la propiedad RecordsAffected para determinar el resultado que su consulta, o consultas, ha
modificado. Si quiere ejecutar una nica consulta de accin use el mtodo ExecuteNonQuery y recupere el
valor que devuelve, que representa este mismo nmero.
Si est ejecutando un lote de consultas y quiere determinar el nmero de filas afectadas utilice el evento
StatementCompleted como se indica en este captulo.

3.4.5

Mtodos de SqlDataReader

La tabla 4.6 resume los mtodos de la clase SqlDataReader de uso ms frecuente.


Tabla 4. 6: Mtodos de uso habitual de la clase SqlDataReader

Mtodo
Close
Get<tipo>
GetBytes
GetChars
GetData
GetDataTypeName
GetFieldType
GetName
GetOrdinal
GetProviderSpecificFieldType
GetProviderSpecificValue
GetProviderSpecificValues
GetSchemaTable
GetSqlValue
GetValue
GetValues

IsDBNull
NextResult
Read

Descripcin
Cierra el SqlDataReader
Devuelve los contenidos de un campo indicado por su ordinal de la fila actual con el tipo
indicado
Devuelve un array de bytes a partir de un campo de la fila actual
Devuelve un array de caracteres a partir de un campo de la fila actual
Devuelve un nuevo SqlDataReader a partir de un campo
Devuelve el nombre del tipo de datos para un campo en base a su nmero de orden
Devuelve el tipo de datos .NET de un campo en base a su nmero de orden
Devuelve el nombre de un campo en base a su nmero de orden
Devuelve el nmero de orden de un campo en base a su nombre
Similar a GetFieldType, pero devuelve el SqlType para un campo en base a su ordinal
Similar a GetValue, pero devuelve el valor en forma de SqlType
Similar a GetValues, pero devuelve un array de SqlTypes
Devuelve informacin de esquema (nombres de campo y tipos) del SqlDataReader
como DataTable.
Devuelve el valor de un campo en base a su ordinal en forma de SqlType.
Devuelve el valor de un campo en base a su ordinal como tipo de datos .NET
Acepta un array que el SqlDataReader usar para devolver el contenido de la fila
actual. Esta llamada devuelve un entero de 32 bit que indica el nmero de entradas
devueltas en el array.
Indica si un campo contiene un valor nulo
Pasa al siguiente conjunto de resultados
Mueve a la siguiente fila

Read
El mtodo Read accede a la siguiente fila de datos. Recuerde que la primera fila del conjunto de
resultados no estar disponible por medio del SqlDataReader hasta que no se llame al mtodo Read. La
primera vez que se llama al mtodo Read el SqlDataReader se coloca sobre la primera fila de datos en el
conjunto de resultados. Llamadas posteriores avanzan a la siguiente fila de datos.
El mtodo Read tambin devuelve un valor Boolean para indicar si hay ms resultados para la consulta.
El cdigo de ejemplo que examinamos anteriormente examina continuamente los resultados hasta que el
mtodo Read devuelve False.

GetValue, GetSqlValue, GetProviderSpecificValue


El mtodo GetValue es similar a la propiedad Item o al indexador; al pasarle un entero el mtodo
devuelve el contenido del campo con el tipo de objeto genrico.
El mtodo GetValue y los diferentes mtodos Get<tipo>, a diferencia de la propiedad Item, aceptan solo
enteros como ndice del campo y no hacen bsqueda basada en cadenas. El SqlDataReader est diseado
pensando en la velocidad; la referencia a un elemento en una coleccin por su ordinal es ms rpido que
hacer que la coleccin localice el elemento por su nombre.
Los mtodos GetSqlValue y GetProviderSpecificValue se comportan como el mtodo GetValue, salvo
que devuelven los datos en un tipo SqlType especializado en lugar de un tipo .NET genrico.

Get<tipo>
SqlDataReader tambin ofrece mtodos que devuelven tipos de datos especficos. Si sabe que un
campo contiene datos de cadena puede llamar al mtodo GetValue del SqlDataReader y moldear los datos
a cadena o simplemente llamar a GetString. El uso del lector fuertemente tipado proporciona mejor
rendimiento.
SqlDataReader tiene mtodos para devolver tipos disponibles en el marco de trabajo .NET que se
corresponden a los tipos de datos de la base de datos GetByte, GetChar, GetDateTime, etc. La clase
SqlDataReader tambin tiene mtodos que devuelven tipos del espacio de nombres SqlTypes
GetSqlDecimal, GetSqlString, etc.
53

ADO.NET 2.0

GetValues, GetSqlValues, GetProviderSpecificValues.


El mtodo GetValues permite almacenar los contenidos de una fila en un array. Si quiere recuperar los
contenidos de todos los campos lo ms rpidamente posible el uso de este mtodo proporciona mejor
rendimiento que comprobar los valores de cada columna por separado. Este es un ejemplo sencillo.
SqlDataReader lector = comando.ExecuteReader();
object[] datos = new object[lector.FieldCount];
while{
lector.GetValues(datos);
Console.WriteLine(datos[0].ToString());
}

Los mtodos GetSqlValues y GetProviderSpecificValues son similares al mtodo GetValues, pero


trabajan con SqlTypes en lugar de con tipos .NET bsicos.

NextResult
Si est trabajando con consultas por lotes que devuelven mltiples conjuntos de resultados use el
mtodo NextResult para pasar al siguiente conjunto de resultados. Como el mtodo Read, NextResult
devuelve un valor booleano para indicar que no ha conseguido leer resultados.
A diferencia de Read, no es necesario usar NextResult antes de empezar a leer el primer conjunto de
resultados.

Close
Cuando se est usando un objeto SqlDataReader es importante recorrer los resultados y cerrar el
SqlDataReader lo ms rpidamente posible. Salvo que el objeto SqlConnection tenga activado MARS, est
bloqueado para realizar ningn otro trabajo mientras tenga un cursor abierto. Si intenta usar un
SqlConnection que no tenca activado MARS y que tenga un SqlDataReader abierto recibir una excepcin
que indica que la operacin necesita una conexin abierta y disponible.

GetName, GetOrdinal, GetDataTypeName


El SqlDataReader tiene mtodos que puede usar para aprender ms sobre los resultados devueltos por
la consulta. Si quiere determinar el nombre de un campo concreto puede llamar al mtodo GetName. Si ya
conoce el nombre del campo al que quiere acceder pero no su nmero de orden dentro del conjunto de
resultados, puede pasar el nombre del campo al mtodo GetOrdinal para recuperar el nmero de orden. El
mtodo GetDataTypeName acepta un entero que representa la posicin ordinal de un campo y devuelve
una cadena que contiene el nombre del tipo de datos de SQL Server.

GetFieldType, GetProviderSpecificFieldType
El mtodo GetFieldType se comporta como GetDataTypeName, salvo que devuelve el tipo de datos
.NET que se usa para almacenar los contenidos del campo. Este mtodo puede ser til si no est seguro de
qu mtodo Get usar GetString, GetDateTime, etc.
El mtodo GetProviderSpecificFieldType se comporta de la misma forma, salvo que devuelve el SqlType
usado para almacenar los contenidos del campo. Este mtodo puede ser til si no est seguro de qu
mtodo usar para devolver los contenidos del campo como SqlType GetSqlString, GetSqlDateTime, etc.

GetSchemaTable
El mtodo GetSchemaTable permite obtener un DataTable que contiene objetos DataColumn
correspondientes a las columnas recuperadas por la consulta. No acepta ningn parmetro.
Los datos devueltos por GetSchemaTable pueden ser un poco difciles de comprender al principio. Este
mtodo devuelve un DataTable con una estructura predefinida. Cada DataRow corresponde a un campo
diferente en los resultados de la consulta, y los objetos DataColumn representan propiedades o atributos de
estos campos.
El siguiente fragmento imprime el nombre y tipo de datos en la base de datos para cada campo que
devuelve la consulta. La forma ms sencilla de comprender los datos devueltos por GetSchemaTable es
vincular el DataTable que devuelve con un DataGrid de Windows.
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT OrderID, CustomerID, EmployeeID, OrderDate FROM Orders";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);

54

Escuela de Informtica del Ejrcito

conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
DataTable tabla = lector.GetSchemaTable();
lector.Close();
conexin.Close();
foreach(DataRow fila in tabla.Rows)
Console.WriteLine("{0} - {1}", fila["ColumnName"],
(SqlDbType)fila["ProviderType"]);

El DataTable contiene informacin adicional si se pasa CommandBehavior.KeyInfo en la llamada a


ExecuteReader

GetData
La propiedad Depth y el mtodo GetData estn reservados para consultas que devuelven datos
jerrquicos. Estas caractersticas no tienen soporte en la versin actual de ADO.NET.

3.4.6

Crear objetos SqlParameter

La clase SqlParameter tiene siete constructores. La clase SqlParameterCollection tiene cuatro mtodos
Add sobrecargados que se pueden usar para crear objetos SqlParameter y aadirlos a la coleccin, adems
del nuevo mtodo AddWithValue. Tambin puede usar el mtodo CreateParameter de SqlCommand. Son
muchas posibilidades.
Qu forma de crear un SqlParameter es la mejor?. Eso depende de qu propiedades del SqlParameter
quiera configurar. Un constructor permite proporcionar valores para las propiedades ParameterName,
SqlDbType, Size, Direction, IsNullable, Precision, Scale, SourceColumn, SourceVersion y Value. Piense en
las propiedades que quiere configurar y use el constructor que le proporcione la funcionalidad que necesita.

3.4.7

Propiedades de la clase SqlParameter

La tabla 4.7 muestra las propiedades de uso habitual de la clase SqlParameter.


Tabla 4. 7: Propiedades de uso habitual de la clase SqlParameter

Propiedad
DbType

Tipo
DbType

Direction

ParameterDirection

IsNullable
ParameterName
Precision
Scale
Size
SourceColumn

Boolean
String
Byte
Byte
Int32
String

SourceColumnNullMapping

Boolean

SourceVersion

DataRowVersion

SqlDbType
SqlValue
Value
XmlSchemaCollectionDatabase
XmlSchemaCollectionName
XmlSchemaCollectionOwningSchema

SqlDbType
Object
Object
String

Descripcin
Especifica el tipo de datos en la base de datos para el
parmetro.
Especifica la direccin del parmetro entrada, salida,
entrada/salida o retorno.
Indica si el parmetro puede aceptar null.
Especifica el nombre del parmetro.
Especifica la precisin para el parmetro.
Especifica la escala numrica para el parmetro.
Especifica el tamao del parmetro.
Especifica el nombre de la columna en el DataSeto a la
que hace referencia este parmetro. Vea el Captulo 9.
Se usa en casos especiales para manipular nulos en la
lgica de actualizacin de SqlDataAdapter. Vea el
Captulo 10.
Especifica la versin de la columna en el DataSet a la
que hace referencia este parmetro. Vea el Captulo 9.
Especifica el tipo de datos SQL para el parmetro.
Especifica el valor para el parmetro usando un SqlType
Especifica el valor para el parmetro.
Se utilizan para manipular valores SqlXml en
parmetros. Vea el Captulo 11.

ParameterName
SqlCommand solamente soporta parmetros con nombre; no puede usarlo con marcadores de
parmetro como OLE DB y ODBC. Por tanto, la propiedad ParameterName debe coincidir con el parmetro
correspondiente tal como aparece en la consulta o en el procedimiento almacenado.

Direction
Si est llamando a un procedimiento almacenado y quiere usar parmetros de salida o valores de retorno
debe configurar la propiedad Direction de su Parameter con uno de los valores de la tabla 4.8. Como el
valor predeterminado es Input solo es necesario especificar esta propiedad para parmetros que no son
solamente de entrada.
Tabla 4. 8: Miembros de la enumeracin ParameterDirection

Constante
Input
Output

Valor
1
2

Descripcin
Valor predeterminado. El parmetro es solamente de entrada.
El parmetro es solo de salida.

55

ADO.NET 2.0

Tabla 4. 8: Miembros de la enumeracin ParameterDirection

Constante
InputOutput
ReturnValue

Valor
3
6

Descripcin
El parmetro es de entrada y salida
El parmetro contendr el valor de retorno de un procedimiento almacenado.

La mayor parte de las herramientas de generacin de cdigo consultan a la base de datos la informacin
de parmetros, incluyendo la direccin. Incluso si est utilizando una herramienta robusta, como las que
incluye Visual Studio, puede ser necesario modificar el valor de Direction en algunos casos.
Por qu?. La mayor parte de las bases de datos soportan parmetros de entrada, salida y
entrada/salida en procedimientos almacenados, pero no todas las bases de datos tienen construcciones de
lenguaje que permitan especificar la direccin de los parmetros del procedimiento almacenado. SQL
Server soporta la palabra clave OUTPUT en definiciones de procedimientos almacenados para especificar
que el parmetro puede devolver un valor. Sin embargo, la definicin del parmetro es la misma
independientemente de si el parmetro es de entrada/salida o solo de salida. Como resultado, las
herramientas de generacin de cdigo no pueden determinar si el parmetro es de entrada/salida o solo de
salida. Las herramientas de Visual Studio asumen que el parmetro es de entrada/salida. Si quiere que el
parmetro sea solo de salida debe indicarlo explcitamente en el cdigo.

Value, SqlValue
Utilice la propiedad Value para configurar o comprobar el valor del SqlParameter. Esta propiedad tiene el
tipo de datos Object, por lo que tendr que convertir los datos al tipo de datos correcto.
La propiedad SqlValue tiene el mismo comportamiento que la propiedad Value, salvo que se utiliza para
tipos de datos SqlTypes en lugar de tipos de datos bsicos de .NET.

SourceColumn, SourceVersion, SourceColumnNullMapping


Las propiedades SourceColumn, SourceVersion y SourceColumnNullMapping controlan cmo obtiene el
SqlParameter los datos a partir de un DataRow cuando se envan cambios pendientes a la base de datos
llamando al mtodo Update del DataAdapter.

DbType, SqlDbType
La propiedad DbType est disponible en todas las clases Parameter SqlParameter, OracleParameter,
OdbcParameter y OleDbParameter. Esta propiedad tiene un valor de la enumeracin DbType, una
enumeracin que recoge tipos de datos generales de base de datos, como AnsiString, DateTime, Int16,
Int32, String, etc.
La propiedad SqlDbType es similar, salvo que utiliza terminologa de SQL Server para definir los tipos de
datos VarChar en lugar de AnsiString, SmallInt en lugar de Int16, NVarChar en lugar de String, etc. y
aade valores que no tienen equivalente en DbType NText, Image, Timestamp, etc.
Utilice la propiedad DbType solo si est escribiendo cdigo que debe funcionar con diferentes tipos de
Parameter. Si est trabajando solo con SqlParameter use la propiedad SqlDbType, cuyos valores estn
especficamente pensados para SQL Server.
Puede configurar la propiedad SqlDbType directamente, o puede confiar en la deduccin de tipo para
configurarla. Si crea un SqlParameter con una llamada a AddWithValue de una SqlParameterCollection se
utiliza el valor proporcionado para configurar implcitamente la propiedad SqlDbType. Tambin puede
configurar esta propiedad implcitamente al asignar un valor a la propiedad Value.
En trminos generales, siempre se elige el tipo adecuado para SqlDbType cuando se configura la
propiedad Value; para parmetros de salida configure SqlDbType explcitamente.

Precision, Scale, Size


Cuando se define la estructura de una tabla en una base de datos algunos tipos de datos necesitan que
se especifique informacin adicional adems del propio tipo de datos. Las columnas basadas en binario y
en caracteres a menudo tienen un tamao mximo; si est usando SqlParameter con uno de estos tipos de
datos debe asignar el tamao mximo a Size. Los tipos de datos numricos a menudo permiten especificar
la escala (nmero de dgitos) y precisin (nmero de dgitos decimales).

56

Escuela de Informtica del Ejrcito

Recuperar datos con SqlDataAdapter

Como se vio en el Captulo anterior, puede usar objetos SqlCommand y SqlDataReader para ejecutar
consultas examinar sus resultados. Pero, que pasa si quiere almacenar los resultados de una consulta
usando el cach fuera de lnea de ADO.NET, la clase DataSet?.
Podra ejecutar la consulta y despus cargar un DataSet fila a fila recorriendo los datos disponibles en el
SqlDataReader. Otra opcin es usar el mtodo Load que ADO.NET 2.0 aade a las clases DataSet y
DataTable, como se ve en el siguiente fragmento:
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
conexin.Open();
SqlDataReader lector = comando.ExecuteReader();
//Almacena los resultados fuera de lnea
DataTable tabla = new DataTable();
tabla.Load(lector);
lector.Close();
conexin.Close();
//Muestra los datos
dgResultados.DataSource = tabla;

Este caso es muy simple, ya que parte de una tabla vaca creada para la ocasin; pero si el DataTable y
SqlDataReader no coinciden, la complejidad aumenta mucho.
En este ejemplo se ejecuta una consulta SELECT estndar. Si no le gustan los nombres de las columnas
puede asignarles un alias dentro de la consulta. Por ejemplo, si sustituye la consulta del ejemplo anterior por
esta otra, la tabla ser ms legible. Los corchetes en torno al nombre Id.Cliente son necesarios a causa del
punto.
SELECT CustomerID AS [Id.Cliente], CompanyName AS Nombre FROM Customers

Y que pasa si en lugar de una consulta SELECT queremos ejecutar procedimiento almacenado?. Si el
procedimiento contiene columnas con nombres ilegibles o inadecuados no hay forma de cambiarlos, si no
es modificando mediante cdigo la estructura de la tabla por medio de sus columnas. El cdigo resultante
no resulta simple, de hecho puede resultar bastante confuso.
Afortunadamente, no es necesario. El modelo de objetos ADO.NET ofrece una solucin ms elegante: la
clase SqlDataAdapter. En este captulo aprender cmo usar esta clase para almacenar los resultados de
consultas en objetos DataSet y DataTable.

4.1 Qu es un SqlDataAdapter
La clase SqlDataAdapter acta como un puente entre las mitades conectadas y desconectadas del
modelo de objetos ADO.NET. Puede usar esta clase para obtener datos de la base de datos y almacenarlos
en su DataSet. Tambin puede usarla para enviar a la base de datos actualizaciones almacenadas en el
DataSet. En este captulo nos centraremos en la obtencin de datos desde la base de datos; ms adelante
veremos la direccin contraria.

4.1.1

Utilidad de la clase SqlDataAdapter

El objeto SqlDataAdapter est diseado para trabajar con datos desconectados. Tal vez el mejor ejemplo
sea su mtodo Fill. Ni siquiera es necesario tener una conexin activa con la base de datos para llamar a
este mtodo. Si lo llama sobre un SqlDataAdapter cuya conexin est cerrada, el propio adaptador abre la
conexin, consulta la base de datos, obtiene los resultados de la consulta, los almacena en el DataSet y
cierra la conexin.
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
DataSet ds = new DataSet();
SqlDataAdapter adp = new SqlDataAdapter(consulta, cadcon);
adp.Fill(ds);
dgResultados.DataSource = ds;
dgResultados.DataMember = "Table";

Una vez que se completa la llamada, ya no existe ninguna conexin entre el objeto SqlDataAdapter y el
objeto DataSet. El DataSet no mantiene ninguna referencia, interna ni externa, al SqlDataAdapter, y el
57

ADO.NET 2.0

SqlDataAdapter no mantiene ninguna referencia al DataSet. El DataSet no contiene informacin que indique
de donde proceden los datos ni cadena de conexin ni nombre de tabla. El DataSet contiene de forma
predeterminada los nombres de columna, pero estros nombres de columna no tienen por qu coincidir con
columnas de la tabla en la base de datos. De esta forma puede pasar objetos DataSet desde su servidor de
capa media a sus aplicaciones cliente sin divulgar informacin sobre la ubicacin o estructura de su base de
datos.
El SqlDataAdapter es una comunicacin de dos direcciones. Puede usarlo para enviar una consulta y
almacenar sus resultados en un DataSet, y puede usarlo para enviar cambios pendientes de vuelta a la
base de datos utilizando su mtodo Update. Cuando se llama a Update se proporciona el DataSet como
parmetro; el DataSet almacena los cambios, pero es el SqlDataAdapter el que contiene la lgica de
actualizacin.
La lgica de actualizacin del SqlDataAdapter la controla el programador. Puede usar sus propias
consultas INSERT, UPDATE y DELETE o procedimientos almacenados. El SqlDataAdapter tiene cuatro
propiedades que contienen objetos Command, una para leer datos, otra para enviar actualizaciones
pendientes, otra para inserciones y otra para borrados. Puede especificar sus propias consultas o
procedimientos almacenados para cada uno de estos objetos, as como parmetros para mover datos
desde el DataSet a su procedimiento almacenado y vuelta.
Puede proporcionar su propia lgica de actualizacin, o puede solicitar a ADO.NET que genere consultas
de accin. Incluso puede usar Visual Studio para generar la lgica de actualizacin en tiempo de diseo.

4.1.2

Anatoma de la clase SqlDataAdapter

La clase SqlDataAdapter est diseada para ayudarle a


almacenar los resultados de su consulta en objetos DataSet
y DataTable. La clase SqlDataAdapter consiste en una serie
de objetos SqlCommand y una coleccin de propiedades de
correspondencia que determinan cmo se comunica el
SqlDataAdapter con el DataSet. La figura 5.1 muestra la
estructura de SqlDataAdapter.

Comandos hijos
Cuando se usa un SqlDataAdapter para almacenar los
resultados de una consulta en un DataSet el adaptador utiliza
un SqlCommand y su SqlConnection para comunicar con la
base de datos el correspondiente a la propiedad
SelectCommand. Internamente, el SqlDataAdapter utiliza un
SqlDataReader para obtener los resultados y copia la
informacin en filas nuevas en el DataSet.
La clase SqlDataAdapter tiene otras propiedades que
contienen SqlCommand InsertCommand, UpdateCommand
y DeleteCommand. Estas propiedades se utilizan para enviar
los cambios a la base de datos.

Fig.5. 1: Estructura de la clase SqlDataAdapter

Coleccin TableMappings
De forma predeterminada el SqlDataAdapter asume que las columnas del SqlDataReader se
corresponden con las columnas del DataSet. Pero habr situaciones en las que querr que el esquema del
DataSet sea diferente al de la base de datos. Puede querer utilizar un nombre diferente para una columna
concreta del DataSet. Ya vimos al inicio del captulo que puede lograrlo por medio de un alias en la
consulta. El SqlDataAdapter ofrece un mecanismo para hacer corresponder los resultados de la consulta
con la estructura del DataSet: la coleccin TableMappings.
La propiedad TableMappings contiene un objeto DataTableMappingsCollection que contiene una
coleccin de objetos DataTableMapping. Cada uno de estos objetos permite crear una correspondencia
entre una tabla (o vista o procedimiento almacenado) de la base de datos con el nombre de tabla
correspondiente en el DataSet. El objeto DataTableMapping tiene tambin una propiedad ColumnMappings
que contiene un objeto DataColumnMappingsCollection, que consiste en una coleccin de objetos
DataColumnMapping. Cada objeto DataColumnMapping hace corresponder una columna de la base de
datos con una columna del DataSet. Estas clases residen en el espacio de nombres System.Data.Common.
La figura 5.2 muestra la forma de funcionar de estas colecciones. En la figura hacemos corresponder la
tabla Emp de la base de datos con la tabla Empleados del DataSet, pero la informacin de correspondencia
indica que se est haciendo corresponder Table con Empleados. Esto se debe a que el SqlDataAdapter
en realidad no tiene conocimiento de con qu tabla de la base de datos se comunica. El SqlDataAdapter
puede recuperar nombres de columna del resultado de la consulta usando el SqlDataReader, pero no tiene
58

Escuela de Informtica del Ejrcito

forma de determinar el nombre de la tabla. Como resultado el


SqlDataAdapter asume que el nombre de la tabla es Table.
Por tanto la entrada en la coleccin TableMappings es de
Tables a Empleados.
El siguiente fragmento muestra cmo configurar la
coleccin TableMappings de un SqlDataAdapter.

Fig.5. 2:Coleccin TableMappings de SqlDataAdapter

SqlDataAdapter adp = new SqlDataAdapter();


DataTableMapping mapaTablas;
DataColumnMapping mapaColumnas;
mapaTablas = adp.TableMappings.Add(Table, Empleados);
mapaColumnas = mapaTablas.Add(EmpID, IdEmpleado);
mapaColumnas = mapaTablas.Add(LName, Apellido);
mapaColumnas = mapaTablas.Add(FName, Nombre);

4.2 Crear y usar objetos SqlDataAdapter


Ahora que sabemos para qu sirve un SqlDataAdapter vamos a ver cmo crearlo y usarlo.

4.2.1

Crear un SqlDataAdapter

Cuando se crea un SqlDataAdapter por lo general se quiere configurar su propiedad SelectCommand


con un objeto SqlCommand vlido. El siguiente ejemplo configura SelectCommand para un nuevo
SqlDataAdapter.
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
SqlDataAdapter adaptador = new SqlDataAdapter();
adp.SelectCommand = adaptador;

Constructores de SqlDataAdapter
La clase SqlDataAdapter tiene tres constructores que pueden ayudar a simplificar la tarea de creacin.
Uno de los constructores acepta una cadena de consulta y una cadena de conexin.
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);

Este mtodo tiene un inconveniente potencial. Suponga que va a usar una serie de objetos
SqlDataAdapter en su aplicacin. La creacin de estos objetos de esta forma crea un nuevo SqlConnection
para cada SqlDataAdapter. Puede asegurarse de que sus objetos SqlDataAdapter usan el mismo objeto
SqlConnection usando el constructor de SqlDataAdapter que acepta un objeto SqlConnection. El siguiente
cdigo crea dos objetos SqlDataAdapter que usan el mismo SqlConnection.
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
SqlConnection conexin = new SqlConnection(cadcon);
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
SqlDataAdapter adpClientes = new SqlDataAdapter(consulta, conexin);
consulta = "SELECT OrderID, CustomerID, OrderDate FROM Orders";
SqlDataAdapter adpPedidos = new SqlDataAdapter(consulta, conexin);

SqlDataAdapter tambin tiene un tercer constructor que acepta un objeto SqlCommand. Si ya ha creado
un objeto SqlCommand y quiere crear un SqlDataAdapter que lo use para leer de la base de datos, utilice
este constructor.
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
SqlConnection conexin = new SqlConnection(cadcon);
SqlCommand comando = new SqlCommand(consulta, conexin);
SqlDataAdapter adaptador = new SqlDataAdapter(comando);

4.2.2

Recuperar resultados de una consulta

Ahora que hemos visto unas pocas formas de crear un SqlDataAdapter mediante programacin veamos
como usar uno para almacenar los resultados de una consulta en un DataSet. Primero, crearemos un
SqlDataAdapter sencillo para obtener datos de la tabla Customers en la base de datos Northwind.
59

ADO.NET 2.0

Uso de mtodo Fill


La llamada al mtodo Fill de la clase SqlDataAdapter ejecuta la consulta almacenada en la propiedad
SelectCommand del SqlDataAdapter y almacena sus resultados en un DataSet.
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
DataSet ds = new DataSet();
SqlDataAdapter adp = new SqlDataAdapter(consulta, cadcon);
adp.Fill(ds);
dgResultados.DataSource = ds;
dgResultados.DataMember = ds.Tables[0].TableName;

En este cdigo, al llamar a Fill se crea un DataTable en el DataSet. El nuevo DataTable contiene
columnas que se corresponden con las que devuelve la consulta CustomerID y CompanyName.

Recuperar resultados usando SqlTypes


La clase DataSet ahora est equipada completamente para manejas las diferentes clases SqlType del
espacio de nombres System.Data.SqlTypes. En ADO.NET 2.0 puede usar un SqlDataAdapter para
almacenar los resultados de su consulta usando SqlTypes en lugar de los tipos de datos .NET estndar
asignando True a la propiedad ReturnProviderSpecificTypes de SqlDataAdapter antes de llamar a Fill.
Puede modificar el ejemplo anterior para devolver SqlTypes y el resto del cdigo seguir corriendo
correctamente. La diferencia es que los objetos DataColumn que se crean implcitamente tendrn como
propiedad DataType SqlString en lugar de String.

Crear objetos DataTable y DataColumn usando el mtodo Fill


Al llamar al mtodo Fill en el ejemplo anterior se crea un DataTable en el DataSet. El nuevo DataTable
tiene columnas con los nombres de las columnas de la consulta, pero en nombre del objeto DataTable es
Table, no Customers.
El SqlDataAdapter crea implcitamente un SqlDataReader para recuperar los resultados de la consulta.
Antes de que el SqlDataAdapter examine la primera fila obtiene informacin de las caractersticas de
esquema del SqlDataReader para determinar nombres de columna y tipos de datos. Como vimos en el
Captulo 3, el nombre de tabla al que hace referencia la consulta no est disponible en el esquema de
SqlDataReader de forma predeterminada. Si comprueba la propiedad TableName del DataTable que cre el
SqlDataReader en el ejemplo anterior ver que tiene el poco descriptivo nombre Table.
Sin embargo, puede aadir informacin de esquema al propio SqlDataAdapter de modo que sepa cmo
asociar los resultados de la consulta con una DataTable llamada Clientes. Ya hemos visto este
comportamiento cuando discutimos la coleccin TableMappings de SqlDataAdapter. Podemos aadir un
elemento a la coleccin para indicar al SqlDataAdapter que queremos que coloque los resultados de la
consulta en una tabla llamada Clientes:
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
DataSet ds = new DataSet();
SqlDataAdapter adp = new SqlDataAdapter(consulta, cadcon);
adp.TableMappings.Add("Table", "Cliente");
adp.Fill(ds);
dgResultados.DataSource = ds;
dgResultados.DataMember = "Cliente";

Sobrecargas del mtodo Fill


Hay ms de una forma de usar el mtodo Fill para rellenar un DataSet. Vamos a ver los mtodos Fill
disponibles por grupos.
Especificar el DataTable. SqlDataAdapter ofrece dos mtodos Fill que dan ms control sobre el DataTable
que se utilizar.
En lugar de tener que aadir una entrada a la coleccin TableMappings del objeto SqlDataAdapter puede
especificar un nombre de tabla en el mtodo Fill.
SqlDataAdapter.Fill(DataSet, NombreTabla);

Tambin puede especificar un DataTable en lugar de un DataSet cuando llame al mtodo Fill.
SqlDataAdapter.Fill(DataTable);

Este mtodo Fill es til cuando ya ha creado un DataTable que quiere rellenar.
60

Escuela de Informtica del Ejrcito

string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +


"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
DataTable tabla = new DataTable();
SqlDataAdapter adp = new SqlDataAdapter(consulta, cadcon);
adp.Fill(tabla);
dgResultados.DataSource = tabla;

Paginar con el mtodo Fill de la clase SqlDataAdapter. Habr recorrido catlogos en lnea que muestran
elementos pgina a pgina. Si el catlogo tiene cientos de elementos el sitio Web puede mostrar 20
elementos por pgina. El SqlDataAdapter tiene un mtodo Fill que puede usar para obtener solo una parte
de los resultados de la consulta:
SqlDataAdapter.Fill(DataSet, intRegInicio, intNumRegistros, NombreTabla);

Recuerde que el parmetro para el registro inicial tiene base cero, de modo que el cdigo para obtener
las 20 primeras filas es:
SqlDataAdapter.Fill(DataSet, 0, 20, Productos);

Tambin es importante tener en mente que el uso de este mtodo Fill afecta solo al almacenamiento de
filas en el DataSet. Supongamos que est consultando una tabla que contiene 1000 filas y est obteniendo
las filas en pginas de 20 registros. La siguiente llamada almacena las ltimas 20 filas en el DataSet:
SqlDataAdapter.Fill(DataSet, 980, 20, Productos);

Pero la consulta en realidad obtiene 1000 registros. El SqlDataAdapter ejecuta el SqlCommand de su


propiedad SelectCommand y llama al mtodo Read del SqlDataReader resultante 980 veces para saltarse
las filas correspondientes.
Aunque esta forma del mtodo Fill puede facilitar la ruptura de la consulta en pginas, no es un prodigio
de eficiencia. Hay formas ms eficientes, pero mucho ms complejas de lograr la paginacin con objetos
DataSet y SqlDataReader que veremos al hablar de aplicaciones Web.

Abrir y cerrar conexiones


En los ejemplos que muestran cmo usar el mtodo Fill puede haber observado que no hemos abierto ni
cerrado ninguna conexin. Si llama al mtodo Fill de un objeto DataAdapter y la conexin correspondiente a
su propiedad SelectCommand est cerrada, el DataAdapter abre la conexin, enva la consulta, obtiene los
resultados y cierra la conexin. El objeto SqlDataAdapter siempre deja la conexin en el estado en el que se
la encuentra; si est abierta antes de llamar a Fill, seguir abierta despus de la ejecucin del mtodo.
Esta forma de funcionar puede ser til porque no es necesario abrir la conexin, y no hay problema si
olvida cerrarla. Sin embargo, hay ocasiones en la que es conveniente abrir la conexin explcitamente.
Supongamos que al arrancar la aplicacin usa varios objetos SqlDataAdapter para cargar sus DataSet
con los resultados de varias consultas. Ya ha visto cmo usar el constructor para que varios SqlDataAdapter
usen la misma conexin. Si en este caso llama a los mtodos Fill de todos los SqlDataAdapter
consecutivamente, estar abriendo y cerrando la conexin para cada llamada. La forma de evitar estas
aperturas y cierres repetidos es llamar al mtodo Open de la conexin antes del primer Fill y a Close
despus del ltimo.

Varias llamadas al mtodo Fill


Qu hacer si quiere renovar los datos en su DataSet?. Tal vez haya ledo los datos al iniciar la
aplicacin y quiera obtener datos ms recientes. La solucin sencilla es limpiar el DataSet, o DataTable, y
llamar de nuevo al mtodo Fill de SqlDataReader.
Supongamos que no se da cuenta de que esta es la mejor forma de proceder, y se limita a llamar de
nuevo a Fill por segunda vez. De esta forma se le est pidiendo al SqlDataAdapter que ejecute la consulta
especificada y almacene los resultados en el DataSet dos veces. La primera llamada a Fill crea una nueva
tabla dentro del DataSet llamada Clientes. La segunda llamada al mtodo Fill aade los resultados de la
consulta en la misma tabla del DataSet; por tanto, cada cliente aparecer dos veces. Ni el SqlDataAdapter
ni el DataTable tienen forma de saber qu clientes son duplicados.
Por lo general, los administradores de base de datos definen claves principales en las tablas de una
base de datos. Una de las ventajas de esta prctica es que impide que los usuarios creen filas duplicadas.
El objeto DataTable tiene una propiedad PrimaryKey; si el DataTable que est rellenando el SqlDataAdapter
tiene una clave principal, el DataTable usar esta clave para determinar qu filas son duplicados.
Supongamos que el nombre de compaa de un cliente ha cambiado en la base de datos. Al llamar al
mtodo Fill por segunda vez se recupera esta nueva informacin. El DataTable usar su clave principal para
determinar si ya existe una fila para este cliente concreto. Si el cliente ya existen en el DataTable se aplicar
61

ADO.NET 2.0

la informacin recuperada a la fila que ya existe en el DataTable. Sin embargo, las filas eliminadas en la
base de datos no se eliminarn del DataTable.
Suponga que un cliente concreto estaba en la base de datos la primera vez que se llam al mtodo Fill
del objeto SqlDataAdapter y este objeto lo aade al DataTable. Ms adelante, alguien observa que este
cliente no debe estar en la base de datos y lo elimina de la misma. Si llama a Fill por segunda vez el
SqlDataAdapter no encontrar informacin de este cliente en la consulta, pero no elimina la fila del
DataTable.
Y ya hemos cerrado el crculo completo. Si necesita renovar todos los datos debe limpiar el DataSet o
DataTable antes de llamar al mtodo Fill del SqlDataAdapter. Usando esta metodologa se asegura de que
no tendr filas duplicadas, incluso sin clave principal en el DataTable, y no tendr en su aplicacin filas que
ya no existan en la base de datos.

4.2.3

Hacer corresponder los resultados de la consulta con el DataSet

Anteriormente describimos el papel que juega la coleccin TableMappings de la clase SqlDataAdapter.


Vamos a ver con ms detalles cmo se usa esta coleccin en el cdigo.

Coleccin TableMappings de la clase SqlDataAdapter


La coleccin TableMappings controla cmo hace corresponder el SqlDataAdapter el DataSet con la base
de datos. Si deja esta coleccin vaca, llama al mtodo Fill y proporciona como parmetro un DataSet sin
especificar un nombre de tabla el SqlDataAdapter asumir que quiere trabajar con un DataTable llamado
Table.
La propiedad TableMappings contiene un objeto DataTableMappingsCollection. Este objeto contiene una
coleccin de objetos DataTableMapping. Aadiendo la siguiente lnea de cdigo se aade a la coleccin
TableMappings un objeto DataTableMapping que indica que debe comunicarse con una tabla del DataSet
llamada Empleados.
SqlDataAdapter.TableMappings.Add(Table, Empleados);

Una vez creado un objeto DataTableMapping puede crear correspondencias de columna para la tabla.
En un ejemplo anterior hicimos corresponder las columnas EmpID, LName y FName de la base de datos
con las columnas IdEmpleado, Apellido y Nombre del DataSet usando el siguiente cdigo:
SqlDataAdapter adp = new SqlDataAdapter();
DataTableMapping mapaTablas;
DataColumnMapping mapaColumnas;
mapaTablas = adp.TableMappings.Add(Table, Empleados);
mapaColumnas = mapaTablas.Add(EmpID, IdEmpleado);
mapaColumnas = mapaTablas.Add(LName, Apellido);
mapaColumnas = mapaTablas.Add(FName, Nombre);

Tanto el objeto DataTableMappingsCollection como el objeto DataColumnMappingsCollection tienen un


mtodo AddRange que puede usar para aadir un array de correspondencias a la coleccin en una sola
llamada:
SqlDataAdapter adaptador = new SqlDataAdapter();
DataTableMapping mapaTablas = adaptador.Add(Table, Empleados);
DataColumnMapping[] mapasColumnas = new DataColumnMapping[]{
new DataColumnMapping(EmpID, IdEmpleado),
new DataColumnMapping(LName, Apellido),
new DataColumnMapping(FName, Nombre),
};
mapaTablas.ColumnMappings.AddRange(mapasColumnas);

Propiedad MissingMappingAction
Ahora ya sabe cmo cargar la coleccin TableMappings de un objeto SqlDataAdapter con informacin de
columna y de tabla. Sin embargo, habr observado que no tiene que proporcionar esta informacin.
Anteriormente vio ejemplos que usaban el mtodo Fill de un SqlDataAdapter para crear y rellenar un
DataTable sin que el SqlDataAdapter tuviera informacin de correspondencia entre columnas.
En la mayora de los casos los desarrolladores usan los mismos nombres de columna en el DataSet y en
la base de datos. El equipo de desarrollo de ADO.NET se dio cuenta de que a los desarrolladores no les
gustara tener que configurar la coleccin TableMappings con nombres idnticos para la base de datos y el
DataSet para poder cargar datos en el DataSet. Cuando el SqlDataAdapter examina los resultados de la
consulta y encuentra una columna que no existe en la coleccin de correspondencias comprueba su
propiedad MissingMappingAction para determinar que hacer con ella.
La propiedad MissingMappingAction acepta valores de la enumeracin del mismo nombre del espacio de
nombres System.Data. De forma predeterminada su valor es Passthrough. Con este valor es
62

Escuela de Informtica del Ejrcito

SqlDataAdapter asume que las columnas que no aparecen en la coleccin de correspondencias se deben
incluir en el DataSet usando los nombres de columna del conjunto de resultados. El valor Ignore indica al
SqlDataAdapter que ignore las columnas que no aparecen en la coleccin. Otro posible valor es Error, que
indica que se debe lanzar una excepcin si se detecta en el resultado de la consulta una columna que no
existe en la coleccin.

4.2.4

Trabajar con lotes de consultas

Todas las consultas utilizadas en este captulo recuperan un nico conjunto de resultados. Algunas
bases de datos, como SQL Server, permiten enviar un lote de consultas que devuelve mltiples conjuntos
de resultados. Si crea un SqlDataAdapter con una consulta de este tipo y llama a su mtodo Fill con un
DataSet como parmetro, se crear una tabla por cada conjunto de resultados. Las tablas de este DataSet
tendrn los nombres Table, Table1, Table2, y as sucesivamente.
La coleccin TableMappings de SqlDataAdapter puede contener mltiples objetos DataTableMapping
para controlar los nombres que usar el SqlDataAdapter para almacenar los resultados del lote de
consultas. El siguiente cdigo almacena los resultados de una consulta por lotes en dos tablas llamadas
Clientes y Pedidos.
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName, ContactName FROM Customers " +
"WHERE CustomerID = 'ALFKI';" +
"SELECT OrderID, CustomerID, OrderDate FROM Orders WHERE CustomerID = 'ALFKI'";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.TableMappings.Add("Table", "Clientes");
adaptador.TableMappings.Add("Table1", "Pedidos");
DataSet ds = new DataSet();
adaptador.Fill(ds);

4.2.5

Obtener informacin de esquema

La clase DataTable, que describir en detalle ms adelante, est diseada para imponer limitaciones a
los datos, como clave principal, longitud mxima de campos de cadena y posibilidad de aceptar nulos.
Obtener esta informacin en tiempo de ejecucin puede ser costoso, y en muchos casos los desarrollos no
tienen necesidad de recuperar esta informacin. Por eso, la clase SqlDataAdapter de forma predeterminada
no obtiene esta informacin. Sin embargo, si se encuentra en una situacin en la que est dispuesto a
recuperar la penalizacin para recuperar informacin de esquema adicional sobre los resultados puede usar
un par de caractersticas del SqlDataAdapter: la propiedad MissingSchemaAction y el mtodo FillSchema.

Propiedad MissingSchemaAction
Habr observado que hasta ahora, todos los ejemplos que usan el mtodo Fill de la clase
SqlDataAdapter utilizan objetos DataSet y DataTable que no contienen informacin de esquema. De forma
predeterminada el SqlDataAdapter aadir columnas para almacenar los resultados de la consulta si estas
no existen ya en el DataSet o DataTable. Este comportamiento est controlado por la propiedad
MissingSchemaAction.
La propiedad acepta valores de la enumeracin MissingSchemaAction del espacio de nombres
System.Data. El valor predeterminado es Add. Como en el caso de MissingMappingAction puede ignorar las
columnas que falten usando el valor Ignore o lanzar una excepcin usando el valor Error.
La enumeracin MissingSchemaAction tiene otro valor: AddWithKey, un nombre poco adecuado para su
significado. Si se utiliza este valor y el SqlDataAdapter encuentra una columna que no existe en el DataSet
o DataTable, se aade la columna y se configuran dos atributos de esquema adicionales: MaxLength y
AllowDBNull. Si el DataTable an no contiene ninguna columna este valor hace que el SqlDataAdapter pida
a la base de datos informacin de clave principal.

Mtodo FillSchema
El mtodo FillSchema de SqlDataAdapter se puede usar para obtener solamente informacin de
esquema y almacenarla en su DataSet o DataTable. Las firmas de este mtodo se corresponden con las del
mtodo Fill bsico. Puede pasarle un DataSet, un DataTable, o un DataSet y un nombre de tabla.
Cada mtodo FillSchema tambin necesita un valor de la enumeracin SchemaType: Mapped o Source.
Este valor determina si el SqlDataAdapter aplica las configuraciones de su coleccin TableMappings a los
resultados de la consulta. Si usa Source el SqlDataAdapter usar los nombres de columna que devuelva la
consulta. Usando Mapped el SqlDataAdapter aplicar las configuraciones de TableMappings a las columnas
que devuelve la consultas.
FillSchema configura las propiedades AutoIncrement, AllowDBNull y MaxLength de las columnas
devueltas y tambin crear una clave principal en el DataTable resultante si la base de datos indica que el
63

ADO.NET 2.0

resultado de la consulta contiene una columna o conjunto de columnas que representa una clave principal o
nica.

4.3 Referencia de la clase SqlDataAdapter


4.3.1

Propiedades

Las propiedades de la clase SqlDataAdapter se pueden dividir en dos grupos las que controlan la
comunicacin con el origen de datos y las que controlan la comunicacin con el DataSet.
Las propiedades que contienen la comunicacin con el almacn de datos son las que contienen objetos
SqlCommand: SelectCommand, UpdateCommand, InsertCommand y DeleteCommand. Estas propiedades
contienen los objetos que ejecuta el SqlDataAdapter cuando se quiere mover informacin entre el DataSet y
la base de datos.
La tabla 5.1 describe las propiedades de la clase SqlDataAdapter
Tabla 5. 1: Propiedades de la clase SqlDataAdapter

Propiedad
AcceptChangesDuringFill

Tipo
Boolean

AcceptChangesDuringUpdate

Boolean

ContinueUpdateOnError

Boolean

DeleteCommand
FillLoadOption

SqlCommand
LoadOption

InsertCommand
MissingMappingAction

SqlCommand
MissingMappingAction

MissingSchemaAction

MissingSchemaAction

ReturnProviderSpecificTypes

Boolean

SelectCommand

SqlCommand

TableMappings

DataTableMappingCollection

UpdateBatchSize

Int32

UpdateCommand

SqlCommand

Descripcin
Determina el RowState de las filas recuperadas por el
SqlDataAdapter. Predeterminado: True
Determina si el SqlDataAdapter llamar implcitamente
a AcceptChanges tras enviar cambios pendientes en un
DataRow. Predeterminad: True.
Controla si el SqlDataAdapter continua enviando
cambios si encuentra un error. Predeterminado: False.
Comando usado para enviar borrados pendientes.
Controla cmo maneja el DataTable la carga de filas
que ya existen en el DataTable. Es necesario que est
configurada la propiedad PrimaryKey. Predeterminado:
OverwriteChanges.
Comando usado para enviar inserciones pendientes.
Controla el comportamiento del SqlDataAdapter cuando
se obtienen columnas que no aparecen en la coleccin
TableMappings. Predeterminado: Passthrough.
Controla el comportamiento del SqlDataAdapter cuando
obtiene columnas que no aparecen en la coleccin de
columnas del objeto DataTable. Predeterminado = Add.
Controla si el SqlDataAdapter usa tipos estndar .NET o
tipos especficos del proveedor, en este caso SqlTypes,
para almacenar los resultados. Predeterminado: False.
Comando que se usa para consultar la base de datos y
obtener resultados para almacenar en un DataSet o
DataTable.
Coleccin que usa el SqlDataAdapter para hacer
corresponder los resultados de la consulta con el
DataSet.
Controla cuantos DataRow enva por lote el
SqlDataAdapter. Predeterminado: 1.
Comando usado para enviar actualizaciones pendientes

SelectCommand, UpdateCommand, InsertCommand, DeleteCommand


Cada una de estas propiedades contiene un SqlCommand.
Si proporciona una cadena de consulta en el constructor de SqlDataAdapter esta cadena se convertir
en la propiedad CommandText de SelectCommand.
Si proporciona una SqlConnection en el constructor de SqlDataAdapter, ste se usar en la propiedad
Connection de las cuatro propiedades. Si proporciona una cadena de conexin, cada una de las cuatro
propiedades tendr su propio SqlConnection basado en esta cadena.

TableMappings
Como ya hemos dicho, el SqlDataAdapter y el DataSet estn completamente desconectados el uno del
otro. Por tanto cmo sabe el SqlDataAdapter cmo comunicarse con el DataSet?. Qu pasa si el DataSet
proporcionado al mtodo Fill contiene varias tablas?. Cmo sabr el SqlDataAdapter qu DataTable
examinar?.
El SqlDataAdapter tiene una propiedad TableMappings que contiene una coleccin de objetos
DataTableMapping. Cada objeto DataTableMapping tiene una propiedad ColumnMappings que devuelve
una coleccin de objetos DataColumnMapping. Esta jerarqua de objetos se corresponde con la coleccin
de objetos DataTable y objetos DataColumn del DataSet.
64

Escuela de Informtica del Ejrcito

Cuando el SqlDataAdapter recupera datos del almacn de datos usa la informacin de la coleccin
TableMappings para determinar en qu parte del DataSet almacenar los resultados de la consulta. Por lo
general, los nombres de columna de la consulta sern los mismos que se usen en el DataSet; si quiere
cambiarlos deber usar esta propiedad.
El siguiente cdigo muestra cmo configura la coleccin TableMappings de un SqlDataAdapter en base
a la consulta. En cada DataTableMapping y DataColumnMapping la primera cadena corresponde al nombre
del elemento recuperado de la base de datos, y la segunda al nombre del elemento en el DataSet.
En este ejemplo consultamos la tabla Authors de la base de datos de ejemplo pubs. Esta tabla tiene
columnas llamadas au_id, au_lname y au_fname, Cambiaremos los el nombre de la tabla a Autores y
los de las columnas a IdAutor, Apellido y Nombre.
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=pubs;" +
"Integrated Security=True";
string consulta = "SELECT au_id, au_fname, au_lname FROM Authors";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTableMapping mapaTablas = adaptador.TableMappings.Add("Table", "Autores");
mapaTablas.ColumnMappings.Add("au_id", "IdAutor");
mapaTablas.ColumnMappings.Add("au_fname", "Nombre");
mapaTablas.ColumnMappings.Add("au_lname", "Apellido");
DataSet ds = new DataSet();
adaptador.Fill(ds);

Qu pasa si la consulta que ejecuta el SqlDataAdapter contiene informacin que no aparece en la


coleccin TableMappings de SqlDataAdapter?. De forma predeterminada el SqlDataAdapter asumir que
quiere recuperar esta informacin y aadirla a la tabla.

MissingMappingAction, MissingSchemaAction
Cuando el SqlDataAdapter obtiene los resultado de la consulta busca las tablas y columnas
correspondientes en su coleccin TableMappings. La propiedad MissingMappingAction de SqlDataAdapter
controla su comportamiento en situaciones en que el SqlDataAdapter recupera tablas o columnas que no se
corresponden con entradas de la coleccin TableMappings. El valor predeterminado es Passthrough, pero
puede usar otros valores de la enumeracin MissingMappingAction del espacio de nombres System.Data. El
valor Ignore har que se ignoren las tablas y columnas de la consulta que no se encuentren en la coleccin
TableMappings. Si utiliza el valor Error recibir una excepcin si la consulta contiene tablas o columnas que
no aparecen en esta coleccin.
El SqlDataAdapter tiene tambin una propiedad MissingSchemaAction que controla el comportamiento
del SqlDataAdapter si las tablas o columnas de la consulta no aparecen en el DataSet de destino. El valor
predeterminado es Add, que indica al SqlDataAdapter que aada al DataSet las tablas o columnas. Puede
asignar a esta propiedad otros valores de la enumeracin MissingSchemaAction de System.Data. El valor
Ignore hace que se ignoren las tablas o columnas que no aparezcan en el DataSet, y Error que se lance una
excepcin.
El valor AddWithKey de MissingSchemaAction aadir las tablas o columnas que falten en el DataSet,
pero tambin aadir informacin de esquema. Este comportamiento es similar a llamar al mtodo
FillSchema del SqlDataAdapter.

AcceptChangesDuringFill, AcceptChangesDuringUpdate
La propiedad AcceptChangesDuringFill acepta un valor booleano y controla el RowState de las filas
recuperadas por el SqlDataAdapter. Cuando su valor es True, predeterminado, los nuevos objetos DataRow
tendrn como RowState Unchanged. Si el valor de esta propiedad es False el RowState de las filas
aadidas ser Added.
Esto significa que si cambia AcceptChangesDuringFill a False puede consultar una tabla de un base de
dato, pasar el DataSet a un DataAdapter configurado para comunicar con otra base de datos e insertar las
filas recin recuperadas en esta base de datos.
La propiedad AcceptChangesDuringUpdate es similar. Cuando se enva a la base de datos un cambio
pendiente en un DataRow por medio del mtodo Update de SqlDataAdapter, un proceso que veremos ms
adelante, el SqlDataAdapter llama implcitamente a AcceptChanges para el DataRow. Este proceso marca
el DataRow como Unchanged, indicando que no quedan cambios pendientes de enviar. Si no quiere que el
SqlDataAdapter llame a AcceptChanges tras el envo cambie AcceptChangesDuringUpdate, cuyo valor
predeterminado es True, a False.

ContinueUpdateOnError
Si utiliza un SqlDataAdapter para enviar actualizaciones a la base de datos est confiando en
actualizacin optimista. Si lee los contenidos de una fila, modifica la fila en el DataSet y enva el cambio
65

ADO.NET 2.0

pendiente a la base de datos usando un SqlDataAdapter el intento de actualizacin puede fallar si otro
usuario ya ha modificado los contenidos de la misma fila. Ms adelante trataremos esta situacin; por ahora
sepa que cuando se es optimista las cosas no siempre funcionan como se espera.
La propiedad ContinueUpdateOnError controla cmo reacciona el SqlDataAdapter cuando detecta un
fallo en un intento de enviar los cambios pendientes en un DataRow. De forma predeterminada el valor de
esta propiedad es False, lo que significa que el SqlDataAdapter se detiene cuando encuentra un fallo. Si
quiere que el SqlDataAdapter contine a pesar de los fallos cambie el valor de esta propiedad a True.

ReturnProviderSpecificTypes
De forma predeterminada el SqlDataAdapter almacena los resultados de la consulta en un DataSet o
DataTable usando tipos de datos .NET bsicos String, DateTime, etc. Si quiere almacenar estos
resultados usado SqlTypes SqlString, SqlDateTime, etc. Asigne el valor True a la propiedad
ReturnProviderSpecificTypes.

FillLoadOption
La propiedad FillLoadOption controla cmo maneja un DataTable los resultados de consulta que
corresponden con una fila que ya existe. Esta propiedad acepta un valor de la enumeracin LoadOption, y el
valor predeterminado es OverwriteChanges. Este valor hace que el DataTable sobrescriba los cambios
almacenados actualmente en el DataRow. El DataTable escribir los valores recuperados tanto en el valor
original como en el actual del DataRow. Si utiliza el valor PreserveChanges se conservarn los cambios
actuales en el DataRow, escribiendo los valores recuperados por el SqlDataAdapter solo en los valores
originales del DataRow correspondiente.
La opcin Upsert es una combinacin de update (actualizar) e insert (insertar), y la traduccin libre es si
la fila existe, trata la accin como una actualizacin, si no existe, como una insercin. Si utiliza este valor
los valores recuperados por el SqlDataAdapter se asignarn a los valores actuales de las filas que ya
existe; si no existe un DataRow correspondiente en el DataTable, la nueva fila se marcar como pendiente
de insertar.

UpdateBatchSize
De forma predeterminada, el SqlDataAdapter enva los cambios pendientes a la base de datos fila a fila.
ADO.NET 2.0 permite enviar varias filas a la vez. Para permitir la actualizacin por lotes cambie el valor
predeterminado de UpdateBatchSize, 1, por el valor deseado.

4.3.2

Mtodos

La clase SqlDataAdapter tiene solo cuatro mtodos, que se describen en la tabla 5.2.
Tabla 5. 2: Mtodos de la clase SqlDataAdapter

Mtodo
Fill
FillSchema
GetFillParameters
Update

Descripcin
Ejecuta la consulta almacenada en SelectCommand y almacena los resultados en un DataTable.
Recupera informacin de esquema correspondiente a la consulta almacenada en el
SelectCommand.
Devuelve un array que contiene los parmetros para el SelectCommand.
Enva los cambios almacenados en el DataSet, DataTable o DataRow a la base de datos.

Fill
Al llamar al mtodo Fill de SqlDataAdapter se ejecuta la consulta almacenada en el objeto SqlCommand
de la propiedad SelectCommand y se almacenan los resultados en un DataTable de un DataSet. El mtodo
tambin devuelve un entero de 32 bit que indica el nmero de fila recuperadas.
Cuando se pasa un DataSet como parmetro del mtodo Fill el SqlDataAdapter examina los contenidos
de su coleccin TableMappings para determinar que objetos DataTable y DataColumn utilizar en el DataSet.
Si el SqlDataAdapter no encuentra la informacin de esquema que espera en su coleccin TableMappings o
en el DataSet consulta sus propiedades MissingMappingAction y MissingSchemaAction para determinar
cmo reaccionar.
El mtodo Fill est sobrecargado. Puede pasarle un DataTable en lugar de un DataSet, o un DataSet y
una cadena con el nombre del DataTable que quiere cargar o crear.
El mtodo Fill tambin tiene una sobrecarga que resulta til cuando es necesario utilizar paginacin. Esta
versin permite especificar el DataSet de destino, el nmero del registro inicial, la cantidad de registros y el
nombre de la tabla de destino. El SqlDataAdapter ejecuta la consulta y sencillamente descarta los registros
anteriores al inicial solicitado. A continuacin recupera el nmero de registros solicitado. Si se alcanza el
final del conjunto de resultados antes de obtener el nmero de registros pedido, se recuperan los registros
existentes sin lanzar ninguna excepcin.
Esta no es la mejor forma de dividir el resultado de una consulta en pginas. Supongamos que tenemos
un conjunto de resultados con 100 filas que queremos mostrar de 10 en 10. Obtendremos 10 pginas, para
66

Escuela de Informtica del Ejrcito

cada una de las cuales se solicitarn a la base de datos las 100 filas correspondientes al conjunto de
resultados, descartando las que no correspondan a la pgina.
La nica ventaja de este mtodo de paginacin es su sencillez. Existen formas mucho ms eficientes de
lograr el mismo efecto, pero resultan ms complejas. Por ejemplo, puede almacenar la clave del ltimo
registro recuperado y usarla como parmetro de la consulta para recuperar los registros siguientes.

FillSchema
El mtodo FillSchema permite recuperar informacin de esquema sobre la consulta antes de ejecutarla.
Como el mtodo Fill, FillSchema recupera nombres y tipos de datos para cada columna de la consulta.
FillSchema tambin recupera informacin sobre si la columna acepta valores null y configura la propiedad
AllowDBNull de los objetos DataColumn que crea.
Cuando se llama a FillSchema el SqlDataAdapter busca una clave principal o ndice nico en el conjunto
de resultados. Si encuentra uno de ellos configura la propiedad PrimaryKey del DataTable con el
DataColumn o array de DataColumn.
El uso del mtodo FillSchema es muy similar al de Fill con una excepcin. Como en el caso de Fill puede
proporcionar un DataSet, un DataSet y un nombre de tabla o un DataTable. La diferencia est en que
FillSchema aade un parmetro que permite controlar si se recupera la informacin de esquema
directamente del origen de datos o se aplica el TableMappings del SqlDataAdapter a la informacin
recuperada.
Este parmetro acepta valores de la enumeracin SchemaType de System.Data Source o Mapped. Si
se especifica Source el SqlDataAdapter genera informacin de esquema usando solo los nombres de
columna recuperados del origen de datos. Especificando Mapped se fuerza al SqlDataAdapter para aplicar
los contenidos de la coleccin TableMappings.
El mtodo FillSchema devuelve un array de objetos DataTable que contiene los objetos DataTable que
ha cargado el mtodo.
Puede llamar al mtodo FillSchema haciendo referencia a un DataTable que ya existe. En este caso el
SqlDataAdapter no sobrescribe las columnas que ya aparecen en el DataTable, sino que aade nuevas si
las que devuelve la consulta no aparecen en el DataTable.

GetFillParameters
El mtodo GetFillParameters acta como un atajo para la coleccin Parameters del objeto
SelectCommand del SqlDataAdapter. GetFillParameters devuelve la informacin de parmetros como un
array de objetos IParameter en lugar de usar el tipo Parameter del proveedor .NET especfico. Salvo que
necesite comprobar o modificar el tamao, precisin o escala del parmetro podr acceder a sus
parmetros usando este mtodo.

Update
El mtodo Update se utiliza para enviar al almacn de datos los cambios pendientes en un DataTable o
DataSet.
Como para Fill o FillSchema puede pasar a Update un DataSet, un DataSet y un nombre de tabla o un
DataTable. El mtodo Update proporciona otra sobrecarga puede pasar un array de objetos DataRow.
Esta opcin puede ser til si quiere pasar un subconjunto de filas de una tabla en base a un filtro o relacin.
El mtodo Update devuelve un entero que indica el nmero de filas actualizados con xito en el almacn
de datos.

4.3.3

Eventos

La clase SqlDataAdapter proporciona tres eventos que se muestran en la tabla 5.3.


Tabla 5. 3: Eventos de la clase SqlDataAdapter

Evento
FillError
RowUpdating
RowUpdated

Se lanza
Cuando el SqlDataAdapter encuentra un error al rellenar el DataSet o DataTable
Antes de enviar una fila modificada a la base de datos
Despus de enviar una fila modificada a la base de datos

FillError
Si el SqlDataAdapter encuentra un error mientras rellena el DataSet o DataTable podr capturar este
error usando el evento FillError. El siguiente cdigo de ejemplo lanza el evento FillError porque la propiedad
MissingSchemaAction tiene el valor Error y una de las columnas del conjunto de resultados (EmployeeID)
no aparece en el DataTable.

67

ADO.NET 2.0

private void Form1_Load(object sender, EventArgs e) {


string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT OrderID, CustomerID, EmployeeID FROM Orders";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.MissingSchemaAction = MissingSchemaAction.Error;
adaptador.FillError += new FillErrorEventHandler(adaptador_FillError);
DataTable tabla = new DataTable("Orders");
tabla.Columns.Add("OrderID", typeof(int));
tabla.Columns.Add("CustomerID", typeof(string));
adaptador.Fill(tabla);
dgClientes.DataSource = tabla;
}
void adaptador_FillError(object sender, FillErrorEventArgs e) {
MessageBox.Show(e.Errors.Message);
e.Continue = true;
}

Hasta ahora no he sido capaz de usar este evento para capturar situaciones en que los datos
recuperados por el SqlDataAdapter violen una restriccin en el DataSet o DataTable.

RowUpdating, RowUpdated
El SqlDataAdapter tambin lanza eventos cuando enva cambios pendientes a la base de datos por
medio del mtodo Update. Si quiere examinar los cambios pendientes en su fila antes de enviarlos use el
mtodo RowUpdating. Si quiere ejecutar cdigo inmediatamente despus de enviar un cambio use el evento
RowUpdated.
static void Main(string[] args) {
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT TOP 1 OrderID, EmployeeID FROM Orders";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
SqlCommandBuilder cmdBuild = new SqlCommandBuilder(adaptador);
adaptador.RowUpdated += new SqlRowUpdatedEventHandler(adaptador_RowUpdated);
adaptador.RowUpdating += new SqlRowUpdatingEventHandler(adaptador_RowUpdating);
DataTable tabla = new DataTable("Orders");
adaptador.Fill(tabla);
tabla.Rows[0]["EmployeeID"] = (int)tabla.Rows[0]["EmployeeID"] + 1;
adaptador.Update(tabla);
tabla.Rows[0]["EmployeeID"] = (int)tabla.Rows[0]["EmployeeID"] - 1;
adaptador.Update(tabla);
Console.WriteLine("****************** Actualizacin completa ******************");
}
static void adaptador_RowUpdating(object sender, SqlRowUpdatingEventArgs e) {
Console.WriteLine("Evento RowUpdating: {0}", e.StatementType);
Console.WriteLine("
OrderID: {0}", e.Row["OrderID"]);
Console.WriteLine("
EmployeeID de {0} a {1}",
e.Row["EmployeeID", DataRowVersion.Original], e.Row["EmployeeID"]);
Console.WriteLine();
}
static void adaptador_RowUpdated(object sender, SqlRowUpdatedEventArgs e) {
Console.WriteLine("Evento RowUpdated: {0}", e.StatementType);
Console.WriteLine("
OrderID: {0}", e.Row["OrderID"]);
if(e.Status == UpdateStatus.ErrorsOccurred)
Console.WriteLine("
Se han producido errores");
else
Console.WriteLine("
Actualizado con xito");
Console.WriteLine();
}

Con actualizacin bsica, no por lotes, el SqlDataAdapter enva los cambios a la base de datos fila a fila.
Se lanza el evento RowUpdating, el SqlDataAdapter enva el cambio pendiente y se lanza el evento
RowUpdated.
El comportamiento cambia ligeramente cuando se utiliza la caracterstica de actualizacin por lotes de
SqlDataAdapter. Antes de enviar un lote de cambios el SqlDataAdapter lanza el evento RowUpdating para
cada fila pendiente en el lote. El SqlDataAdapter enva el lote de cambios y despus lanza el evento
RowUpdated una sola vez.

68

Escuela de Informtica del Ejrcito

Si quiere examinar las filas enviadas por el SqlDataAdapter por medio del evento RowUpdated
necesitar un poco ms de trabajo. El argumento de evento SqlRowUpdatedEventArgs puede ayudar. La
propiedad StatementType devolver Batch si el evento RowUpdated contiene un lote de actualizaciones.
Use la propiedad RowCount para determinar el nmero de cambios enviados a SQL Server en el lote. Use
el mtodo CopyToRows para acceder a las filas individuales.
static void Main(string[] args) {
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT OrderID, EmployeeID FROM Orders " +
"WHERE CustomerID = 'ALFKI'";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.UpdateBatchSize = 10;
SqlCommandBuilder cmdBuild = new SqlCommandBuilder(adaptador);
adaptador.RowUpdated += new SqlRowUpdatedEventHandler(adaptador_RowUpdated);
adaptador.RowUpdating += new SqlRowUpdatingEventHandler(adaptador_RowUpdating);
DataTable tabla = new DataTable("Orders");
adaptador.Fill(tabla);
foreach(DataRow fila in tabla.Rows)
fila["EmployeeID"] = (int)fila["EmployeeID"] + 1;
adaptador.Update(tabla);
foreach(DataRow fila in tabla.Rows)
fila["EmployeeID"] = (int)fila["EmployeeID"] - 1;
adaptador.Update(tabla);
Console.WriteLine("****************** Actualizacin completa ******************");
}
static void adaptador_RowUpdating(object sender, SqlRowUpdatingEventArgs e) {
Console.WriteLine("Evento RowUpdating: {0}", e.StatementType);
Console.WriteLine("
OrderID: {0}", e.Row["OrderID"]);
Console.WriteLine("
EmployeeID de {0} a {1}",
e.Row["EmployeeID", DataRowVersion.Original], e.Row["EmployeeID"]);
Console.WriteLine();
}
static void adaptador_RowUpdated(object sender, SqlRowUpdatedEventArgs e) {
Console.WriteLine("Evento RowUpdated: {0}", e.StatementType);
Console.WriteLine("Cambios enviados: {0}", e.RowCount);
DataRow[] filas = new DataRow[e.RowCount];
e.CopyToRows(filas);
foreach(DataRow fila in filas)
Console.WriteLine("
OrderID: {0}", fila["OrderID"]);
if(e.Status == UpdateStatus.ErrorsOccurred)
Console.WriteLine("
Se han producido errores");
else
Console.WriteLine("
Actualizado con xito");
Console.WriteLine();
}

69

ADO.NET 2.0

70

Escuela de Informtica del Ejrcito

Trabajar con objetos DataSet

En los ltimos captulos se ha descrito la funcionalidad bsica de las clases conectadas en el modelo de
objetos de ADO.NET, que componen un proveedor de datos .NET. Ahora es el momento de discutir la mitad
desconectadas las clases que usa ADO.NET para proporcionar un cach de datos desconectado rico y
funcional. En este captulo discutiremos los aspectos bsicos del almacenamiento de datos en la clase
DataSet y muchas de las clases que residen en un objeto DataSet.
El captulo describe brevemente las caractersticas principales del DataSet y despus se centra en
ejemplos de cmo puede usarlo. A lo largo del camino el captulo presenta otras clases (DataTable,
DataRow, DataColumn, DataRelation, UniqueConstraint y ForeignKeyConstraint) cuando los escenarios
presentados precisan su uso. Como resultado de la cantidad de clases que se tratan, este es un captulo
largo. Esta es una lista de los escenarios principales relativos a DataSet que explorar este captulo antes
de discutir cmo crear objetos DataSet usando Visual Studio y presentar la informacin de referencia de las
clases discutidas:

Crear un objeto DataSet.


Examinar la estructura creada por una llamada a Fill de SqlDataAdapter
Examinar los datos devueltos por un SqlDataAdapter
Validar los datos en un DataSet
Crear objetos DataTable en cdigo
Modificar los contenidos de un DataTable
Opciones de serializacin y remoting de DataSet en ADO.NET

5.1 Caractersticas de la clase DataSet


En su ncleo, un objeto DataSet es un conjunto de datos. Cuando los desarrolladores visualizan los
resultados devueltos por una consulta por lo general lo hacen con forma de tabla. Puede usar un DataSet
para almacenar los resultados de una consulta, pero un DataSet puede contener el resultado de mltiples
consultas, cada uno en su propia tabla.
La clase DataSet proporciona unas funcionalidades mucho ms potentes que las de DataReader,
funcionalidades que estudiaremos a continuacin.

5.1.1

Trabajar con datos desconectados

Los datos de un DataSet estn desconectados de la base de datos. Una vez que se obtienen los
resultados y se almacenan en un DataSet usando un objeto DataAdapter ya no hay conexin entre el
DataSet y la base de datos. Los cambios al contenido del DataSet no afectan a la base de datos. Si otros
usuarios modifican datos correspondientes al DataSet en la base de datos, no ver los cambios en el
DataSet.
El trabajo con estructuras de datos desconectadas definitivamente tiene muchas ventajas. La ventaja
principal es que no es necesaria una conexin viva con la base de datos. Una vez obtenidos los resultados
y colocados en un DataSet puede cerrar la conexin y seguir trabajando con los datos en el DataSet.
Las estructuras de datos desconectadas como los DataSet tambin son tiles cuando se crean
aplicaciones multicapa. Si su aplicacin usa objetos de negocio que se ejecutan en un servidor de capa
media para acceder a la base de datos, sus objetos de negocio necesitan pasar estructuras de datos
desconectadas a su aplicacin cliente. La clase DataSet est diseada para estas situaciones. Puede pasar
los contenidos de un DataSet de un componente a otro. El componente que recibe los datos puede trabajar
con la informacin como DataSet o como documento XML.

5.1.2

Recorrer, ordenar, buscar y filtrar

La clase DataSet permite examinar los contenidos de cualquier fila en cualquier momento. Puede
desplazarse hacia delante y hacia atrs sobre los resultados de la consulta siempre que quiera. Esto hace
que los objetos DataSet sean ideales para escenarios en los que el cdigo necesita recorrer datos, como
rutinas de informe, o en las que un usuario necesita recorrer los resultados de una consulta.
Los objetos DataSet tambin permiten cambiar la forma de ver los resultados de la consulta. Puede
ordenar los datos en un DataSet en base a una columna o a una serie de columnas. Puede buscar una fila
en base a un criterio simple. Tambin puede aplicar un filtro a los datos del DataSet de modo que solo las
filas que cumplan el criterio de filtro sean visibles. Veremos estas caractersticas ms a fondo ms adelante.

5.1.3

Trabajar con datos jerrquicos

Los objetos DataSet estn diseados para trabajar con datos jerrquicos. Permite definir relaciones
entres las tablas d datos que contiene. Visual Studio crear esta relacin automticamente en base a una
limitacin de clave externa definida entre las dos tablas en la base de datos. El control DataGridView se
puede configurar para usar la relacin de forma que solo muestre los registros correspondientes.
71

ADO.NET 2.0

5.1.4

Almacenar cambios

El trabajo con datos de solo lectura es sencillo. Uno de los mayores retos en la creacin de una
aplicacin de base de datos es transformar la entrada de usuario en cambios en el contenido de la base de
datos. La creacin de esta lgica en una aplicacin multicapa puede presentar un reto incluso mayor si la
aplicacin necesita almacenar cambios y enviarlos a la base de datos todos a la vez.
La clase DataSet permite almacenar los cambios a una fila de datos de modo que se puedan enviar a la
base de datos usando un DataAdapter. Tambin puede examinar las filas modificadas en el DataSet para
determinar en qu forma han cambiado la fila se ha insertado, modificado o borrado? as como
comparar sus valores original y actual.
En este captulo se aprender cmo modificar los contenidos de un DataSet. El envo de los cambios a la
base de datos usando SqlDataAdapter se ver ms adelante.

5.1.5

Integracin XML

El DataSet de ADO.NET est creado desde los cimientos para trabajar con XML. Puede guardar y cargar
contenidos de un DataSet desde y en archivos como documentos XML. El DataSet tambin permite separar
la informacin de esquema (tabla, columna y limitaciones) en un archivo de esquema XML.
En ADO.NET los objetos DataSet y los documentos XML son casi intercambiables. Es fcil pasar de una
estructura de datos a la otra. Esta dualidad permite que los desarrolladores usen los interfaces con los que
estn ms cmodos. Los programadores XML puede trabajar con objetos DataSet como documentos XML y
los programadores de base de datos pueden trabajar con documentos XML como objetos DataSet.
Veremos las caractersticas de los DataSet para XML ms adelante.

5.2 Uso de objetos DataSet


De algn modo, el DataSet y las clases relacionadas recuerdan a las matryoshka las muecas rusas
que se meten unas dentro de otras. Un DataSet contiene objetos DataTable y DataRelation. Un objeto
DataTable contiene DataRow, DataColumn y Constraint. Todas estas clase residen en el espacio de
nombres System.Data.
En lugar de explicar estas clases una a una, en este captulo mostrar la funcionalidad bsica del
DataSet por medio de ejemplos simples. A lo largo del camino veremos algo sobre las restantes clases
mencionadas.

5.2.1

Crear un objeto DataSet

La instanciacin de un DataSet en el cdigo es directa; sencillamente se usa la palabra clave new. El


DataSet tiene un constructor opcional que puede usar para configurar su propiedad DataSetName con una
cadena.
DataSet datos = new DataSet(NombreDelDataSet);
Console.WriteLine(datos.DataSetName);

5.2.2

Examinar la estructura creada al llamar a Fill

En el Captulo 4 aprendi a obtener los resultados de una consulta y ponerlos en un DataSet llamando al
mtodo Fill de la clase SqlDataAdapter. Antes de examinar lo resultados de la consulta vamos a ver la
estructura que se crea en el DataSet para almacenar estos resultados.

Objetos DataTable
El SqlDataAdapter almacena los resultados de una consulta en un DataTable. Puede usar este objeto
para examinar los resultados de una consulta, que se exponen en forma de una coleccin de filas y
columnas. La clase DataTable, a diferencia de SqlDataReader, est concebida para mantener datos
durables y proporciona una mayor funcionalidad. Los datos que contiene un DataTable se pueden modificar,
ordenar y filtrar.
Para manejar estos datos el DataTable expone una estructura ms perdurable para los datos que
contiene. Cada objeto DataTable tiene una propiedad Columns que devuelve una coleccin de objetos
DataColumn. Cada DataColumn se corresponde con una columna en los resultados de la consulta.

Objetos DataColumn
Dicho simplemente, los objetos DataColumn definen el esquema del DataTable. Cuando se usa el
mtodo Fill de la clase SqlDataAdapter para crear un DataTable el SqlDataAdapter tambin crea un objeto
DataColumn para cada columna en el resultado de la consulta. Los nuevos objetos DataColumn que crea el
SqlDataAdapter tienen configuradas solamente sus propiedades ms bsicas Name, Ordinal y DataType.
Este ejemplo muestra informacin bsica sobre los objetos DataColumn creados al llamar al mtodo Fill
de SqlDataAdapter:
72

Escuela de Informtica del Ejrcito

string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +


"Integrated Security=True";
string consulta = "SELECT OrderID, CustomerID from Orders";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataSet datos = new DataSet();
adaptador.Fill(datos, "Pedidos");
foreach(DataColumn columna in datos.Tables["Pedidos"].Columns)
Console.WriteLine("Nombre: {0}; Tipo: {1}", columna.ColumnName, columna.DataType);

Un objeto DataColumn contiene mucha ms informacin, pero por ahora interrumpiremos el estudio de
DataColumn para aprender a examinar los datos que el SqlDataAdapter ha colocado en el DataTable.

5.2.3

Examinar los datos que devuelve el SqlDataAdapter

Por medio de la clase DataTable todas las filas de un conjunto de resultados estn disponibles en todo
momento. Esta clase expone una propiedad Rows que contiene la coleccin de objetos DataRow
disponibles en la tabla. Vamos a ver cmo usar los objetos DataRow para examinar los resultados de la
consulta. En los ejemplos utilizaremos el DataSet creado en el ejemplo anterior.

Objetos DataRow
La clase DataRow permite examinar y modificar los contenidos de una fila del DataTable. Para acceder
al objeto DataRow correspondiente a una fila correspondiente en el DataTable se usa la propiedad Rows.
Esta propiedad contiene una DataRowCollection que contiene una coleccin de objetos DataRow. Como la
mayora de las clases coleccin, la clase DataRowCollection permite especificar un entero para indicar el
elemento al que quiere acceder.
El siguiente fragmento utiliza el mtodo Fill de la clase SqlDataAdapter para obtener los resultados de
una consulta y ponerlos en un nuevo objeto DataTable. A continuacin accede a la primera fila devuelta y
muestra los contenidos de sus dos columnas.
DataRow fila = datos.Tables["Pedidos"].Rows[0];
Console.WriteLine("OrderID
: {0}", fila["OrderID"]);
Console.WriteLine("CustomerID: {0}", fila["CustomerID"]);

Como puede ver, una vez obtenido un objeto DataRow del DataTable, el acceso al valor de una columna
concreta es similar al acceso a datos en un SqlDataReader. La clase DataRow tiene una propiedad Item
parametrizada que acta como indexador, devolviendo la columna especificada. Puede proporcionar un
nombre de columna, como en el ejemplo anterior, o un entero que representa la posicin ordinal de la
columna. Como en el caso del SqlDataReader el uso del ordinal resulta ms rpido.

Examinar datos en un DataRow


La clase DataRow expone una propiedad Table que devuelve el DataTable al que pertenece. Puede usar
esta propiedad para volver al DataTable y recuperar el nmero total de columnas y el nombre de cada una
de ellas. Este ejemplo usa esta propiedad para mostrar los contenidos de un DataRow, incluyendo nombres
de columna.
static void MuestraFila(DataRow fila) {
foreach(DataColumn columna in fila.Table.Columns)
Console.WriteLine("{0}: {1}", columna.ColumnName, fila[columna]);
}

El siguiente fragmento muestra una tercera forma para examinar los contenidos de una columna
concreta. El indexador de la clase DataRow acepta un objeto DataColumn. Esta forma es ligeramente ms
rpida, aproximadamente un 6%, que la bsqueda basada en ordinal.

Examinar los DataRow de un DataTable


Puede recorrer los objetos DataRow de un DataTable tan fcilmente como cualquier otra coleccin del
marco de trabajo .NET, usando un bucle foreach. El siguiente fragmento recorre los contenidos de un
DataTable, utilizando el mtodo MuestraFila del ejemplo anterior.
DataTable tabla = datos.Tables["Pedidos"];
foreach(DataRow fila in tabla.Rows) {
Console.WriteLine("Contenido de la fila: {0}", tabla.Rows.IndexOf(fila));
MuestraFila(fila);
}

5.2.4

Validar datos en el DataSet

Las bases de datos proporcionan diferentes mecanismos que puede usar para asegurar que los datos
que contienen son vlidos. La base de datos de ejemplo Northwind tiene definidas muchas reglas y
limitaciones. La columna CustomerID de la tabla Customers debe contener una cadena de hasta cinco
caracteres, y su valor debe ser nico dentro de la tabla. La tabla Orders genera un nuevo valor para OrderID
73

ADO.NET 2.0

y exige que el valor de CustomerID en cada fila haga referencia a una entrada existente en la tabla
Customers. En ocasiones querr aplicar reglas similares para validar datos en su aplicacin antes de enviar
cambios a su base de datos.
Este tipo de lgica puede parecer redundante porque se hacen las mismas validaciones dos veces, pero
puede mejorar el rendimiento. Si un usuario introduce datos que no son vlidos y nuestro DataSet los
acepta, cuando se enven a la base de datos esta los rechazar, pero habremos consumido tiempo y trfico
de red. Si nuestro DataSet rechaza estos datos localmente, nos evitaremos conexiones innecesarias con la
base de datos.
Para evitar la necesidad de escribir lgica de validacin que imite las limitaciones de una base de datos,
el DataSet de ADO.NET ofrece muchos de los mismos mecanismos disponibles en los sistemas de base de
datos. Puede separar estos mecanismos, tambin denominados limitaciones en dos categoras
restricciones a nivel de columna y restricciones a nivel de tabla.

Propiedades de validacin de DataColumn


La clase DataColumn expone una serie de propiedades que puede usar para validar datos.
ReadOnly. La forma ms sencilla de asegurar que los datos son vlidos es no permitir que los
usuarios los modifiquen. Si quiere que los datos de un DataColumn sean de solo lectura asigne el
valor True a su propiedad ReadOnly.
AllowDBNull. Algunas columnas de base de datos exigen tener un valor, mientras que otras aceptan
valores vacos o nulos. La propiedad AllowDBNull de DataColumn permite especificar si la columna
acepta valores nulos.
MaxLength. Muchas bases de datos restringen el tamao de una cadena en una columna. Por
ejemplo, en la tabla Customers la columna CustomerID acepta una columna de hasta cinco
caracteres y la compaa CompanyName hasta 40. Puede restringir la longitud de un DataColumn por
medio de la propiedad DataColumn.
Unique. El DataColumn permite especificar que los valores de una columna deben ser nicos usando
la propiedad Unique. Cuando se asigna el valor True a esta propiedad ADO.NET impide que se
aada o modifique una fila si esta accin crea un valor duplicado en la columna, lanzando una
ConstraintException.

Coleccin Constraints de la clase DataTable


Tambin puede validar los datos del DataSet configurando propiedades de los objetos DataTable. El
modelo de objetos de ADO.NET incluye dos clases que puede usar para definir limitaciones sobre un
DataTable. Estas clases, UniqueConstraint y ForeignKeyConstraint, derivan de la clase Constraint, y se
pueden aadir, modificar y examinar por medio de la coleccin Constraints de DataTable. La propiedad
PrimaryKey, por su parte, permite especificar una clave principal en la tabla.
UniqueConstraint. Si asigna True a la propiedad Unique de un DataColumn habr definido una
limitacin de unicidad en el DataTable que contiene la columna. A la vez se habr aadido un objeto
UniqueConstraint a la coleccin Constraints del DataTable. Configurar la propiedad Unique de un
DataColumn es ms simple que crear un nuevo UniqueConstraint en la coleccin Constraints. Sin
embargo, habr ocasiones en que deber crear objetos UniqueConstraint explcitamente, por ejemplo
cuando quiera asegurar la unicidad de una combinacin de columnas.
PrimaryKey. La clase DataTable permite definir una clave principal por medio de su propiedad
PrimaryKey, pero no existe una clase con este nombre. La propiedad PrimaryKey contiene un array
de DataColumn que el DataTable usa para construir un UniqueConstraint para imponer la unicidad de
la clave principal. El objeto DataRowCollection de ADO.NET tiene un mtodo Find que permite
localizar una fila en el DataTable mediante el valor o valores de la clave principal. Este mtodo se
tratar ms adelante.
Un DataTable puede tener varias limitaciones de unicidad, pero como mucho una clave principal.
ForeignKeyConstraint. Tambin puede aadir al DataTable limitaciones de clave externa; un
ejemplo en la base de datos Northwind es la exigencia de que cada fila de la tabla Orders tenga en su
columna CustomerID un valor existente en la columna CustomerID de la tabla Customers. Los objetos
ForeignKeyConstraint permiten crear esta limitacin.
Por lo general no ser necesario crear explcitamente ForeignKeyConstraint. Al crear un DataRelation
entre dos objetos DataTable de un mismo DataSet se crea un ForeignKeyConstraint. Este tema se
tratar ms adelante.
Las limitaciones definidas sobre las tablas y columnas de un DataSet solo son vlidas dentro del
DataSet. Si la base de datos acepta valores que contravienen estas limitaciones, el intento de cargar los
datos de la base de datos en el DataSet provocar una ConstraintException.
74

Escuela de Informtica del Ejrcito

Recuperar informacin de esquema


La validacin lleva tiempo. En muchos escenarios no querr configurar propiedades de validacin en su
DataSet, por lo que SqlDataAdapter no configura las propiedades de validacin en los objetos DataColumn
ni aade limitaciones a la coleccin Constraints cuando crea el DataTable por medio de su mtodo Fill,
salvo que se pida explcitamente.
Hay dos formas de indicar al SqlDataAdapter que quiere recuperar esta informacin de esquema de la
base de datos cuando aade columnas a su DataTable asignando AddWithKey a la propiedad
MissingSchemaAction de SqlDataAdapter o llamando al mtodo FillSchema.
El uso de SqlDataAdapter para obtener informacin de esquema puede ahorrar tiempo en el proceso de
diseo, pero salvo que su aplicacin sea una aplicacin de consulta para bases de datos que no conozca,
debe evitar su uso.
Si pide a SqlDataAdapter que obtenga informacin de esquema adicional usando estas caractersticas,
ste objeto consultar la informacin a la base de datos para cada nueva columna que cree. De esta forma
se configuran las propiedades ReadOnly, AllowDBNull, MaxLength y ReadOnly.
El SqlDataAdapter tambin intentar generar una clave principal para el DataTable, pero la peticin de
esta informacin provoca una penalizacin de rendimiento. Cuando SQL Server analiza la consulta que
recibe determinar a qu tabla o tablas hace referencia y obtiene la informacin necesaria para construir los
resultados. Si pide informacin de clave para los resultados de la consulta SQL Server tiene que hacer un
trabajo extra.
Tras determinar la tabla o tablas a las que hace referencia la consulta SQL Server tambin debe obtener
la informacin de clave principal para la tabla o tablas. Si su consulta hace referencia a una tabla que no
contiene informacin de clave principal, SQL Server busca una limitacin de unicidad. Si SQL Server
encuentra una clave principal o nica para identificar las columnas en la tabla comprueba si la consulta hace
referencia a la clave completa. Si la consulta solo utiliza una parte de la clave SQL Server no marca esta
parte como clave en el conjunto de resultados y el SqlDataAdapter no la marca como parte de la clave
principal del DataTable.
Como puede ver, una simple llamada a FillSchema de SqlDataAdapter obliga a una cantidad de trabajo
que no es trivial. El cdigo se ejecutar mucho ms rpido si proporciona el esquema para sus DataTable y
DataSet por medio de su cdigo.

5.2.5

Crear objetos DataTable en cdigo

Hasta ahora hemos creado objetos DataTable usando los mtodos Fill y FillSchema de SqlDataAdapter.
Tambin ha aprendido que debera crear sus propios objetos DataTable, en especial si quiere validar sus
datos usando limitaciones a nivel de tabla o de columna.

Crear un objeto DataTable


Puede crear un objeto DataTable de la misma forma que crea un DataSet. El constructor de DataTable
tiene un constructor adicional que permite especificar una cadena para usar en la propiedad TableName del
nuevo objeto.
DataTable tabla = new DataTable(Clientes);

Aadir el DataTable a la coleccin Tables de un DataSet


Una vez creado un DataTable puede aadirlo a la propiedad Tables, de tipo DataTableCollection, de un
objeto DataSet existente usando su mtodo Add:
DataSet datos = new DataSet();
datos.Tables.Add(tabla);

El mtodo Add de la coleccin DataTableCollection est sobrecargado de modo que puede crear un
objeto DataTable y aadirlo a la coleccin en un solo paso:
DataSet datos = new DataSet();
DataTable tabla = datos.Tables.Add(Clientes);

Puede determinar si un DataTable reside en un DataSet comprobando su propiedad DataSet. Si el


DataTable reside en un DataSet, la propiedad DataSet lo devuelve; en caso contrario devuelve null. La
propiedad DataSet del objeto DataTable es de solo lectura.

Aadir columnas a un DataTable


Para almacenar los resultados de una consulta el DataTable necesita tener columnas. Podemos aadir
columnas a la coleccin Columns de un DataTable usando cdigo casi idntico al usado para aadir un
nuevo DataTable al DataSet:
75

ADO.NET 2.0

DataSet datos = new DataSet();


DataTable tabla = datos.Tables.Add(Clientes);
DataColumn columna = tabla.Columns.Add(CustomerID);

Especificar un tipo de datos para un DataColumn


Puede usar la propiedad DataType de un DataColumn para configurar o comprobar el tipo de datos que
contiene. Esta propiedad es de solo lectura hasta que se aaden datos a la coleccin Rows del DataTable.
Aunque el tipo de datos que seleccione para su DataColumn depender del tipo de datos de la columna
en la base de datos no hay una relacin uno a uno entre los tipos de datos de base de datos y los tipos de
datos de DataColumn.
De forma predeterminada los objetos DataColumn tienen string como DataType. El DataColumn tiene un
constructor que permite especificar un tipo de datos, as como un nombre de columna. De forma similar, el
mtodo Add de la clase DataColumnCollection est sobrecargado para especificar valores para las
propiedades ColumnName y DataType.
DataSet datos = new DataSet();
DataTable tabla = datos.Tables.Add(Pedidos);
DataColumn columna = tabla.Columns.Add(OrderID, typeof(int));

El tipo de datos para la propiedad DataType es Type. El fragmento anterior muestra cmo obtener el
valor Type correspondiente al tipo de datos entero.

Aadir una clave principal


Las propiedades AllowDBNull, ReadOnly, MaxLength y Unique del objeto DataColumn permiten validar
los datos almacenados en la columna:
DataSet datos = new DataSet();
DataTable tabla = datos.Tables.Add(Clientes);
DataColumn columna = tabla.Columns.Add(CustomerID);
columna.AllowDBNull = false;
columna.ReadOnly = false;
columna.MaxLength = 5;
columna.Unique = true;

Configurar la clave principal para un DataTable es ligeramente ms complejo. La propiedad PrimaryKey


contiene un array de objetos DataColumn, por lo que no puede limitarse a asignar a la propiedad el nombre
de la columna o columnas que quiere usar para la clave.
Algunos DataTable usarn una sola columna como clave principal, otros usan un conjunto de columnas.
El siguiente fragmento incluye cdigo para los dos escenarios. La tabla Clientes usa una nica columna,
CustomerID, mientras que Detalles usa una combinacin de dos columnas, OrderID y ProductID. En ambos
casos debe crear un array de DataColumn y asignarlo a la propiedad PrimaryKey.
DataSet datos = new DataSet();
//Crea la tabla de clientes
DataTable tabla = datos.Tables.Add(Clientes);
tabla.Columns.Add(CustomerID, typeof(string));
...
tabla.PrimaryKey = new DataColumn[]{tabla.Columns[CustomerID]};
//Crea la tabla Detalles
tabla = datos.Tables.Add(Detalles);
tabla.Columns.Add(OrderID, typeof(int));
tabla.Columns.Add(ProductID, typeof(int));
...
tabla.PrimaryKey = new DataColumn[]{
tabla.Columns[OrderID], tabla.Columns[ProductID]};

Cuando se configura la clave principal de un DataTable ADO.NET automticamente asigna False a la


propiedad AllowDBNull de los objetos DataColumn incluidos en ella.

Aadir otras limitaciones


La clave principal es la limitacin ms usada, pero tambin puede aadir a un DataTable limitaciones de
unicidad y clave externa. La coleccin Constraints de la clase DataTable tiene un mtodo Add sobrecargado
que puede usar para aadir una nueva clave principal, clave nica o clave externa.
El mtodo Add de la clase ConstraintCollection acepta cualquier objeto que hereda de Constraint, de
modo que puede pasarle tanto un objeto UniqueConstraint como un ForeignKeyConstraint.

76

Escuela de Informtica del Ejrcito

Al igual que los mtodos Add de DataTableCollection y DataColumnCollection, puede crear su limitacin
a la vez que la aade. Puede usar este mtodo para aadir tanto limitaciones nicas como externas, y tanto
de una columna como de varias.
Primero vamos a ver un ejemplo que crea limitaciones nicas usando el mtodo Add. El primer
parmetro contiene el nombre de la nueva limitacin nica. El segundo parmetro contiene el DataColumn,
o array de DataColumn, sobre el que se aplica la limitacin. El tercer parmetro es un valor booleano que
determina si la nueva limitacin se usar como clave principal del DataTable.
DataSet datos = new DataSet();
DataTable tablaClientes = datos.Tables.Add("Clientes");
tablaClientes.Columns.Add("CustomerID", typeof(string));
tablaClientes.Columns.Add("CompanyName", typeof(string));
//...
tablaClientes.Constraints.Add(
"PK_CustomerID", tablaClientes.Columns["CustomerID"], true);
tablaClientes.Constraints.Add(
"UK_CompanyName", tablaClientes.Columns["CompanyName"], false);

El mtodo Add est sobrecargado de modo que se puede usar para crear limitaciones de clave externa.
La firma del mtodo es muy similar. El primer parmetro contiene el nombre de la nueva limitacin; el
segundo parmetro contiene el DataColumn o array de DataColumn de la tabla madre; el tercer parmetro
contiene el DataColumn o array de DataColumn de la tabla hija.
DataSet datos = new DataSet();
DataTable tablaClientes = datos.Tables.Add("Clientes");
DataTable tablaPedidos = datos.Tables.Add("Pedidos");
tablaClientes.Columns.Add("CustomerID", typeof(string));
//...
tablaPedidos.Columns.Add("OrderID", typeof(int));
tablaPedidos.Columns.Add("CustomerID", typeof(int));
//..
tablaPedidos.Constraints.Add("FK_Clientes_Pedidos",
tablaClientes.Columns["CustomerID"], tablaPedidos.Columns["CustomerID"]);

El problema es que este cdigo resulta difcil de leer y seguir; salvo que quien lo haya escrito haya tenido
la precaucin de usar nombres que sigan un cierto cdigo, como PK para clave principal y FK para clave
externa, es difcil saber que tipo de limitacin se est creando. Este problema se evita creando la limitacin
explcitamente para aadirla a la coleccin despus.

Columnas con autoincremento


El uso de columnas con autoincremento para generar valores clave para la base de datos tiene ventajas
e inconvenientes. El beneficio principal es que se utiliza lgica centralizada para crear enteros simples
nicos que sirven como clave para nuevas filas. La desventaja principal es que no se conocer el valor de la
clave hasta que no se enve la fila a la base de datos. Algunos desarrolladores y administradores de base
de datos prefieren usar Guid para generar claves nicas antes de enviar la fila a la base de datos.
Si est usando columnas de autoincremento en su base de datos las caractersticas de autoincremento
de ADO.NET pueden ayudarle a asignar una clave a sus filas antes de enviarlas a la base de datos.
ADO.NET proporciona soporte para las columnas con autoincremento por medio de tres propiedades de
DataColumn: AutoIncrement, AutoIncrementSeed y AutoIncrementStep.
Si quiere que ADO.NET genere estos valores de autoincremento para nuevas filas en su DataTable
asigne True a la propiedad AutoIncrement de su DataColumn.
DataSet datos = new DataSet();
DataTable tabla = datos.Tables.Add("Pedidos");
DataColumn columna = tabla.Columns.Add("OrderID", typeof(int));
columna.AutoIncrement = true;
columna.AutoIncrementSeed = -1;
columna.AutoIncrementStep = -1;
columna.ReadOnly = true;

El fragmento anterior crea una columna y la marca como autoincremento, pero a la vez asigna -1 a las
propiedades AutoIncrementSeed y AutoIncrementStep. Esta configuracin hace que se creen valores
negativos para la columna, con las ventajas que se explicarn un poco ms adelante.
Las propiedades AutoIncrementSeed y AutoIncrementStep controlan cmo genera ADO.NET nuevos
valores. Cuando est trabajando con una tabla vaca, ADO.NET asignar el valor de AutoIncrementSeed a
la columna de autoincremento de la primera nueva fila, y usar la propiedad AutoIncrementStep para
incrementar los siguientes valores. Por ejemplo, si AutoIncrementSeed es 3 y AutoIncrementStep 2, los
cinco primeros valores sern 3, 5, 7, 9, 11.
77

ADO.NET 2.0

El comportamiento cambia ligeramente si se aaden filas al DataTable usando un DataAdapter. Suponga


que est trabajando con un DataTable cuya estructura se corresponde con la de la tabla Customers de la
base de datos Northwind y que las propiedades AutoIncrementSeed y AutoIncrementStep del DataColumn
OrderID valen 5. Si aade nuevas filas a la tabla cuando est vaca los valores de la columna OrderID sern
consecutivamente 5, 10, 15, 20, etc. Sin embargo, si aade filas al DataTable usando un DataAdapter y
despus nuevas filas usando el mtodo Add de la coleccin Rows, los nuevos valores generados para
OrderID dependern de los datos obtenidos de la base de datos. ADO.NET generar valores incrementales
sucesivos en base al valor mayor de la columna en el DataTable y el valor de la propiedad
AutoIncrementStep.
Supongamos que en este ejemplo el valor mximo de OrderID en su DataTable es 973. Si genera
nuevas filas en este momento ADO.NET sumar el valor de AutoIncrementStep a este valor mximo, y el
nuevo OrderID ser 978.
Es muy importante tener en cuenta que ADO.NET solo conoce los valores que existen en el DataTable.
No sabe qu valor generar SQL Server para la prxima fila. Supongamos que ejecuta una consulta que
solo obtiene los pedidos para un cliente, y que el mximo valor de OrderID en el DataTable es 973. La base
de datos puede contener valores mayores que este, pero ADO.NET no tiene forma de saberlo, por lo que
cuando genere valores de autoincremento para nuevas filas es posible que use valores que ya existen en la
base de datos.
Las caractersticas de autoincremento de ADO.NET nos proporcionan un medio para asignar nmeros
consecutivos a los registros, de modo que podamos contar y paginar. El siguiente fragmento de cdigo llena
un DataTable en base a los resultados de una consulta simple. Antes de rellenar la tabla el cdigo le aade
una columna con autoincremento. Como la consulta no devuelve datos para esta columna, ADO.NET
genera un nuevo valor para cada fila recuperada.
El fragmento utiliza objetos DataView y DataRowView, que discutiremos ms adelante, pero su uso en
este ejemplo es auto explicativo. Una vez relleno el DataTable en base a los resultados de la consulta
usaremos la propiedad RowFilter de DataView para ver solo una pgina del DataTable y escribir los
contenidos de esta pgina en pantalla.
A pesar de lo que se dijo en el Captulo 4 y anteriormente en este mismo captulo se usa el mtodo
FillSchema para crear la estructura del DataTable, pero se hace solamente para reducir la cantidad de
cdigo. En una aplicacin real no lo hara, por cuestiones de rendimiento; privilegios de profesor.
//Prepara la conexin y la tabla
string cadcon = @"Data Source=.\SqlExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName FROM Customers";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tabla = new DataTable("Clientes");
adaptador.FillSchema(tabla, SchemaType.Source);
//Aade la columna secuencial
DataColumn columna = tabla.Columns.Add("NumFila", typeof(int));
columna.AutoIncrement = true;
columna.AutoIncrementSeed = 1;
columna.AutoIncrementStep = 1;
//Recupera los resultados de la consulta
adaptador.Fill(tabla);
//Crea un DataView para mostrar las filas 21 a 30
int tamPagina = 10;
int numPagina = 3;
DataView vista = new DataView(tabla);
vista.RowFilter = String.Format("NumFila > {0} and NumFila <={1}",
(numPagina - 1) * tamPagina, numPagina * tamPagina);
//Muestra el contenido de las filas visibles en el DataView
foreach(DataRowView fila in vista)
Console.WriteLine("{0}: {1} - {2}", fila["NumFila"], fila["CustomerID"],
fila["CompanyName"]);

Esta forma de paginacin es mejor que la que vimos anteriormente, pero sigue sin ser ptima, ya que
exige recuperar todas las filas, incluso aunque solo se quiera mostrar una parte. Sin embargo, este ejemplo
muestra la utilidad de las caractersticas de autoincremento de ADO.NET para resolver problemas
aparentemente no relacionados.

78

Escuela de Informtica del Ejrcito

Qu hacer y qu no hacer con autoincremento


Si: Usar las caractersticas de autoincremento de ADO.NET si est usando columnas con autoincremento
en su base de datos.
Si: Configurar las propiedades de autoincremento antes de aadir filas al DataTable. De otra forma, los
valores de estas propiedades no afectarn a los valores de autoincremento que genere el DataTable.
No: Enviar los valores de autoincremento generados por ADO.NET a la base de datos. Los valores que
genera ADO.NET estn simplemente para marcar un lugar. Deje que la base de datos genere los
nuevos valores reales. Ms adelante se ver cmo permitir que la base de datos genere valores y
cmo obtener estos valores y colocarlos en las filas correspondientes de su DataTable.
No: Mostrar valores de autoincremento de nuevas filas que no se han enviado a la base de datos. La base
de datos probablemente generar valores diferentes a los creados por ADO.NET. El usuario de la
aplicacin puede no estar al tanto de que el valor generado por ADO.NET es solamente temporal. Si el
usuario anota el valor generado para una fila por ADO.NET posiblemente cuando quiera utilizarlo este
valor sea incorrecto.
Si: Configurar las propiedades AutoIncrementSeed y AutoIncrementStep a -1 antes de aadir filas a la
tabla. De esta forma se generan valores temporales negativos. Si la configuracin de autoincremento
en la base de datos es la predeterminada, estos valores temporales no pueden aparecer en la base de
datos, lo que puede servir de ayuda para no asumirlos como reales.

Aadir una columna basada en expresin


Los administradores de bases de datos por lo general evitan incluir en sus tablas informacin que se
pueda obtener a partir de informacin existente en la base de datos. Por ejemplo, la tabla Order Details de
la base Northwind contiene columnas que almacenan el precio por unidad y la cantidad para cada lnea de
elemento en un pedido, pero no contiene una columna para coste total de la lnea. A los usuarios no les
preocupa si el coste total de la lnea se almacena en la base de datos, siempre que puedan ver este coste.
La mayora de las bases de datos soportan en su lenguaje de consulta expresiones para aadir
columnas calculadas. Si quiere que la base de datos calcule y devuelva el total de una lnea en el conjunto
de resultados, puede usar la siguiente consulta:
SELECT OrderID, ProductID, UnitPrice, Quantity, UnitPrice * Quantity AS Total
FROM [Order Details]

Si rellena un DataTable con los resultados de esta consulta tendr una columna que contiene el
resultado de la expresin, pero si se modifican los valores de las columnas Quantity o UnitPrice en el
DataTable el valor de la columna Total permanecer sin cambios. La base de datos realiza el clculo y
devuelve el resultado; una vez recuperados los resultados de la consulta el contenido de la columna no
variar.
ADO.NET permite crear objetos DataColumn basados en expresin. En lugar de incluir una expresin
como la anterior en la consulta, se puede configurar la propiedad Expression de un DataColumn. Cuando se
examinen los valores de la columna ADO.NET evaluar la expresin y devolver el resultado. Puede
modificar el valor de cualquiera de las columnas que intervienen en la expresin, y cuando consulte la
columna calculada el valor ser el correcto.
El siguiente fragmento aade al DataTable una columna que contiene el coste total de un elemento de
pedido:
DataSet datos = new DataSet();
DataTable tabla = datos.Tables.Add(Detalles);
tabla.Columns.Add("Quantity", typeof(int));
tabla.Columns.Add("UnitPrice", typeof(decimal));
tabla.Columns.Add("Total", typeof(decimal), "Quantity * UnitPrice");

La propiedad Expresin soporta una amplia variedad de funciones, incluyendo funciones agregadas que
pueden hacer referencia a datos en otros objetos DataTable del DataSet.

Crear objetos DataTable para las tablas Customers, Orders y Order Details
Hemos visto muchas caractersticas de las clases DataSet, DataTable y DataColumn. Ahora vamos a
unirlas todas en un mismo DataSet. El siguiente cdigo crea un nuevo DataSet que contiene tres objetos
DataTable. En el proceso el cdigo configura propiedades de los objetos DataColumn, incluyendo
DataType, AllowDBNull y AutoIncrement y crea limitaciones de clave principal y de clave externa.
Este cdigo aade un par de opciones ms refinadas para el DataSet y configura los valores de
AutoIncrementStep y AutoIncrementSeed en la columna OrderID del DataTable Orders para lograr mayor
79

ADO.NET 2.0

control sobre los valores de autoincremento que crea ADO.NET para los nuevos pedidos. Tambin
configura la propiedad MaxLength de las columnas basadas en cadena.
El cdigo crea limitaciones de clave externa, pero no rellena la coleccin Relations del DataSet. La clase
DataRelation se ver ms adelante.
El asistente de configuracin de origen de datos genera un DataSet fuertemente tipado. Por ahora basta
con saber que el DataSet fuertemente tipado es una clase que tiene todas las caractersticas de un DataSet
pero adems expone estructuras como objetos DataTable y DataColumn en forma de propiedades bien
definidas en lugar de como simples colecciones. El cdigo siguiente crea un DataSet que no est
fuertemente tipado.
DataSet datos = new DataSet();
DataTable tabla;
DataColumn columna;
ForeignKeyConstraint fk;
//Crea la tabla de clientes
tabla = datos.Tables.Add("Clientes");
tabla.Columnas.Add("CustomerID", typeof(string)).MaxLength = 5;
tabla.Columnas.Add("CompanyName", typeof(string)).MaxLength = 40;
tabla.Columnas.Add("ContactName", typeof(string)).MaxLength = 30;
tabla.Columnas.Add("Phone", typeof(string)).MaxLength = 24;
tabla.PrimaryKey = new DataColumn[]{tabla.Columns["CustomerID"]};
//Crea la tabla de pedidos
tabla = datos.Tables.Add("Pedidos");
columna = tabla.Columns.Add("OrderID", typeof(int));
columna.AutoIncrement = true;
columna.AutoIncrementSeed = -1;
columna.AutoIncrementStep = -1;
columna.ReadOnly = true;
columna = tabla.Columns.Add("CustomerID", typeof(string));
columna.AllowDBNull = false;
columna.MaxLength = 5;
columna = tabla.Columns.Add("EmployeeID", typeof(int));
columna = tabla.Columns.Add("OrderDate", typeof(DateTime));
tabla.PrimaryKey = new DataColumn[]{tabla.Columns["OrderID"]};
//Crea la tabla de detalles de pedido
tabla = datos.Tables.Add("Detalles Pedido");
columna = tabla.Columns.Add("OrderID", typeof(int));
columna = tabla.Columns.Add("ProductID", typeof(int));
columna = tabla.Columns.Add("UnitPrice", typeof(decimal)).AllowDBNull = false;
columna = tabla.Columns.Add("Quantity", typeof(int));
columna.AllowDBNull = false;
columna.DefaultValue = 1;
columna = tabla.Columns.Add("Discount", typeof(decimal)).DefaultValue = 0;
columna = tabla.Columns.Add("Total", typeof(decimal),
"UnitPrice * Quantity * (1 - Discount)");
tabla.PrimaryKey = new DataColumn[]{
tabla.Columns["OrderID"], tabla.Columns["ProductID"]};
//Crea las limitaciones de clave externa
fk = new ForeignKeyConstraint("FK_Clientes_Pedidos",
datos.Tables["Clientes"].Columns["CustomerID"],
datos.Tables["Pedidos"].Columns["CustomerID"]);
datos.Tables["Pedidos"].Constraints.Add(fk);
fk = new ForeignKeyConstraint("FK_Pedidos_Detalles",
datos.Tables["Pedidos"].Columns["OrderID"],
datos.Tables["Detalles Pedido"].Columns["OrderID"]);
datos.Tables["Detalles Pedido"].Constraints.Add(fk);

5.2.6

Modificar el contenido de un DataTable

Ya sabe cmo crear objetos DataSet, DataTable y DataColumn, y sabe cmo usar un SqlDataAdapter
para almacenar los resultados de una consulta en objetos DataTable. Tambin sabe cmo examinar los
contenidos de un DataTable. Vamos a ver ahora cmo aadir, modificar y borrar objetos DataRow. En los
ejemplos de este apartado se utilizar la tabla creada por el siguiente fragmento:
DataTable tabla = new DataTable("Clientes");
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("CompanyName", typeof(string));

80

Escuela de Informtica del Ejrcito

Aadir un nuevo DataRow


Cada objeto DataTable tiene una propiedad Rows que devuelve un objeto DataRowCollection, que
contiene una coleccin de objetos DataRow. Como la mayora de las colecciones, puede usar su mtodo
Add para aadir un nuevo elemento. Sin embargo, los objetos DataRow se diferencian de otros objetos
ADO.NET en el modo de crearlos.
Supongamos que quiere aadir mediante programacin 10 objetos DataRow a un DataTable que
contiene 10 objetos DataColumn. Para aadir una fila a la tabla se crea un objeto DataRow, se asignan
valores a sus campos y se aade la fila a la coleccin Rows del DataTable.
Si examina el explorador de objetos de Visual Studio ver que la clase DataRow no contiene ningn
constructor pblico. Aunque esto puede parecer poco intuitivo, tiene sentido; cmo puede un DataRow
determinar su estructura es decir, qu columnas contiene?. Como el DataTable ya contiene este
esquema, es el punto natural para crear nuevos DataRow.
La clase DataTable tiene un mtodo NewRow que devuelve un nuevo objeto DataRow que contiene
informacin sobre cada una de las columnas en la tabla. Una vez creado el nuevo DataRow puede cargar
sus diferentes columnas usando su propiedad tem. Tambin puede usar esta propiedad para examinar el
contenido de las columnas en la fila. La propiedad Item acta como indexador, por lo que ni siquiera es
necesario citarla explcitamente. Basta con indexar el DataRow con el nombre de la columna o su posicin.
El mtodo NewRow del DataTable crea una nueva fila, pero no la aade al DataTable. Por lo general no
querr la nueva fila en cuanto la cree porque, en este momento, est vaca. Los campos de la fila tienen su
valor predeterminado, o null si no hay valor predeterminado. Al crear la fila sin aadirla a la tabla puede
asignar valores a sus columnas antes de hacer que el DataRow sea parte del DataTable. La columna
CustomerID de nuestra tabla Customers no acepta valores null pero no tiene valor predeterminado.
Supongamos que tiene un objeto DataTable Customers que tiene una clave principal basada en la columna
CustomerID. Si intenta aadir una nueva fila a la tabla sin asignar un valor a la columna CustomerID se
producir una excepcin.
Una vez que se han proporcionado los valores para las columnas necesarias en la fila y ya est lista para
aadir a la tabla, se usa el mtodo Add del DataRowCollection con la nueva fila:
DataRow fila = tabla.NewRow();
fila["CustomerID"] = "NUECO";
fila["CompanyName"] = "Nueva Compaa";
tabla.Rows.Add(fila);

El mtodo Add de la clase DataRowCollection est sobrecargado, de modo que puede crear un DataRow
con el RowState Added, proporcionando una lista de valores para las columnas:
tabla.Rows.Add("NEWCO", "Nueva Compaa");

La clase DataTable ofrece una tercera forma de aadir una nueva fila a la tabla: el mtodo
LoadDataRow. Este mtodo es similar a la sobrecarga del mtodo Add que permite proporcionar valores
para la fila, pero tambin permite controlar el RowState del nuevo DataRow. Para usar este mtodo se
proporciona un array de valores como primer parmetro; los elementos del array se corresponden con las
columnas de la tabla. El segundo parmetro, AcceptChanges, permite controlar el valor de la propiedad
RowState del nuevo DataRow. Si este parmetro es false, como en el siguiente fragmento, el RowState de
la nueva fila es Added, como si se hubiera creado la fila usando consecutivamente los mtodos NewRow y
Add de DataTable.
tabla.LoadDataRow(new object[]{"NUECO", "Nueva Compaa"}, false);

Cuando se envan los cambios a la base de datos mediante el mtodo Update de SqlDataAdapter, el
adaptador examina el RowState de cada DataRow para determinar cmo actualizar la base de datos
modificando una fila existente, aadiendo una nueva o borrando una antigua. Si el segundo parmetro de
LoadDataRow es true, el nuevo DataRow tendr Unchanged como RowState, lo que significa que la fila no
contiene cambios pendientes que el adaptador deba enviar a la base de datos. Esta propiedad se discutir
en detalle ms adelante.

Modificar una fila existente


Hay tres modos de modificar el contenido de una fila mediante programacin. Empecemos por el ms
simple.
Una vez que tiene un objeto DataRow puede establecer el valor de una columna por medio de la
propiedad Item. Anteriormente vio como usar esta propiedad para comprobar el contenido de una columna.
La propiedad es de lectura/escritura, por lo que la puede usar para establecer el valor. El siguiente
fragmento usa el mtodo Find de la coleccin Rows para localizar una cierta fila en la tabla y despus
81

ADO.NET 2.0

cambia el valor de la columna CompanyName. Discutiremos el mtodo Find con mayor detalle ms
adelante.
tabla.PrimaryKey = new DataColumn[]{tabla.Columns["CustomerID"]};
tabla.LoadDataRow(new object[]{"NUECO","Nueva Compaa"});
DataRow fila = tabla.Rows.Find("NUECO");
if(fila == null)
//No se ha encontrado el cliente
else
fila["CompanyName"] = "Valor modificado";

La segunda forma de actualizar una fila es similar, salvo que aade llamadas a los mtodos BeginEdit y
EndEdit de DataRow.
DataRow fila = tabla.Rows.Find("NUECO");
if(fila == null)
//No se ha encontrado el cliente
else{
fila.BeginEdit();
fila["CompanyName"] = "Valor modificado";
fila.EndEdit();
}

El uso de estos dos mtodos permite almacenar en buffer los cambios a la fila. Al llamar a EndEdit se
guardan los cambios en la dila. Si decide que no quiere mantener los cambios, puede llamar a CancelEdit
en lugar de EndEdit para anular los cambios y devolverla al mismo estado que al llamar a BeginEdit.
Hay otra diferencia entre las dos formas de modificar una fila. El DataTable tiene eventos como
RowChanging, RowChanged, ColumnChanging y ColumnChanged que puede usar para examinar los
cambios en una fila o columna. Cuando, o si, se lanzan estos eventos depende de cmo se modifica una fila
llamando o sin llamar a BeginEdit y EndEdit.
En el primer ejemplo el contenido de la fila cambia cada vez que se modifica una columna en la fila. Los
eventos de la clase DataTable se lanzan cada vez que se modifican los contenidos de una columna.
Usando BeginEdit se bloquean los eventos hasta que se llama a EndEdit. Si se llama a CancelEdit en lugar
de EndEdit los cambios se descartan, y como la fila no se actualiza no se lanzan los eventos.
La tercera forma de modificar los contenidos de una fila es usando la propiedad ItemArray. Como la
propiedad Item, esta propiedad se puede usar para recuperar y modificar los contenidos de la fila. La
diferencia entre las propiedades es que la propiedad Item trabaja con una columna cada vez y la propiedad
ItemArray devuelve y acepta un array en el que cada elemento se corresponde con una columna.
La propiedad ItemArray es til si quiere recuperar o modificar valores de varias columnas en una sola
lnea de cdigo. Si quiere modificar un subconjunto de los valores disponibles en una fila, use null para
indicar que no quiere reemplazar el valor de una columna en el DataRow. Por ejemplo, el siguiente
fragmento usa la propiedad ItemArray para dejar sin tocar el valor de CustomerID (primera columna) pero
modificar el valor de CompanyName (segunda columna).
DataRow fila = tabla.Rows.Find("NUECO");
fila.ItemArray = new object[]{null, "Valor cambiado"};

Trabajar con valores nulos en un DataRow


La clase DataRow tiene un mtodo IsNull que se puede usar para comprobar si una columna contiene un
valor null. Este mtodo acepta un nombre de columna, un entero que representa el ndice de la columna o
un objeto DataColumn.
DataRow fila = tabla.Rows.Find("NUECO");
if(fila.IsNull("CompanyName"))
Console.WriteLine("El nombre de compaa es nulo");
else
Console.WriteLine("Nombre de compaa: " + fila["CompanyName"]);

Cuando se quiere asignar un valor nulo a una columna no se puede usar la palabra clave null. El marco
de trabajo .NET incluye la clase DBNull en el espacio de nombres System. Para asignar un valor nulo a una
columna en un DataRow use la propiedad Value de esta clase:
Fila["CompanyName"] = DBNull.Value;

Borrar un DataRow
Borrar un DataRow es ms sencillo que modificarlo. Simplemente llame al mtodo Delete del DataRow.
Sin embargo, esta accin no lo elimina del DataTable; ADO.NET marca la fila como pendiente de borrar.
Por qu no se limita ADO.NET a eliminar la fila de la tabla?.
82

Escuela de Informtica del Ejrcito

Recuerde que los objetos de almacenamiento de datos en el modelo de objetos de ADO.NET acta
como cach de datos, de modo que se pueden recuperar datos de la base de datos, modificarlos en modo
desconectado y enviar ms tarde los cambios pendientes. Cuando se llama al mtodo Delete del DataRow
no se est borrando la fila correspondiente en la base de datos; se est marcando la fila en el DataTable
como un borrado pendiente de modo que posteriormente puede enviar este cambio pendiente a la base de
datos. Si elimina completamente la fila del DataTable al enviar los cambios pendientes a la base de datos
no habr nada que indique a ADO.NET que debe borrar el registro original en la base de datos.
Si realmente quiere eliminar una fila del DataTable en lugar de marcarla como borrado pendiente puede
usar los mtodos Remove y RemoveAt de la clase DataRowCollection. Utilice el mtodo Remove si tiene
una referencia al DataRow que quiere eliminar, y RemoveAt si lo que tiene es el ndice del objeto DataRow
en la coleccin.
Adems, las clases DataSet y DataTable tienen ambas un mtodo Clear que elimina todos los objetos
DataRow del DataSet o DataTable, conservando su estructura.

Propiedad RowState de DataRow


Los objetos DataSet, DataTable y DataRow actan como cach en datos fuera de lnea. Puede consultar
su base de datos y almacenar los resultados en estos objetos. Como ha visto, puede aadir, modificar y
borrar filas. Como estos objetos ADO.NET no estn conectados con la base de datos, los cambios que haga
no afectarn a los contenidos de la base de datos. Desde luego, la modificacin de datos fuera de lnea no
es muy til si no puede enviar estos cambios a la base de datos ms adelante.
ADO.NET permite enviar los cambios a la base de datos, como se explicar en el Captulo 9. Por ahora
revisaremos los aspectos bsicos de cmo se implementa esta funcionalidad. Para almacenar un cambio en
un DataRow de modo que ADO.NET pueda enviar posteriormente el cambio a la base de datos, ADO.NET
debe recordar qu tipo de cambio se ha hecho.
Una forma de actualizar los datos en la base de datos es lanzar consultas de accin tipo INSERT,
UPDATE o DELETE, o procedimientos almacenados con un contenido de este tipo.
La cuestin es que la lgica usada para insertar una fila es diferente a la que se emplea para modificarla
o eliminarla. Por tanto, ADO.NET debe saber qu tipo de cambio se ha hecho en el DataRow para enviar
correctamente el cambio a la base de datos posteriormente.
ADO.NET almacena esta informacin en una propiedad del DataRow llamada RowState, que usa valores
de la enumeracin DataRowState mostrados en la tabla 6.1. Comprobando esta propiedad puede
determinar si la fila se ha cambiado, junto con el tipo de cambio (insercin, modificacin o borrado) que
contiene la fila.
Tabla 6. 1: Enumeracin DataRowState

Constante
Unchanged
Detached
Added
Modified
Deleted

Valor
2
1
4
16
8

Descripcin
La fila no contiene ningn cambio pendiente
La fila no es miembro de ningn DataTable
La fila se ha aadido al DataTable pero no existe an en la base de datos
La fila contiene cambios pendientes
La fila est pendiente de borrar

La lista de posibles valores pueden hacerle creer que la propiedad RowState puede devolver una
combinacin de valores de DataRowState, pero el valor de la propiedad siempre es uno nico de entre la
enumeracin. La tabla 6.2 muestra posibles escenarios y su resultado.
Tabla 6. 2: Ejemplos de RowState

Ejemplo
Fila recin creada sin aadir

RowState
Detached

fila = tabla.NewRow();
fila["ColX"] = "Valor Inicial";

Se aade la fila al DataTable

Added

tabla.Rows.Add(fila);

Fila recin recuperada del DataTable

Unchanged

fila = tabla.Rows(0);

Tras una edicin

Modified

fila.BeginEdit();
fila["ColX"] = "Valor Nuevo;
fila.EndEdit();

Despus de borrar una fila:

Deleted

fila.Delete();

La clase DataRow tiene un mtodo AcceptChanges que puede usar para aceptar los cambios pendientes
almacenados. La clase SqlDataAdapter llama a este mtodo implcitamente despus de enviar con xito los
83

ADO.NET 2.0

cambios a la base de datos. Este mtodo purga todos los cambios pendientes en un DataRow e
implcitamente configura el RowState. Al llamar a AcceptChanges los RowState Added o Modified se
cambian a Unchanged. Si el RowState es Deleted retira el DataRow de la coleccin de filas del DataTable y
cambia su RowState a Detached.
La clase DataRow tambin tiene un mtodo RejectChanges que puede usar para deshacer los cambios
almacenados en el DataRow. El mtodo RejectChanges restablece los valores actuales del DataRow en
base a sus valores originales, purgando los cambios pendientes que contena previamente. Si el RowState
era Modified o Deleted se devuelve a Unchanged. Si DataRow era Added se retira el DataRow de la
coleccin de filas y su RowState pasa a ser Detached.
En ADO.NET 2.0 hay mtodos nuevos que permite controlar el RowState de los DataRow: SetAdded y
SetModified. El mtodo SetAdded cambia el RowState a Added, y SetModified lo cambia a Modified. Sin
embargo, estos mtodos solo estn disponibles para DataRow cuyo RowState sea Unchanged. Si se llama
sobre un DataRow con un RowState diferente, se obtiene una InvalidOperationException.

Examinar los cambios pendientes en un DataRow


Una vez localizada, gracias a la propiedad RowState, una fila modificada, puede usar la propiedad Item
para examinar los contenidos de las columnas, tanto los actuales como los anteriores a la modificacin.
La propiedad Item acepta un segundo parmetro opcional de la enumeracin DataRowVersion, descrita
en la tabla 6.3.
Tabla 6. 3: Enumeracin DataRowVersion

Constante
Current
Original
Proposed

Valor
512
256
1024

Default

1536

Descripcin
Valor actual almacenado en la columna
Valor original que contena la columna
Valor que se propone para una columna. Solo es vlido cuando se est editando una fila
usando BeginEdit
Accin predeterminada

De forma general, el DataRow tiene dos versiones la que est almacenada actualmente y la que se
almacen originalmente. Normalmente necesitar ambos conjuntos de informacin para localizar la fila.
Despus de actualizar una fila puede comprobar el contenido actual de una columna y el contenido original.
El siguiente cdigo cambia el contenido de la columna CompanyName de un DataRow y despus recupera
tanto el valor actual (nuevo) como el original. Observe que el cdigo pasa true en la llamada a
LoadDataRow, de modo que la fila se aade al DataTable son modificar. No hay estado original para filas
marcadas como inserciones pendientes.
DataTable tabla = new DataTable("Clientes");
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("CompanyName", typeof(string));
tabla.PrimaryKey = new DataColumn[]{tabla.Columns["CustomerID"]};
tabla.LoadDataRow(new object[]{"NUECO", "Valor Inicial"}, true);
DataRow fila = tabla.Rows.Find("NUECO");
fila["CompanyName"] = "Valor nuevo";
Console.WriteLine("DataRowVersion.Current = {0}",
fila["CompanyName", DataRowVersion.Current]);
Console.WriteLine("DataRowVersion.Original = {0}",
fila["CompanyName", DataRowVersion.Original]);

Cuando se edita una fila usando BeginEdit y EndEdit es posible examinar otra versin de la fila, la
versin propuesta. Una vez que se llama a EndEdit los cambios se almacenan en la versin actual de la
fila. Antes, los cambios sern temporales, puesto que se pueden cancelar llamando a CancelEdit.
Mientras se est editando una fila es posible comprobar el valor propuesto de una columna utilizando el
valor Proposed de la enumeracin DataRowVersion. Usando la constante Current se obtendr el valor
anterior a la llamada a BeginEdit que no tiene por qu ser el valor original.
La tabla 6.4 muestra los valores que devuelve una columna segn el valor de la enumeracin
DataRowVersion que se especifique. Las entradas marcadas [Excepcin] representan escenarios en los
que el uso de la opcin indicada provoca una excepcin.
Tabla 6. 4: Valores de varias versiones de una columna en un DataRow

Ejemplo
Fila recin creada pero no aadida

Current
[Exception]

Original
[Excepcin]

Proposed
ValInicial

Default
ValInicial

ValInicial

[Excepcin]

[Excepcin]

ValInicial

fila = tabla.NewRow();
fila[Columna] = ValInicial;

Nueva fila aadida a un DataTable


tabla.Rows.Add(fila);

84

Escuela de Informtica del Ejrcito

Tabla 6. 4: Valores de varias versiones de una columna en un DataRow

Ejemplo
Fila recin recuperada

Current

Original

Proposed

Default

ValRecuperado

ValRecuperado

[Excepcin]

ValRecuperado

ValRecuperado

ValRecuperado

ValorNuevo1

ValorNuevo1

ValorNuevo1

ValRecuperado

[Excepcin]

ValorNuevo1

ValorNuevo1

ValRecuperado

ValorNuevo2

ValorNuevo2

ValorNuevo2

ValRecuperado

[Excepcin]

ValorNuevo2

ValorNuevo2

ValRecuperado

[Excepcin]

ValorNuevo2

[Excepcin]

ValRecuperado

[Excepcin]

[Excepcin]

fila = tabla.Rows[0];

Durante la primera edicin


fila.BeginEdit();
fila[Columna] = ValorNuevo1;

Despus de la primera edicin


fila.EndEdit();

Durante la segunda edicin


fila.BeginEdit();
fila[Columna] = ValorNuevo2;

Despus de la segunda edicin


fila.EndEdit();

Despus de cancelar la edicin


fila.BeginEdit();
fila[Columna] = ValCancelar;
fila.CancelEdit();

Despus de borrar una fila


fila.Delete()

Una edicin con xito cambia el valor actual, pero no afecta al valor original. Al llamar a CancelEdit se
restablece el valor actual al anterior a llamar a BeginEdit, que no necesariamente es el mismo que el
original.
Despus de borrar una fila se obtiene una excepcin si se intenta examinar sus valores actuales, pero
an se puede acceder a sus valores originales.
Hemos discutido tres de los cuatro valores de la enumeracin DataRowVersion. Ahora vamos a ver su
valor Default. Al usar este valor con la propiedad Item no se obtiene el valor predeterminado de la columnas,
esto es tarea de la propiedad DefaultValue. El valor Default de la enumeracin representa el valor
predeterminado del parmetro DataRowVersion de la propiedad Item del DataRow.
Al principio del Captulo 1 se mencion que la propiedad Item devuelve el valor actual de una columna de
la fila. La precisin de esta afirmacin depende de la definicin de actual.
Si no se est en el proceso de editar una fila, llamar a Item omitiendo el parmetro opcional, o
especificando Default, es equivalente a usar Current. Pero si est en el proceso de editar una fila y se omite
el parmetro opcional o se especifica Default se obtiene la versin propuesta para la columna.

5.2.7

Opciones de serializacin y remoting de DataSet

ADO.NET 2.0 incluye nuevas opciones que le pueden dar mejor control y mayor rendimiento en un
servicio Web o aplicacin multicapa que serialice DataSet o utilice remoting sobre ellos.

Serializacin y remoting con DataTable


La serializacin y remoting de DataTable es ADO.NET 2.0 son tan sencillos como la serializacin de
DataSet en ADO.NET 1.x. Muchas de las caractersticas XML que antes estaba asociadas solamente con el
DataSet (ReadXml, ReadXmlSchema y WriteXmlSchema) ahora estn disponibles tambin con DataTable.
El nico requisito es que se debe configurar la propiedad TableName del DataTable antes de usar estas
caractersticas.

DataColumn.DateTimeMode
En la versin 2.0 del marco de trabajo .NET se ha mejorado el tipo de datos DateTime de modo que se
pueda indicar si el valor DateTime representa el tiempo local o el tiempo universal coordinado (UTC)
comprobando su propiedad Kind. Esta propiedad devuelve un valor de la enumeracin DateTimeKind:
Local, Utc o Unspecified. La propiedad DateTimeMode de la clase DataColumn de ADO.NET controla cmo
se serializan los valores DateTime en esta DataColumn.
DateTime horaLocal = DateTime.Now;
DateTime horaUtc = TimeZone.CurrentTimeZone.ToUniversalTime(horaLocal);
Console.WriteLine("Kind: {0,-5} Fecha y hora: {1}", horaLocal.Kind, horaLocal);
Console.WriteLine("Kind: {0,-5} Fecha y hora: {1}", horaUtc.Kind, horaUtc);

Esta funcionalidad puede ser valiosa si est creando una aplicacin de gestin de informacin personal
(PIM). Puede almacenar todas las citas en el almacenamiento central en formato UTC y mostrarlas en la
aplicacin en base a la zona horaria local del usuario.

85

ADO.NET 2.0

Para el objeto de esta discusin asumiremos que la aplicacin PIM usa un cliente de formulario Windows
para interactuar con un servicio Web. La mquina en la que corre el servicio Web se encuentra en Madrid, y
el cliente en Las Palmas. Si queremos que el personal de Las Palmas llegue a las citas a su hora y no una
hora antes, tenemos que tener en cuenta la diferencia horaria.
De forma predeterminada, cuando se serializa un valor de un DataColumn de tipo DateTime en un
DataSet ADO.NET escribe el valor DateTime e incluye la informacin de zona horaria en forma de
desplazamiento UTC. Si la aplicacin cliente recibe esta informacin, traducir su valor automticamente,
en base a la configuracin Windows para su zona horaria local. El trabajo real lo ejecuta el mtodo Parse de
la clase DateTime, que puede gestionar el formato de fecha y hora incluido el desplazamiento UTC. La hora
en el cliente ser la correcta, pero su propiedad Kind ser Unspecified.
En ADO.NET 2.0 la clase DataSet tambin ha aadido mejoras para darle ms control sobre la forma en
que se serializan y deserializan los valores DateTime con la nueva propiedad DateTimeMode de la clase
DataColumn. Para comprender mejor el efecto que puede tener esta propiedad en su aplicacin, veamos
cmo se puede usar en nuestra terica aplicacin PIM.
La propiedad DateTimeMode afecta a la propiedad Kind de los valores DateTime generados al
deserializar un DataSet. Como discutimos anteriormente, cuando la aplicacin recibe un DataSet de un
servicio Web la propiedad Kind se estableca en DateTimeKind.Unspecified para el valor DateTime
generado. Este es el comportamiento esperado cuando la propiedad DateTimeMode del DataColumn tiene
el valor UnspecifiedLocal, el predeterminado. Si quiere que los valores DateTime en el DataSet
deserializado tengan DateTimeKind.Local como propiedad Kind, asigne el valor DataSetDateTime.Local a la
propiedad DateTimeMode del objeto DataColumn. Si el valor asignado a DateTimeMode es
DataSetDateTime.Utc los valores DateTime en el DataSet deserializado tendrn DateTimeKind.Utc como
propiedad Kind.
Tambin puede usar la propiedad DateTimeMode para controlar los datos que se serializan junto con el
DataSet. Cuando el valor de DateTimeMode es UnspecifiedLocal, predeterminado, se escribe el valor de
DateTime junto con la informacin de desplazamiento UTC de la zona horaria actual (dos horas por delante
de UTC):
<DateTimeCol>2007-12-09T10:00:00.00+02:00</DateTimeCol>

Si la propiedad DateTimeMode tiene el valor Unspecified se omite el desplazamiento. Cuando la


propiedad DateTimeMode tiene el valor Local se escribe la informacin de desplazamiento, pero los
DateTime cuya propiedad Kind es Utc se traducen a la zona horaria local antes de la serializacin.
Si se asigna Utc a la propiedad DateTimeMode ADO.NET serializa el valor DateTime con el siguiente
formato UTC.
<DateTimeCol>2007-12-09T10:00:00.00B</DateTimeCol>

Los DateTime cuya propiedad Kind es Local se traducen a UTC. Los DateTime cuya propiedad Kind es
Unspecified no se traduce, se asume que ya son UTC.
Volvamos a nuestra terica aplicacin PIM. Si almacena las fechas y horas de las citas en la base de
datos usando la informacin de zona horaria de la ubicacin del servidor, la aplicacin que accede a los
datos necesita tener en cuenta la esta informacin de zona horaria para sus consultas y su interface de
usuario?. Qu sucede si necesita cambiar la ubicacin del servidor o si necesita replicar datos a un
servidor en otra zona?.
Muchos desarrolladores prefieren almacenar los datos en su base de datos usando valores UTC.
Configurar la propiedad DateTimeMode de sus DataColumn de tipo DateTime con el valor Utc puede
ayudar. Suponga que crea una cita en la parte cliente de su aplicacin PIM usando la hora local, Canarias
por ejemplo. Cuando se enve el DataSet al servicio Web el valor DateTime se traducir a UTC sin tener
que hacerlo de forma manual, antes de enviar el nuevo valor a la base de datos.

DataSet.SchemaSerializationMode
Visual Studio facilita la creacin de servicios Web que devuelvan resultados de consultas usando
DataSet fuertemente tipados. Discutiremos estos DataSet ms adelante. Por ahora el punto clave es
comprender que un DataSet fuertemente tipado deriva de un DataSet bsico e incluye informacin de
esquema que describe sus tablas, columnas y relaciones.
Los mtodos de un servicio Web reciben las peticiones y envan sus respuestas utilizando SOAP. Esto
significa que la respuesta, aunque para nuestra aplicacin sea un DataSet tendr forma XML. Si se examina
el XML que representa un DataSet se observa que la amplia mayora de la informacin que contiene es
informacin de esquema, y que los verdaderos datos representan una parte muy pequea del mensaje.

86

Escuela de Informtica del Ejrcito

Una de las caractersticas ms potentes de los servicios Web es que sus mtodos son auto-descriptivos.
Un cliente puede examinar el archivo .wsdl del servicio Web para comprender los parmetros y el valor de
retorno de sus mtodos. En el caso de que el mtodo devuelva un DataSet, estos metadatos incluyen su
esquema. Como resultado, muchos clientes ya tienen el esquema del DataSet antes de llamar al mtodo.
En otras palabras, para muchas aplicaciones cliente la informacin de esquema que incluye el mensaje de
respuesta es redundante.
ADO.NET permite omitir esta informacin de esquema cuando se serializan DataSet; sencillamente
asigne el valor SchemaSerializationMode.ExcludeSchema a la propiedad SchemaSerializationMode en el
mtodo Web. De esta forma se reduce drsticamente el tamao del mensaje que devuelve el mtodo.
La exclusin del esquema puede marcar una importante diferencia en el rendimiento de la aplicacin. No
solo se serializan menos datos con el DataSet, sino que adems se omite el paso de escribir esta
informacin de esquema en el flujo de serializacin y leerla del mismo.
Esta caracterstica est diseada para DataSet fuertemente tipados y no para DataSet sin tipo. La lgica
para determinar si incluir el esquema est integrada en los DataSet fuertemente tipados, no en la clase
DataSet. Si crea un DataSet simple e intenta modificar su propiedad SchemaSerializationMode obtendr un
mensaje que le indicar que slo se puede configurar esta propiedad cuando se sustituye en clases
derivadas de DataSet.
El DataSet fuertemente tipado sustituye la propiedad SchemaSerializationMode e incluye cdigo de
serializacin modificado que comprueba la propiedad antes de determinar si serializar la informacin de
esquema. La propiedad est disponible en la clase DataSet por que no hay ninguna clase o interface comn
entre la clase DataSet y los DataSet fuertemente tipados.

DataSet.RemotingFormat
Como ya hemos citado, los DataSet se serializan como XML. Este es un formato flexible y fcil de
comprender porque es auto-descriptivo. Sin embargo, XML no siempre es el mejor formato a utilizar. Por
ejemplo, los nombres de campo aparecen repetidos como elementos para cada fila de datos; esta
informacin es redundante. El formato XML es adecuado para DataSet pequeos; en DataSet grandes esta
redundancia puede constituir una seria penalizacin de rendimiento.
ADO.NET 2.0 permite controlar el formato utilizado para enviar un DataSet. De forma predeterminada se
sigue utilizando XML, pero se puede asignar el valor SerializationFormat.Binary a la propiedad
RemoteFormat para usar un formato binario. Qu formato es ms conveniente?; depende de con cuntos
datos trabaje. Para DataSet pequeos, con unos pocos cientos de filas, el formato XML ahorra espacio y
proporciona mejor rendimiento. Para DataSet muy grandes, es preferible el formato binario.

5.3 Trabajar con objetos DataSet en Visual Studio


Ahora sabe mucho sobre la estructura de los objetos DataSet y cmo crearlos en el cdigo. Pero hay
mucho cdigo que escribir. Vamos a ver algunas caractersticas del entorno de desarrollo Visual Studio que
puede usar para crear objetos DataSet con mucho menos esfuerzo.

5.3.1

Crear DataSet fuertemente tipados

Si ha usado versiones anteriores de Visual Studio y est intentando crear un DataSet fuertemente tipado
en Visual Studio 2005 puede que inicialmente est un poco confuso. Por ejemplo, ha desaparecido la
opcin Generar conjunto de datos en el men principal y mens de contexto. La funcionalidad sigue
existiendo, pero ha cambiado de sitio y de nombre y se ha reconstruido. Esta capacidad se tratar al hablar
de DataSet fuertemente tipados.

5.3.2

Crear un DataSet sin tipo

An puede usar Visual Studio 2005 para crear DataSet sin tipo
simples usando dilogos sencillos en tiempo de diseo. Visual
Studio traducir su entrada en cdigo ADO.NET y lo almacenar en
archivos del diseador.
Para aadir un DataSet sin tipo en un diseador, como un
formulario Windows, arrastre el elemento DataSet desde la caja de
herramientas a la superficie del diseador; se abrir el dilogo
Agregar conjunto de datos. Seleccione Conjunto de datos sin tipo y
pulse Aceptar. Ver el nuevo DataSet en la bandeja de
componentes del diseador.
Seleccione el DataSet en la bandeja de componentes y ver sus
propiedades en la ventana de propiedades. Para aadir objetos
DataTable al DataSet seleccione la propiedad Tables y pulse el
botn elipsis; se abrir el Editor de la coleccin de tablas. La figura

Fig.6. 1: Editor de la coleccin de tablas

87

ADO.NET 2.0

6.1 muestra este editor cuando se usa para modificar objetos DataTable dentro de un DataSet.
Ver el mismo editor cuando aada objetos DataColumn al nuevo DataTable. Para agregar objetos
DataColumn seleccione el DataTable correspondiente en el Editor de colecciones del DataSet, seleccione la
coleccin Columns en la lista de la derecha y pulse el botn elipsis.
Una vez aadidos objetos DataColumn puede especificar una clave principal para el DataTable.
Seleccione el DataTable en el Editor de coleccin de tabla y ver la propiedad PrimaryKey en la ventana de
la derecha. Seleccione esta propiedad y pulse la flecha a la derecha; ver una lista de las columnas del
DataTable. Seleccione las columnas que quiere usar para la clave principal y cierre la lista pulsando en
cualquier parte del editor de la coleccin.
Tambin puede aadir elementos a la coleccin Constraints del objeto DataTable. Pulse sobre la
propiedad Constraints del DataTable en el Editor de la coleccin de tablas y pulse el botn elipsis. Se abrir
un nuevo editor de colecciones. Si ha creado una clave principal para su tabla ver que este editor ya
contiene un elemento.
Este nuevo editor de colecciones le permitir aadir restricciones UNIQUE o FOREIGN KEY; al pulsar el
botn Agregar se le mostrarn estas dos opciones. Para crear limitaciones nicas simplemente seleccione
el objeto DataColumn que quiere incluir en la restriccin. Tambin puede especificar un nombre para la
restriccin e indicar si debe ser la clave principal de la tabla.
Para crear una restriccin de clave externa proporcione un nombre la restriccin y seleccione el
DataTable padre. A continuacin seleccione los DataColumn padre e hijo para la nueva restriccin. Tambin
permite listas desplegables que le permiten especificar valores para otras propiedades de esta restriccin
UpdateRule, DeleteRule y CascadeRule. Cuando se crea una nueva ForeignKeyConstraint estas
propiedades toman los valores predeterminados de ADO.NET, Cascade para los dos primeros y None para
el ltimo. Estas propiedades se explorarn con mayor detalle al tratar los DataRelation.

5.4 Referencia de clases


5.4.1

Propiedades de la clase DataSet

La tabla 6.5 muestra las propiedades de uso habitual de la clase DataSet.


Tabla 6. 5: Propiedades de la clase DataSet

Propiedad
CaseSensitive

Tipo
Boolean

DataSetName
DefaultViewManager
DesignMode
EnforceConstraints
ExtendedProperties
HasErrors
IsInitialized
Locale

String
DataViewManager
Boolean
Boolean
PropertyCollection
Boolean
Boolean
CultureInfo

Namespace

String

Prefix

String

Relations
RemotingFormat
SchemaSerializationMode

DataRelationCollection
SerializationFormat
SchemaSerializationMode

Tables

DataTableCollection

Descripcin
Controla si las comparaciones de cadena diferencian
maysculas de minsculas
Devuelve el nombre del DataSet
Devuelve el DataViewManager predeterminado del DataSet
Indica si el DataSet est en modo de diseo
Controla si el DataSet impone las limitaciones que contiene
Contiene una coleccin de propiedades y valores dinmicos.
Indica si el DataSet contiene errores
Indica si el DataSet se ha inicializado
Controla la configuracin local que usa el DataSet para
comparar cadenas.
Contiene el espacio de nombres que usar ADO.NET cuando
escriba los contenidos del DataSet en XML o cuando cargue
datos XML en el DataSet.
Contiene el prefijo de espacio de nombres que usar
ADO.NET cuando escriba los contenidos del DataSet en XML
o cuando cargue datos XML en el DataSet.
Contiene la coleccin de objetos DataRelation del DataSet.
Controla el formato de serializacin que utiliza el DataSet.
Controla si se incluye el esquema cuando se serializa el
DataSet.
Contiene la coleccin de objetos DataTable del DataSet.

CaseSensitive
La propiedad CaseSensitive de los controles DataSet controla si las comparaciones de cadenas dentro
del DataSet diferencian maysculas y minsculas. El valor predeterminado es False.
La propiedad DataTable tambin expone esta propiedad. Al cambiar el valor de esta propiedad en el
DataSet se cambia su valor para todos los objetos DataTable cuya propiedad no se haya configurado.

DataSetName
La propiedad DataSetName contiene el nombre del DataSet. Puede especificar un valor para esta
propiedad en el constructor del objeto DataSet. Si no especifica un valor en el constructor esta propiedad
tomar el valor NewDataSet.
88

Escuela de Informtica del Ejrcito

Si escribe el contenido del DataSet en un documento XML esta propiedad controla el nombre del nodo
raz del documento. La propiedad DataSetName tambin controla el nombre de la clase que se genera si se
utiliza la utilidad XSD.exe para generar un archivo de clase en base al contenido de un archivo de esquema
XML.

DefaultViewManager
La propiedad DefaultViewManager devuelve un objeto DefaultViewManager que puede usar para
controlar el DataView predeterminado de cada DataTable del DataSet. Los DataView se describirn ms
adelante.

DesignMode
La propiedad DesignMode de la clase DataSet contiene un valor boleando que indica si el objeto est en
modo de diseo. Esta propiedad puede ser til cuando se escribe cdigo en un control de usuario. Si el
DataSet se est usando en tiempo de diseo dentro de un componente, DesignMode debe devolver true, en
caso contrario debe devolver false.
El objeto DataTable tambin expone una propiedad DesignMode; en ambos casos la propiedad es de
solo lectura.

EnforceConstraints
Puede usar la propiedad EnforceConstraints para controlar si el DataSet impone las limitaciones que
contiene. De forma predeterminada su valor es true; si quiere desactivar las limitaciones temporalmente
cambie su valor a false.
Si cuando se cambie el valor de esta propiedad a true el contenido del DataSet viola alguna de las
limitaciones se lanza una ConstraintException.

ExtendedProperties
Puede usar la propiedad ExtendedProperties de la clase DataSet para almacenar informacin variada.
La propiedad devuelve un objeto PropertyCollection, diseado para almacenar objetos variados. Aunque
dentro de la coleccin ExtendedProperties del DataSet se pueden almacenar objetos, lo ms probable es
que se use para almacenar cadenas. Cuando se guarda el contenido de un esquema de objeto DataSet en
un archivo o flujo ADO.NET escribe el contenido de ExtendedProperties como cadenas.
Las clases DataTable, DataColumn, DataRelation y Constraint tambin exponen una propiedad
ExtendedProperties.
El siguiente cdigo muestra cmo utilizar la propiedad ExtendedProperties de un objeto DataSet. Como
el cdigo utiliza un IDictionaryEnumerator necesita una referencia a System.Collections.
//Aade propiedades extendidas
ds.ExtendedProperties.Add("Prop1", "Valor1");
ds.ExtendedProperties.Add("Prop2", "Valor2");
ds.ExtendedProperties.Add("Prop3", "Valor3");
//Recupera el valor de una propiedad extendida
Console.WriteLine(ds.ExtendedProperties["Prop2"]);
//Recupera y enumera todas las propiedades extendidas
IDictionaryEnumerator enumerador;
enumerador = ds.ExtendedProperties.GetEnumerator();
while(enumerador.MoveNext())
Console.WriteLine("{0} -> {1}", enumerador.Key, enumerador.Value);

Algunos SGBD como SQL Server permiten propiedades extendidas en tablas y columnas. No hay una
forma directa de recuperar estas propiedades por medio del API de ADO.NET. Si necesita recuperar esta
informacin deber escribir una consulta especifica del SGBD y aadir estas propiedades a la coleccin
ExtendedProperties.

HasErrors
La propiedad HasErrors devuelve un valor booleano que indica si algn objeto DataRow contenido en el
DataSet contiene errores. Si est enviando lotes de cambios a su base de datos y ha configurado la
propiedad ContinueOnUpdateError de sus objetos SqlDataAdapter como true debe comprobar la propiedad
HasErrors de su DataSet despus de enviar cambios para determinar si alguno de los intentos de
actualizacin ha fallado.
Las clases DataTable y DataRow tambin exponen una propiedad HasErrors.

IsInitialized
La clase DataSet soporta la propiedad IsInitialized como parte de su implementacin del interface
ISupportInitializeNotification. Este interface se trata con ms detalle al ver los mtodos BeginEdit y EndEdit.
89

ADO.NET 2.0

Locale
Diferentes lenguajes y culturas utilizan reglas diferentes cuando comparan los contenidos de cadenas.
De forma predeterminada el DataSet usar la informacin de cultura actual del sistema para comparar
cadenas. Puede cambiar este comportamiento configurando la propiedad Locale del DataSet. Esta
propiedad acepta un objeto CultureInfo, que reside en el espacio de nombres System.Globalization.
Al igual que la propiedad CaseSensitive, la propiedad Locale tambin existe en la clase DataTable; al
configurar la propiedad Locale de un DataSet se cambia la propiedad Locale de todos los DataTable que
contiene el DataSet cuya propiedad no se haya configurado.
El siguiente fragmento muestra como configurar la propiedad Locale del DataSet para usar las reglas de
Espaol de Argentina:
DataSet ds = new DataSet();
ds.Locale = new System.Globalization.CultureInfo("en-AR");
Console.WriteLine(ds.Locale.DisplayName);

Namespace y Prefix
Puede usar las propiedades Namespace y Prefix de la clase DataSet para especificar un espacio de
nombre y prefijo XML para el DataSet. ADO.NET usar estos valores cuando escriba los contenidos de su
conjunto de datos en forma XML y cuando cargue documentos XML en su DataSet.
Las clases DataTable y DataColumn tambin exponen estas propiedades.

Relations
La propiedad Relations devuelve un objeto DataRelationCollection que contiene los objetos DataRelation
que residen en el DataSet. Puede utilizar esta propiedad para examinar los objetos DataRelation existentes
as como para aadir, modificar o eliminar.
La clase DataRelation se ver en el prximo captulo.

RemotingFormat
Como ya se ha indicado, ADO.NET 2.0 le da la opcin de elegir un formato de serializacin binario. De
forma predeterminada se usa el formato XML. Sin embargo, si se cambia la propiedad RemotingFormat a
SerializationFormat.Binary se utilizar el formato binario.
La clase DataTable tambin expone una propiedad RemotingFormat.

SchemaSerializationMode
Como ya se indic la propiedad SchemaSerializationMode controla si se incluye el esquema del DataSet
cuando se serializa. Esta propiedad est pensada para DataSet derivados que la sustituyen, en otras
palabras, DataSet fuertemente tipados. Si intenta modificar esta propiedad en un DataSet genrico obtendr
una InvalidOperationException.
De forma predeterminada, se incluye el esquema con los datos serializados. Al excluir el esquema,
configurando esta propiedad como SchemaSerializationMode.ExcludeSchema, puede mejorar mucho el
rendimiento de aplicaciones que acceden a DataSet fuertemente tipados procedentes de servicios Web.

Tables
Puede usar la propiedad Tables para examinar objetos DataTable existentes, as como para aadir,
modificar o eliminar estos objetos. Esta propiedad devuelve un objeto DataTableCollection, que contiene los
objetos DataTable que residen en el DataSet.
Puede acceder a un DataTable por medio de la propiedad Tables usando la propiedad TableName de la
tabla deseada o su ndice dentro de la coleccin. El acceso por medio de ndices proporciona un mejor
rendimiento.

5.4.2

Mtodos de la clase DataSet

La tabla 6.6 muestra los mtodos de uso habitual de la clase DataSet.


Tabla 6. 6: Mtodos de la clase DataSet

Mtodo
AcceptChanges
BeginInit
Clear
Clone
Copy
CreateDataReader

90

Descripcin
Acepta todos los cambios pendientes en el DataSet
Los usan los diseadores de Visual Studio antes de aadir informacin de esquema al
DataSet.
Elimina todos los objetos DataRow del DataSet.
Crea un nuevo objeto DataSet con el mismo esquema pero sin ningn DataRow
Crea un nuevo objeto DataSet con el mismo esquema y los mismos objetos DataRow
Devuelve un DateTableReader que contiene los datos disponibles en el o los DataTable
especificados en la llamada.

Escuela de Informtica del Ejrcito

Tabla 6. 6: Mtodos de la clase DataSet

Mtodo
EndInit
GetChanges
GetXml
GetXmlSchema
HasChanges
InferXmlSchema
Load
Merge
ReadXml
ReadXmlSchema
RejectChanges
Reset
WriteXml
WriteXmlSchema

Descripcin
Lo usan los diseadores de Visual Studio despus de aadir informacin de esquema al
DataSet.
Devuelve un nuevo DataSet con la misma estructura y que contiene todas las filas modificadas
en el original.
Devuelve los contenidos del DataSet como cadena XML.
Devuelve el esquema del DataSet como cadena XML.
Devuelve un valor booleano que indica si algn DataRow del DataSet contiene cambios
pendientes.
Carga informacin de esquema a partir de un esquema XML y permite proporcionar una lista
de espacios de nombres cuyos elementos se quieren excluir del esquema del DataSet.
Carga datos desde un DataReader al DataTable.
Fusiona datos desde otro objeto DataSet o DataTable o array de objetos DataRow en el
DataSet existente.
Lee datos XML desde un archivo, Stream, TextReader o XmlReader al DataSet
Lee informacin de esquema XML desde un archivo, Stream, TextReader o XmlReader al
DataSet
Rechaza todos los cambios pendientes en el DataSet
Restablece el DataSet a su estado original, sin inicializar.
Escribe el contenido del DataSet como XML en un archivo, Stream, TextWriter o XmlWriter
Escribe el esquema del DataSet como XML en un archivo, Stream, TextWriter o XmlWriter

AcceptChanges y RejectChanges
Puede usar los mtodos AcceptChanges y RejectChanges para aceptar o rechazar todos los cambios
pendientes en el DataSet.
Cuando se modifican los contenidos de un objeto DataRow ADO.NET marca el objeto con un cambio
pendiente y configura la propiedad RowState del objeto DataRow con el valor adecuado Added, Modified
o Deleted. ADO.NET mantiene tanto el valor original como el actual de los contenidos del DataRow.
Si llama al mtodo AcceptChanges de su DataSet ADO.NET acepta todos los cambios pendientes
almacenados en los objetos DataRow del DataSet. Todas las filas cuyo RowState sea Added o Modified
cambian esta propiedad a Unchanged. De esta forma tambin cambian los valores de actual a original. Los
objetos DataRow marcados como Deleted se eliminan del DataSet.
Cuando el objeto SqlDataAdapter enva con xito los cambios pendientes almacenados en un objeto
DataRow llama implcitamente al mtodo AcceptChanges del DataRow.
Al llamar al mtodo RejectChanges del DataSet se cancelan todos los cambios pendientes en el
DataSet. Los objetos DataRow marcados como Added se eliminan; el resto de objetos modificados vuelven
a su estado anterior.
Los objetos DataTable y DataRow tambin exponen mtodos AcceptChanges y RejectChanges.

BeginInit y EndInit
Las clases DataSet y DataTable soportan los mtodos BeginInit y EndInit como parte de su
implementacin del interface ISupportInitializeNotification.
Cuando se aaden DataTable, DataColumn y Constraint a un DataSet es posible que sea necesario
aadir los elementos en un orden concreto para poder construir el DataSet. Por ejemplo, si aade un
DataColumn basado en expresin debe aadir antes los DataColumn que forman parte de la expresin.
Las clases DataSet y DataTable implementan el interface ISupportInitializeNotification de modo que los
diseadores como Visual Studio pueden construir los objetos sin preocuparse del orden de las operaciones.
Para clase que soportan este interface, o ISupportInitialize, que representa un subconjunto de las
funcionalidades disponibles por medio de ISupportInitializeNotification, Visual Studio puede colocar el
cdigo que construye los objetos entre llamadas a BeginInit y EndInit. Cuando el cdigo llama al mtodo
BeginInit del DataSet o DataTable se retrasa la evaluacin de los elementos que lo componen hasta la
llamada a EndInit.
El interface ISupportInitializeNotification proporciona dos caractersticas ms la propiedad IsInitialized y
el evento Initialized. La propiedad IsInitialized indica si el objeto se ha inicializado; devuelve de forma
predeterminada true. Una vez que se llama a BeginInit la propiedad IsInitialized devuelve false hasta que se
llame a EndInit. Al llamar al mtodo EndInit se lanza el evento Initialized del objeto.

Clear
Puede usar el mtodo Clear de la clase DataSet para eliminar todos los objetos DataRow que contiene.
Este mtodo es ms rpido que liberar el DataSet y crear uno nuevo con la misma estructura.
La clase DataTable tambin expone un mtodo Clear.
91

ADO.NET 2.0

Clone y Copy
Puede usar el mtodo Copy para crear un nuevo DataSet que contenga la misma estructura y el mismo
conjunto de filas que el original. Si quiere crear un DataSet con la misma estructura pero sin filas utilice
Clone.
La clase DataTable tambin expone los mtodos Clone y Copy.

CreateDataReader
La clase DataSet expone el mtodo DataReader para los desarrolladores que quieren acceder al
contenido del DataSet usando el interface de DataReader. Este mtodo devuelve una instancia de la clase
DataTableReader, que se comporta como un SqlDataReader. El siguiente fragmento carga un DataSet con
el resultado de dos consultas y usa el mtodo CreateDataReader para mostrar su contenido:
string cadcon=@"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta="SELECT OrderID, CustomerID FROM Orders WHERE CustomerID = Anton; "
+ "SELECT CustomerID, CompanyName FROM Customers WHERE CustomerID = Anton;";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataSet ds = new DataSet();
using(DataTableReader lector = ds.CreateDataReader()){
do{
while(lector.Read())
Console.WriteLine("{0} {1}", lector[0], lector[1]);
Console.WriteLine();
}while(lector.NextResult());
}

El mtodo CreateDataReader est sobrecargado. Puede pasar al mtodo un array de DataTable para
seleccionar qu tablas estarn disponibles por medio del lector, as como su orden.
La clase DataTable tambin expone un mtodo CreateDataReader.

GetChanges
El mtodo GetChanges devuelve un DataSet con la misma estructura que el original y que incluye todas
las filas que contienen cambios pendientes. Se ver con mayor detalles al tratar la actualizacin de la base
de datos.
El nuevo DataSet puede contener tambin algunas filas sin cambios necesarias para cumplir las reglas
de integridad referencial.
La clase DataTable tambin expone un mtodo GetChanges.

GetXml y GetXmlSchema
Puede usar el mtodo GetXml para recuperar el contenido de su DataSet, incluyendo su esquema, y
ponerlo en una cadena en formato XML. Si quiere recuperar solo la informacin de esquema utilice el
mtodo GetXmlSchema.
Estos mtodos se discutirn con mayor detalle en el Captulo 11.

HasChanges
El mtodo HasChanges devuelve un valor boolean que indica si el DataSet tiene objetos DataRow que
contienen cambios pendientes. Este mtodo permite averiguar si hay cambios que enviar a la base de datos
antes de establecer la conexin para hacerlo.

Load
En ADO.NET 2.0 la clase DataSet expone un mtodo Load que puede usar para cargar datos desde un
SqlDataReader al DataSet. En ADO.NET 1.0 y 1.1 las nica forma de cargar los resultados de una consulta
en un DataSet, sin aadir los datos fila a fila, era usar el mtodo Fill de un DataAdapter.
Este es un ejemplo rpido que ejecuta un lote de consultas y almacena los resultados en un nuevo
DataSet. El primer parmetro del mtodo Load acepta un SqlDataReader o cualquier clase que implemente
IDataReader. El segundo parmetro controla el RowState de las filas almacenadas en el DataSet y se
corresponde con la propiedad FillLoadOption de SqlDataAdapter. En el ejemplo el tercer parmetro contiene
un array de cadenas que controla los nombres de los DataTable utilizados o creados, similar a la
TableMappingsCollection de SqlDataAdapter.

92

Escuela de Informtica del Ejrcito

DataSet ds = new DataSet();


//Conecta y ejecuta la consulta
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT CustomerID, CompanyName FROM Customers " +
"WHERE CustomerID = 'ANTON';" +
"SELECT OrderID, CustomerID FROM Orders WHERE CustomerID = 'ANTON';";
using(SqlConnection conexin = new SqlConnection(cadcon)) {
using(SqlCommand comando = new SqlCommand(consulta, conexin)) {
conexin.Open();
using(SqlDataReader lector = comando.ExecuteReader()) {
//Carga los datos
ds.Load(lector, LoadOption.PreserveChanges,
new String[]{"Customers", "Orders"});
} //desaparece lector
} //desaparece comando
}//desaparece conexin
//Examina los contenidos del DataSet
foreach(DataTable tabla in ds.Tables){
Console.WriteLine(tabla.TableName);
foreach(DataRow fila in tabla.Rows)
Console.WriteLine("\t{0} - {1}", fila[0], fila[1]);
Console.WriteLine();
}

En el fragmento anterior, cuando se completa el mtodo Load cierra implcitamente el SqlDataReader.


Sin embargo, si va a usar el mtodo Load para cargar datos desde un lector de datos siempre debe trabajar
con el lector dentro de un bloque using. Si la llamada Load lanza una excepcin, por ejemplo porque los
datos cargados en el DataSet violan una restriccin, el mtodo Load no cerrar el lector. Al usar un bloque
using se asegura que el SqlDataReader se cerrar incluso si se produce una excepcin no controlada.
Hay una sobrecarga del mtodo Load que acepta un array de DataTable en lugar de un array de
cadenas. Esta sobrecarga es til si el DataSet ya contiene tablas que se quieren llenar.
Finalmente, hay un mtodo Load que acepta un controlador de error para el caso de que se quieran
capturar errores como el evento FillError de SqlDataAdapter mientras se cargan los datos.
La clase DataTable tambin expone un mtodo Load.

Merge
El mtodo Merge de la clase DataSet permite cargar datos desde otro DataSet o DataTable o cargar un
array de objetos DataRow. Se tratar en detalle en el Captulo 10

ReadXml y WriteXml
Puede usar el mtodo ReadXml para cargar datos XML en su DataSet desde un archivo, TextReader,
Stream o XmlReader. Tambin puede controlar cmo el DataSet lee el XML usando el parmetro mode.
Este parmetro acepta valores de la enumeracin XmlReadMode y permite especificar opciones como si
quiere leer un documento XML completo o solo un fragmento XML y si leer o ignorar el esquema XML.
La clase DataSet tambin expone un mtodo WriteXml de modo que puede escribir los contenidos de su
DataSet como XML. El mtodo WriteXml ofrece las mismas opciones que ReadXml.
Estos mtodos se tratarn con mayor detalle en el Captulo 11.

ReadXmlSchema, WriteXmlSchema e InferXmlSchema


Los mtodos ReadXmlSchema y WriteXmlSchema son similares a sus contrapartidas ReadXml y
WriteXml, pero diseados para trabajar solo con esquemas. Como ReadXml y WriteXml estos mtodos
aceptan un TextReader, un Stream, un XmlReader o una cadena que contiene un nombre de archivo.
El mtodo InferXmlSchema es similar a ReadXmlSchema, pero proporciona un nivel de control aadido.
Permite especificar una lista de espacios de nombres cuyos elementos se quieren ignorar.
Las caractersticas de ADO.NET respecto a XML se tratan en el Captulo 11.

Reset
El mtodo Reset devuelve el DataSet a su estado original sin inicializar. Si quiere descartar un DataSet
existente y empezar a trabajar con uno nuevo, esta opcin es ms rpida que crear un nuevo DataSet.

5.4.3

Eventos de la clase DataSet

La tabla 6.7 muestra los eventos de uso ms habitual de la clase DataSet.


93

ADO.NET 2.0

Tabla 6. 7: Eventos de la clase DataSet

Evento
Initialized
MergeFailed

Descripcin
Se lanza cuando se llama al mtodo EndInit del DataSet
Se lanza si el mtodo Merge del DataSet genera una excepcin

Initialized
La clase DataSet soporta el evento Initialized como parte de su implementacin del interface
ISupportInitializeNotification. Este interface se describi al tratar los mtodos BeginInit y EndInit.
La clase DataTable tambin expone un evento Initialized.

MergeFailed
El evento MergeFailed de la clase DataSet se lanza en condiciones muy precisas y algo oscuras. Por
ejemplo, se lanza cuando se est fusionando un DataTable con el DataSet, el DataSet ya contiene un
DataTable con el mismo nombre, las propiedades PrimaryKey de ambos no son iguales y se usa
MissingSchemaAction.Add en la llamada a Merge.

5.4.4

Propiedades de la clase DataTable

La tabla 6.8 muestra las propiedades de uso habitual de la clase DataTable


Tabla 6. 8: Propiedades de la clase DataTable

Propiedad
CaseSensitive

Tipo
Boolean

ChildRelations

DataRelationCollection

Columns
Constraints
DataSet
DefaultView

DataColumnCollection
ConstraintCollection
DataSet
DataView

DesignMode
ExtendedProperties
HasErrors
IsInitialized
Locale

Boolean
PropertyCollection
Boolean
Boolean
CultureInfo

MinimumCapacity

Integer

Namespace

String

ParentRelations

DataRelationCollection

Prefix

String

PrimaryKey
RemotingFormat
Rows
TableName

DataColumn[]
SerializationFormat
DataRowCollection
String

Descripcin
Controla si las comparaciones de cadenas diferencian maysculas de
minsculas.
Devuelve los objetos DataRelation que contienen datos hijos para el
DataTable.
Contiene la coleccin de objetos DataColumn para el DataTable.
Contiene la coleccin de objetos Constraint para el DataTable.
Devuelve el DataSet al que pertenece el DataTable
Devuelve el objeto DataView que recibirn los controles vinculados al
DataTable.
Indica si el DataTable est en modo de diseo.
Contiene una coleccin de propiedades y valores dinmicos.
Indica si el DataTable contiene errores
Indica si se est inicializando el DataTable
Controla la configuracin local que utiliza el DataTable para comparar
cadenas.
Controla cuanta memoria, en filas, debe reservar inicialmente el
DataTable.
Contiene el espacio de nombres que usar ADO.NET cuando escriba el
contenido del DataTable como XML o cuando cargue contenido XML en
el DataTable.
Devuelve los objetos DataRelation que contienen datos padre para el
DataTable.
Contiene el prefijo de espacio de nombres que usar ADO.NET cuando
escriba el contenido del DataTable como XML o cuando cargue
contenido XML en el DataTable.
Contiene informacin sobre la clave principal del DataTable
Controla si el DataTable se serializa como XML o en formato binario
Contiene la coleccin de objetos DataColumn del DataTable
Contiene el nombre del DataTable

CaseSensitive
La propiedad CaseSensitive del DataTable controla si las comparaciones de cadenas dentro del
DataTable diferencian maysculas de minsculas. La clase DataSet tambin expone una propiedad
CaseSensitive.
De forma predeterminada la propiedad CaseSensitive del DataTable contiene el mismo valor que la del
DataSet padre. Si se cambia el valor en el DataTable, este nuevo valor sustituye al del DataSet.
El valor predeterminado para un DataTable que no reside en un DataSet es false.

ChildRelations y ParentRelations
Las propiedades ChildRelations y ParentRelations permiten examinar los objetos DataRelation que
contienen relaciones padre o hija para el DataTable actual.

Columns
Puede usar la propiedad Columns para examinar objetos DataColumn existente, as como para
aadirlos, modificarlos o eliminarlos. Esta propiedad devuelve un objeto DataColumnCollection, que
contiene los objetos DataColumn que residen en el DataTable.
94

Escuela de Informtica del Ejrcito

Puede acceder a un DataColumn por medio de esta propiedad utilizando la propiedad Ordinal o la
propiedad ColumnName del DataColumn. Al acceder por medio de Ordinal se obtiene un mejor rendimiento.

Constraints
La propiedad Constraints permite examinar las limitaciones definidas para un DataTable. Puede usar
esta propiedad para examinar, modificar, aadir o eliminar limitaciones. La propiedad devuelve un objeto
ConstraintCollection.
Puede acceder a un Constraint usando esta propiedad por medio de la propiedad ConstraintName o el
ndice dentro de la coleccin. El uso del ndice proporciona mejor rendimiento.

DataSet
La propiedad DataSet devuelve el DataSet en el que reside el DataTable. Si el DataTable no reside en
ningn DataSet esta propiedad devuelve un objeto sin inicializar. Esta propiedad es de solo lectura.

DefaultView
Si vincula un control con un DataTable el control en realidad se enlaza con el objeto DataView que
devuelve la propiedad DefaultView. Por ejemplo, puede usar el siguiente cdigo para aplicar un filtro de
modo que solo los clientes de Espaa aparezcan en el DataGrid vinculado al DataTable. El DataTable sigue
conteniendo todos sus registros, independientemente del filtro.
tblClientes.DefaultVew.RowFilter = Country = Spain;
rejillaClientes.DataSource = tblClientes;

El objeto DataView se ver en detalle en el capitulo relativo a ordenacin y filtrado.

DesignMode
La propiedad DesignMode de la clase DataTable contiene un valor booleano que indica si el objeto est
en modo de diseo. Esta propiedad puede ser til cuando se escribe cdigo en un control de usuario. Si el
DataTable se est usando en modo de diseo dentro de un componente DesignMode devolver true; en
caso contrario devolver false. Esta propiedad es de solo lectura.
La clase DataSet tambin expone una propiedad DesignMode.

ExtendedProperties
La propiedad ExtendedProperties devuelve un objeto PropertyCollection que se utiliza para guardar
objetos variados. Puede ver un ejemplo en apartado de propiedades de la clase DataSet.
Las propiedades DataSet, DataColumn, DataRelation y Constraint tambin exponen una propiedad
ExtendedProperties.

HasErrors
La propiedad HasErrors devuelve un valor booleano que indica si alguno de los objetos DataRow dentro
del DataTable contiene errores. Si est enviando lotes de actualizaciones a la base de datos y ha
configurado la propiedad ContinueUpdateOnError del DataAdapter como true debe comprobar la propiedad
HasErrors para verificar si alguno de los intentos de actualizacin ha fallado.
Las clases DataSet y DataRow tambin exponen una propiedad HasErrors.

IsInitialized
La clase DataTable soporta la propiedad IsInitialized como parte de la implementacin del interface
ISupportInitializeNotification, descrito al tratar los mtodos BeginInit y EndInit de DataSet.
La clase DataSet tambin expone la propiedad IsInitialized.

Locale
La propiedad Locale controla cmo compara ADO.NET las cadenas del DataTable. La clase DataSet
tambin expone una propiedad Locale, y al describir esta propiedad se incluye un ejemplo de uso.

MinimumCapacity
Si sabe aproximadamente cuantas filas va a contener el DataTable puede mejorar el rendimiento de su
cdigo configurando la propiedad MinimumCapacity antes de rellenar el DataTable.
De forma predeterminada el valor de esta propiedad es 50, lo que significa que ADO.NET reserva
suficiente memoria para que el objeto DataTable contenga 50 filas de datos. Puede mejorar el rendimiento
de su cdigo asignando a esta propiedad un valor mayor que se corresponda con el nmero de filas que
contendr el DataTable. Si reduce este valor se reduce el consumo de memoria de la aplicacin.
Si asigna ms filas no se producir un error, sino que ADO.NET solicitar ms memoria.
95

ADO.NET 2.0

Namespace y Prefix
Puede usar las propiedades Namespace y Prefix para especificar un espacio de nombres y prefijo XML
para su DataTable. ADO.NET usa estos valores cuando escribe el contenido del DataTable como XML y
cuando carga datos XML en el DataTable.
Las clases DataSet y DataColumn tambin exponen propiedades Namespace y Prefix.

PrimaryKey
La propiedad PrimaryKey contiene un array de objetos DataColumn que constituyen la clave principal del
DataTable.
Esta clave principal tiene dos cometidos. Primero, acta como limitacin nica; dos objetos DataRow no
pueden tener los mismos valores en las columnas de la clave principal. Segundo, es posible localizar un
DataRow dentro del DataTable en base a esta clave principal usando el mtodo Find de la coleccin Rows
del DataTable. Esta posibilidad se ver ms a fondo en el Captulo 7.

RemotingFormat
De forma predeterminada los DataTable se serializan en forma de XML, pero si se asigna el valor
SerializationFormat.Binary a la propiedad RemotingFormat se utilizar formato binario.
La clase DataSet tambin expone una propiedad RemotingFormat.

Rows
La propiedad Rows de la clase DataTable devuelve un objeto DataRowCollection que contiene los
objetos DataRow del DataTable. Puede usar esta propiedad para examinar, aadir, modificar y eliminar
filas.
Solo se puede localizar un DataRow dentro de la coleccin por medio del ndice. Si quiere localizar una
fila concreta por medio de su clave u otro criterio deber usar los mtodos explicados en el Captulo 7.

TableName
La propiedad TableName contiene el nombre del objeto DataTable. Puede configurar esta propiedad en
el constructor.
Cuando se almacena el contenido del DataSet como XML ADO.NET usa la propiedad TableName de
cada DataTable como etiqueta de elemento para cada fila de datos de la tabla.

5.4.5

Mtodos de la clase DataTable

La tabla 6.9 muestra los mtodos de uso habitual de la clase DataTable.


Tabla 6. 9: Mtodos de la clase DataTable

Mtodo
AcceptChanges
BeginInit
BeginLoadData
Clear
Clone
Compute
Copy
CreateDataReader
EndInit
EndLoadData
GetChanges
GetErrors
ImportRow
Load
LoadDataRow
NewRow
ReadXml
ReadXmlSchema
RejectChanges
Reset
Select
WriteXml
WriteXmlSchema

96

Descripcin
Acepta todos los cambios pendientes en el DataTable
Lo utilizan los diseadores de Visual Studio antes de aadir informacin de esquema
Desactiva las restricciones durante la carga de datos
Elimina todos los DataRow del DataTable
Crea un nuevo objeto DataTable con el mismo esquema que el original pero sin DataRow.
Devuelve el valor de una expresin agregada en base al contenido del DataTable.
Crea un nuevo DataTable con el mismo esquema y los mismos DataRow
Crea un DataTableReader con el mismo contenido del DataTable.
Lo utilizan los diseadores de Visual Studio despus de aadir informacin de esquema
Reactiva las restricciones despus de la carga de datos
Devuelve un nuevo DataTable con la misma estructura y que contiene todas las filas
modificadas del original.
Devuelve un array que contiene los objetos DataRow que contienen errores.
Importa un DataRow existente al DataTable
Carga datos desde el DataReader proporcionado
Aade un nuevo DataRow al DataTable en base al contenido de un array
Devuelve un nuevo objeto DataRow para el DataTable
Lee datos XML desde un archivo, Stream, TextReader o XmlReader al DataTable
Lee datos de esquema XML desde un archivo, Stream, TextReader o XmlReader al DataTable
Rechaza todos los cambios pendientes dentro del DataTable
Restablece el DataTable a su estado original, sin inicializar
Devuelve un array de objetos DataRow en base al criterio de bsqueda especificado
Escribe el contenido del DataTable como XML en un archivo, Stream, TextReader o
XmlReader.
Escribe el esquema del DataTable como XML en un archivo, Stream, TextReader o
XmlReader.

Escuela de Informtica del Ejrcito

AcceptChanges y RejectChanges
Puede usar los mtodos AcceptChanges y RejectChanges para aceptar o rechazar todos los cambios
pendientes. Los objetos DataSet y DataRow tambin exponen estos mtodos, que se describen al tratar los
mtodos de DataSet.

BeginInit y EndInit
La clase DataTable soporta los mtodos BeginInit y EndInit como parte de la implementacin del
interface ISupportInitializeNotification, que se describe al tratar los mtodos de DataSet.

BeginLoadData y EndLoadData
Si est aadiendo una serie de objetos DataRow a su objeto DataTable puede mejorar el rendimiento de
su cdigo usando los mtodos BeginLoadData y EndLoadData.
Al llamar a BeginLoadData se desactivan las restricciones para el DataTable. Puede volver a activar las
restricciones de nuevo llamando a EndLoadData. Si el DataTable contiene filas que violan las restricciones
recibir una ConstraintException al llamar a EndLoadData. Para determinar qu filas han causado la
excepcin puede examinar las filas que devuelve el mtodo GetErrors.

Clear
Puede usar el mtodo Clear de la clase DataTable para eliminar todos los objetos DataRow del
DataTable. El uso de este mtodo es ms rpido que liberar el DataTable y crear uno nuevo con la misma
estructura.
El DataSet tambin expone un mtodo Clear.

Compute
Puede usar el mtodo Compute para ejecutar una consulta agregada sobre una sola columna del
DataTable basada en el criterio de bsqueda que especifique.
El siguiente fragmento de cdigo muestra el uso del mtodo Compute para contar el nmero de pedidos
que incluyen chai (ProductID = 1). El fragmento tambin calcula el nmero total de unidades de chai
pedidas.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT OrderID, ProductID, Quantity FROM [Order Details]";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tabla = new DataTable("Detalles");
adaptador.Fill(tabla);
int numPedidos = (int)tabla.Compute("COUNT(OrderID)", "ProductID=1");
Int64 numUnidades = (Int64)tabla.Compute("SUM(Quantity)", "ProductID=1");
Console.WriteLine("Nm. pedidos que incluyen chai: {0}", numPedidos);
Console.WriteLine("Total de unidades chai pedidas: {0}", numUnidades);

No puede usar el mtodo Compute para calcular un agregado que abarque varias columnas, como
SUM(Quantity * UnitPrice). Sin embargo, puede usar una columna basada en expresin para ejecutar el
clculo entre dos columnas (Quantity * UnitPrice) y despus utilizar Compute para calcular la suma de esta
columna, SUM(TotalPedido).
El mtodo Compute devuelve su resultado con el tipo de datos Object. Cuando se realiza un clculo
usando Compute el tipo que utiliza para almacenar los resultados puede sorprenderle. Por ejemplo, la
propiedad DataType de la columna Quantity es un entero de 16 bits, pero la llamada al mtodo Compute
devuelve un entero de 64 bit, en forma de Object.
Si no est seguro de qu tipo de datos usar para almacenar los resultados del mtodo Compute puede
usar un cdigo como este:
object objetoValor = tabla.Compute("SUM(Quantity)", "ProductID=1");
Console.WriteLine(objetoValor.GetType().ToString());

CreateDataReader
La clase DataTable expone un mtodo CreateDataReader para los desarrolladores que quieren acceder
a los contenidos de la tabla usando un interface DataReader. Este mtodo devuelve una instancia de la
clase DataTableReader, que se comporta como un objeto SqlDataReader. La clase DataSet tambin
expone un mtodo CreateDataReader.

97

ADO.NET 2.0

GetChanges
El mtodo GetChanges de la clase DataTable devuelve un nuevo DataTable que tiene la misma
estructura del original y contiene todas las filas con cambios. Esta caracterstica se trata con mayor detalle
en el Captulo 10.
La clase DataSet tambin expone un mtodo GetChanges.

GetErrors
Puede usar el mtodo GetErrors para acceder a los objetos DataRow que contienen errores, ya sean
estos errores violaciones de restricciones o intentos de actualizacin fallidos. El mtodo GetErrors devuelve
un array de objetos DataRow.

ImportRow, LoadDataRow, NewRow


El mtodo ImportRow acepta un objeto DataRow que aade a la DataTable.
El mtodo LoadDataRow acepta un array como primer argumento. Cada elemento del array se
corresponde con un elemento de la coleccin Columns del objeto DataTable. El segundo argumento del
mtodo LoadDataRow recibe un valor booleano para controlar el RowState del nuevo objeto DataRow.
Puede utilizar false para este parmetro si quiere que el nuevo DataRow tenga Added como RowState, y
puede utilizar true si quiere que el RowState sea Unmodified. El mtodo LoadDataRow devuelve el objeto
DataRow recin creado.
El mtodo NewRow devuelve un nuevo objeto DataRow correspondiente al DataTable. El nuevo
DataRow no reside an en la coleccin Rows del DataTable, es necesario aadirlo despus de asignar
valores a sus elementos.
A la hora de elegir uno de estos mtodos siga estos consejos:
Use ImportRow si quiere importar una fila de un DataTable diferente.
Use LoadDataRow si quiere aadir una serie de filas de una sola vez, por ejemplo a partir de un
archivo. Aadir una fila a un DataTable usando LoadDataRow necesita menos lneas de cdigo que
usando NewRow.
En el resto de casos use NewRow.

Load
En ADO.NET 2.0 la clase DataTable expone un mtodo Load que puede usar para cargar datos desde
un DataReader a un DataTable. Este mtodo no existe en ADO.NET 1.x.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT CustomerID, CompanyName from Customers";
DataTable tabla = new DataTable("Clientes");
using(SqlConnection conexin = new SqlConnection(cadcon)) {
using(SqlCommand comando = new SqlCommand(consulta, conexin)){
conexin.Open();
using(SqlDataReader lector = comando.ExecuteReader()) {
//Carga los datos
tabla.Load(lector);
}//Desaparece el lector
}//Desaparece el comando
}//Desaparece la conexin
//Examina el contenido de la tabla
foreach(DataRow fila in tabla.Rows)
Console.WriteLine("{0} - {1}", fila[0], fila[1]);

Cuando se completa el mtodo Load cierra el SqlDataReader implcitamente. Sin embargo, el mtodo
Load se debe usar siempre dentro de un bloque using; si la llamada a Load provoca una excepcin, por
ejemplo por la violacin de alguna restriccin, no se cerrar el lector. El bloque using asegura que el
SqlDataReader se cerrar incluso si se produce una excepcin.
Existe una sobrecarga del mtodo Load que acepta un valor de la enumeracin LoadOption para
controlar el RowState de las filas aadidas o modificadas durante la operacin. Este valor se corresponde
con la propiedad FillLoadOption del SqlDataAdapter. Tambin existe un mtodo Load que acepta un
controlador de error para el caso de querer capturar errores como el evento FillError de SqlDataAdapter
durante la carga de datos.
La clase DataSet tambin expone un mtodo Load.
98

Escuela de Informtica del Ejrcito

ReadXml, ReadXmlSchema, WriteXml, WriteXmlSchema


En ADO.NET 2.0 la clase DataTable expone los mtodos ReadXml, ReadXmlSchema, WriteXml y
WriteXmlSchema igual que la clase DataSet.
Las caractersticas relacionadas con XML se tratarn en el Captulo 11.

Reset
El mtodo Reset de la clase DataTable devuelve el objeto a su estado original, sin inicializar. Si quiere
descartar un DataTable existente y empezar a trabajar con uno nuevo use el mtodo Reset en lugar de
crear una nueva instancia.
La clase DataSet tambin expone un mtodo Reset.

Select
Puede usar el mtodo Select para localizar una o varias filas dentro de un DataTable en base a
diferentes criterios de bsqueda. Este mtodo devuelve un array de objetos DataRow que satisfacen el
criterio especificado.
Este mtodo se ver con mayor detalle en el Captulo 7.

5.4.6

Eventos de la clase DataTable

La tabla 6.10 muestra los eventos de uso habitual de la clase DataTable.


Tabla 6. 10: Eventos de la clase DataTable

Evento
ColumnChanged
ColumnChanging
Initialized
RowChanged
RowChanging
RowDeleted
RowDeleting
TableCleared
TableClearing
TableNewRow

Cundo se lanza
Despus de que haya cambiado el contenido de una columna
Justo antes de que el contenido de una columna cambie
Cuando se llama al mtodo EndInit del DataTable
Despus de que el contenido de una fila haya cambiado
Justo antes de que el contenido de una fila cambie
Despus de que se haya borrado una fila
Justo antes de que se borre una fila
Justo despus de que se vace un DataTable
Justo antes de que se vace un DataTable
Cuando se llama al mtodo NewRow del DataTable

ColumnChanged y ColumnChanging
Los eventos ColumnChanged y ColumnChanging se lanzan cada vez que cambia el contenido de una
columna en una fila. Puede usarlos para validar los datos, activar y desactivar controles, etc.
Los eventos reciben un argumento de tipo DataColumnChangeEventArgs que tiene propiedades como
Row y Column que puede usar para determinar qu fila y columna han cambiado.
Recuerde que si usa estos eventos para modificar el contenido de la fila puede entrar en un bucle infinito.

Initialized
La clase DataTable soporta el evento Initialized como parte de la implementacin del interface
ISupportInitializeNotification. Este interface se describe al tratar los mtodos BeginInit y EndInit de DataSet.
La clase DataSet tambin expone un evento Initialized.

RowChanged y RowChanging
Los eventos RowChanged y RowChanging se lanzan cuando cambia el contenido o la propiedad
RowState de un objeto DataRow.
Puede determinar por qu se ha lanzado el evento comprobando la propiedad Action del argumento
DataRowChangeEventArgs del evento. Tambin puede acceder a la fila que se est modificando por medio
de la propiedad Row del mismo argumento.

RowDeleted y RowDeleting
Los eventos RowDeleted y RowDeleting exponen los mismos argumentos y propiedades que
RowChanged y RowChanging. La nica diferencia es que estos eventos se lanzan cuando se borra una fila
del DataTable.

TableClearing y TableCleared
Si llama al mtodo Clear de un DataTable se lanza el evento TableClearing antes de borrar las filas y el
evento TableCleared despus. Ambos eventos usan la misma estructura de controlador de evento
DataTableClearEventHandler y el mismo tipo de argumento DataTableClearEventArgs.

99

ADO.NET 2.0

TableNewRow
El evento TableNewRow se lanza cuando se llama al mtodo NewRow de DataTable. Este evento puede
resultar til si quiere generar mediante programacin valores predeterminados para columnas en nuevas
filas. Tambin puede usar el evento TableNewRow para generar valores en aplicaciones Windows que usan
controles enlazados, porque la vinculacin de datos de Windows confa en el mtodo NewRow de
DataTable para crear nuevas filas de datos. El evento recibe un parmetro DataTableNewRowEventArgs
cuya propiedad Row representa la fila recin aadida.
El mayor inconveniente de este evento es que se lanza solo cuando se crean nuevas filas usando
NewRow. No se lanza para filas creadas al llamar a DataTable.LoadDataRow, DataTable.Rows.Add,
DataTable.Load o SqlDataAdapter.Fill.

5.4.7

Propiedades de la clase DataColumn

La tabla 6.11 muestra las propiedades de uso habitual de la clase DataColumn.


Tabla 6. 11: Propiedades de la clase DataColumn

Propiedad
AllowDBNull
AutoIncrement

Tipo
Boolean
Boolean

AutoIncrementSeed

Integer

AutoIncrementStep

Integer

Caption

String

ColumnMapping

MappingType

ColumnName
DataType

String
Type

DateTimeMode
DefaultValue

DataSetDateTime
Object

Expression
ExtendedProperties
MaxLength
Namespace

String
PropertyCollection
Integer
String

Ordinal

Integer

Prefix

String

ReadOnly
Table
Unique

Boolean
DataTable
Boolean

Descripcin
Controla si la columna acepta valores nulos
Controla si ADO.NET generar valores auto incrementados para esta
columna
Controla qu valor utiliza ADO.NET para el primer valor de una columna con
auto incremento
Controla el valor que ADO.NET usa para generar los valores auto
incrementados posteriores
Controla el ttulo de la columna. No establece el ttulo de la columna en
controles vinculados como DataGrid.
Controla cmo almacena ADO.NET el contenido de la columna en un
documento XML.
Contiene el nombre del objeto DataColumn.
Controla el tipo que usa ADO.NET para almacenar el contenido de la
columna.
Controla el formato de serializacin para columnas DateTime.
Controla el valor predeterminado que usar ADO.NET para la columna en
nuevas filas.
Controla cmo genera ADO.NET los valores para columnas calculadas
Contiene una coleccin de propiedades y valores dinmicos.
Especifica la longitud mxima de la cadena que puede contener la columna
Contiene el espacio de nombres que usar ADO.NET cuando escriba el
contenido de la columna en XML o cargue datos XML en la columna.
Devuelve el ndice del DataColumn dentro de la coleccin Columns del
DataTable.
Contiene el prefijo para el espacio de nombres que usar ADO.NET cuando
escriba el contenido de la columna en XML o cargue datos XML en la
columna.
Controla si los contenidos de la columna son de solo lectura.
Devuelve el DataTable al que pertenece el DataColumn.
Controla si ADO.NET exige que los valores de esta columna sean nicos
para cada fila.

AllowDBNull
Puede usar la propiedad AllowDBNull para controlar si el DataColumn aceptar valores nulos. De forma
predeterminada el valor de esta propiedad es true cuando se crea un nuevo objeto DataColumn.
Al usar el mtodo Fill de SqlDataAdapter para crear nuevos objetos DataColumn no se pone la propiedad
AllowDBNull a false incluso aunque la columna correspondiente en la base de datos no acepte valores
nulos. El SqlDataAdapter no obtiene esta informacin de esquema de la base de datos al llamar a Fill.
Llamando al mtodo FillSchema se obtiene esta informacin y se aplica a las nuevas columnas del
DataTable.

AutoIncrement, AutoIncrementSeed, AutoIncrementStep


Puede usar estas propiedades para controlar s y cmo ADO.NET genera valores auto incrementados
para la columna.
Al asignar true a la propiedad AutoIncrement se obliga a ADO.NET a generar nuevos valores auto
incrementados para la columna. El valor predeterminado es false, y como con AllowDBNull se debe llamar
al mtodo FillSchema de SqlDataAdapter para que cambie a true en los DataColumn correspondientes a
columnas con auto incremento en la base de datos.
Si AutoIncrement tiene el valor true ADO.NET usar los valores de las propiedades AutoIncrementSeed
y AutoIncrementStep para generar nuevos valores con auto incremento. El valor predeterminado de
100

Escuela de Informtica del Ejrcito

AutoIncrementSeed es 0, y el de AutoIncrementStep -1. Por las razones que se indicaron en el Captulo 1,


recomiendo asignar -1 a ambos valores cuando se use ADO.NET para generar nuevos valores con auto
incremento. Usando el mtodo FillSchema de SqlDataAdapter no se configuran las propiedades
AutoIncrementSeed ni AutoIncrementStep.
En este Captulo se ha explicado que aunque se puede usar ADO.NET para generar valores para las
columnas de auto incremento en el DataSet no se debe intentar enviar estos valores a la base de datos.
Deje que la base de datos genere sus propios valores.

Caption
El nombre de la propiedad Caption implica (en ingls) que el valor de esta propiedad determinar el texto
en el encabezado de la columna en un control DataGrid o DataGridView. Desafortunadamente, no es as.
Seguro que alguien me corrige, pero no conozco ninguna utilidad para esta propiedad. Si no configura esta
propiedad su valor predeterminado ser el mismo que el de ColumnName.

ColumnMapping
Puede usar la propiedad ColumnMapping para controlar cmo escribe ADO.NET los contenidos de la
columna cuando devuelve un DataSet como XML.
La propiedad ColumnMapping acepta valores de la enumeracin MappingType del espacio de nombres
System.Data. El valor predeterminado de esta propiedad es Element, lo que significa que el valor de cada
columna de un DataRow aparece como etiqueta de elemento. Otros valores posibles son Attribute, Hidden o
SimpleContent.
En los siguientes ejemplos se muestra el resultado de usar el valor Element (a la izquierda) o Attribute (a
la derecha).
<MiDataSet>
<Clientes>
<CustomerID>ABCDE</CustomerID>
<CompanyName>Nuevo Cliente</CompanyName>
<ContactName>Nuevo contacto</ContactName>
<Phone>91 11 111 111</Phone>
</Clientes>
</MiDataSet>

<MiDataSet>
<Clientes CustomerID=ABCDE
CompanyName=Nuevo Cliente
ContactName=Nuevo contacto
Phone=91 11 111 111 />
</MiDataSet>

Las caractersticas relacionadas con XML se vern en el Captulo 11.

ColumnName
La propiedad ColumnName contiene el nombre del DataColumn. Puede configurar esta propiedad en el
constructor.

DataType
La propiedad DataType controla el tipo de datos que ADO.NET usar para almacenar los contenidos de
la columna. De forma predeterminada es una cadena. ADO.NET almacena los datos usando un tipo de
datos .NET.

DateTimeMode
La propiedad DateTimeMode controla cmo se serializan los valores DateTime en un DataColumn.

DefaultValue
Puede usar la propiedad DefaultValue para generar un valor predeterminado para la columna en cada
nueva fila. Esta propiedad acepta un valor esttico por medio del tipo de datos genrico Object. Si quiere
generar un valor de forma dinmica utilice el evento TableNewRow.

Expression
Puede almacenar una expresin en la propiedad Expression, y ADO.NET evaluar esta expresin en el
momento en el que se le pida el contenido de la columna. Cuando esta expresin contiene algo que no sea
una cadena vaca automticamente la propiedad ReadOnly de la columna cambia a true.
El siguiente fragmento muestra cmo utilizar la propiedad Expression de un DataColumn para que
devuelva el resultado del producto de otras dos columnas del DataTable Quantity y UnitPrice. El cdigo
aade tambin un DataRow al DataTable y muestra el contenido de la columna basada en expresin.
DataTable tabla = new DataTable("Detalles");
tabla.Columns.Add("OrderID", typeof(int));
tabla.Columns.Add("ProductID", typeof(int));
tabla.Columns.Add("Quantity", typeof(int));
tabla.Columns.Add("UnitPrice", typeof(Decimal));

101

ADO.NET 2.0

DataColumn calculada = new DataColumn("TotalProducto", typeof(Decimal));


calculada.Expression = "Quantity * UnitPrice";
tabla.Columns.Add(calculada);
DataRow fila = tabla.NewRow();
fila["OrderID"] = 1;
fila["ProductID"] = 1;
fila["Quantity"] = 6;
fila["UnitPrice"] = 18;
tabla.Rows.Add(fila);
Console.Write(fila["TotalProducto"]);

En el Captulo 6 aprender cmo hacer referencia al contenido de otros objetos DataTable en una
columna calculada. Puede consultar las funciones que se pueden usar en la propiedad Expression en la
documentacin MSDN.

ExtendedProperties
La propiedad ExtendedProperties devuelve un objeto PropertyCollection, diseado para contener objetos
variados. Las clases DataSet, DataTable, DataRelation y Constraint tambin exponen esta propiedad.
Para ms informacin consulte esta propiedad en la clase DataSet.

MaxLength
Puede usar esta propiedad para asegurar que el usuario no escribe en el DataColumn una cadena ms
larga que la que acepta la base de datos.
El valor predeterminado es -1, lo que significa que no hay longitud mxima. Como con AllowDBNull y
AutoIncrement esta propiedad no se configura al crea objetos DataColumn por medio del mtodo Fill de
SqlDataAdapter; es necesario usar el mtodo FillSchema.

Namespace y Prefix
Puede usar las propiedades Namespace y Prefix de la clase DataColumn para especificar el espacio de
nombres y prefijo XML que usar ADO.NET cuando escriba el contenido del DataSet como XML o cargue
datos desde XML en el DataSet. Las clases DataSet y DataTable tambin exponen esta propiedad.

Ordinal
La propiedad Ordinal devuelve la posicin del DataColumn dentro de la coleccin Columns del objeto
DataTable. Esta propiedad es de solo lectura, y si el DataColumn no es parte de un DataTable devuelve -1.

ReadOnly
Puede usar la propiedad ReadOnly para controla si el contenido de la columna es de solo lectura. El
valor predeterminado es false.
Si configura la propiedad Expression la propiedad ReadOnly cambia automticamente a true y se
convierte en propiedad de solo lectura.
Si intenta cambiar el valor de una columna cuya propiedad ReadOnly es true ADO.NET lanza una
ReadOnlyException, pero incluso si la propiedad ReadOnly es true puede modificar el contenido de la
columna antes de aadir la fila a la coleccin Rows de un objeto DataTable.
La propiedad ReadOnly, como AllowDBNull y AutoIncrement, es una de las que configura
SqlDataAdapter por medio de su mtodo FillSchema, pero no por medio de su mtodo Fill.

Table
La propiedad Table devuelve el DataTable al que pertenece el objeto DataColumn. Esta propiedad es de
solo lectura, y si el DataColumn no reside en un DataTable devuelve un objeto sin inicializar.

Unique
Puede utilizar la propiedad Unique para asegurar que todos los valores de una columna en un DataTable
son diferentes. Su valor predeterminado es false.
Al cambiar Unique a True se crea implcitamente un objeto UniqueConstraint para el DataTable en el que
reside la columna. De forma similar, al aadir un UniqueConstraint basado en una sola columna se cambia
la propiedad Unique del DataColumn implicado a true.
Si crea una restriccin de unicidad o una clave principal sobre una coleccin de columnas no se pone a
true la propiedad Unique de estas columnas, ya que no es necesario que cada columna sea nica.
Esta es otra de las propiedades que no es configurada por el mtodo Fill de SqlDataAdapter sino por su
mtodo FillSchema.
102

Escuela de Informtica del Ejrcito

5.4.8

Propiedades de la clase DataRow

La tabla 6.12 muestra las propiedades de uso habitual de la clase DataRow.


Tabla 6. 12: Propiedades de la clase DataRow

Propiedad
HasErrors
Item
ItemArray
RowError
RowState
Table

Tipo
Boolean
Object
Object[]
String
DataRowState
DataTable

Descripcin
Indica si la fila contiene errores
Devuelve o modifica el contenido de una columna
Devuelve o modifica el contenido de la fila
Devuelve o modifica informacin de error sobre la fila
Devuelve el estado de la fila
Devuelve el DataTable al que pertenece la fila

HasErrors
Puede usar la propiedad HasErrors para determinar si la fila contiene errores. Esta propiedad devuelve
un boolean y es de solo lectura.

Item
La propiedad Item permite examinar o modificar el contenido de una columna en una fila. Puede acceder
al contenido de la columna especificando el ordinal de la columna, su nombre o el propio objeto
DataColumn.
La propiedad Item admite un valor de la enumeracin DataRowVersion que permite elegir qu versin de
la columna quiere ver.
En C# esta propiedad es el indexador de la clase DataRow.

ItemArray
Usando la propiedad ItemArray puede recuperar o modificar todas las columnas de la fila. Esta propiedad
devuelve o acepta un array del tipo Object, en el que cada elemento del array corresponde a una columna
del DataTable.
Cuando use la propiedad ItemArray para cambiar los contenidos de una fila puede usar la palabra clave
null para mantener el valor actual de una columna.
La siguiente lnea modifica el contenido de las columnas segunda, tercera y cuarta de la fila y deja sin
modificar la primera y la ltima. Esta opcin es necesaria cuando existen columnas de solo lectura.
fila.ItemArray = new object[]{null, 2, 3, 4, null};

RowError
La propiedad RowError devuelve una cadena que contiene informacin de error para la fila. Puede
configurar esta propiedad con una cadena para indicar que la fila contiene un error. La propiedad HasErrors
del DataRow puede devolver true incluso si RowError est vaco.

RowState
La propiedad RowState devuelve un valor de la enumeracin RowState que indica el estado actual de la
fila. Esta propiedad es de solo lectura. Los valores de esta enumeracin se han discutido en este captulo.

Table
La propiedad Table devuelve el DataTable al que pertenece el objeto DataRow. Esta propiedad es de
solo lectura.

5.4.9

Mtodo de la clase DataRow

La tabla 6.13 resume los mtodos de uso habitual de la clase DataRow.


Tabla 6. 13: Mtodos de la clase DataRow

Mtodo
AcceptChanges
BeginEdit
CancelEdit
ClearErrors
Delete
EndEdit
GetChildRows
GetColumnError
GetColumnsInError
GetParentRow
GetParentRows
HasVersion
IsNull
RejectChanges

Descripcin
Acepta los cambios pendientes almacenados en el DataRow
Comienza el proceso de edicin del DataSet
Cancela los cambios hechos desde la llamada a BeginEdit
Limpia los errores en el DataRow
Marca el DataRow como borrado
Consigna los cambios hechos desde la llamada a BeginEdit
Devuelve un array de objetos DataRow hijos del actual en base a una DataRelation.
Recupera informacin de error de una columna concreta
Recupera un array de objetos DataColumn que contiene errores para la fila actual.
Devuelve el DataRow padre del actual en base a una DataRelation
Devuelve un array de objetos DataRow padre del actual en base a una DataRelation
Devuelve un boolean que indica si el DataRow puede devolver una cierta versin de los datos
Indica si una cierta columna de la fila contiene un valor null
Descarta los cambios pendientes almacenados en el DataRow

103

ADO.NET 2.0

Tabla 6. 13: Mtodos de la clase DataRow

Mtodo
SetAdded
SetColumnError
SetModified
SetParentRow

Descripcin
Cambia el RowState del DataRow a Added
Configura la informacin de error para una cierta columna en la fila
Cambia el RowState del DataRow a Modified
Cambia el DataRow padre del actual segn una DataRelation

AcceptChanges y RejectChanges
Los DataRow almacenan los cambios pendientes de forma que posteriormente se puedan enviar a la
base de datos. Los mtodos AcceptChanges y RejectChanges permiten aceptar o descartar estos cambios
respectivamente.
De forma predeterminada, cuando se envan con xito cambios pendientes a la base de datos usando un
SqlDataAdapter, el adaptador llama implcitamente al mtodo AcceptChanges del objeto DataRow. Desde
este momento el DataRow tendr como RowState Unmodified.
Puede descartar los cambios pendientes en un DataRow llamando a su mtodo RejectChanges. Como
en el caso anterior, el RowState del DataRow cambia a Unmodified.
Suponga que tiene una fila de datos de cliente que contiene un cambio pendiente. El campo
CompanyName contena originalmente el valor CompanyName Inicial, pero ahora contiene
CompanyName Nuevo.
Si llama al mtodo AcceptChanges el DataRow ya no mantiene el valor original CompanyName Inicial.
El DataRow devolver CompanyName Nuevo independientemente de la versin que se solicite.
En cambio, si llama al mtodo RejectChanges el DataRow descarta el valor CompanyName Nuevo. El
DataRow devolver CompanyName Inicial independientemente de la versin que se solicite.

BeginEdit, CancelEdit, EndEdit


Los mtodos BeginEdit, CancelEdit y EndEdit permiten almacenar o cancelar una serie de cambios en el
DataRow. Por ejemplo, puede que quiera permitir al usuario modificar los contenidos de una fila y mostrar
un dilogo que le de la posibilidad de aceptarlos o descartarlos.
CancelEdit y EndEdit tienen un comportamiento diferente a AcceptChanges y RejectChanges. La mejor
forma de explicar la diferencia entre los dos conjuntos de mtodos es mostrar un cdigo de ejemplo. El
siguiente fragmento crea un nuevo DataRow y modifica su contenido. A continuacin llama a BeginEdit,
modifica el contenido de nuevo y muestra las diferentes versiones de la fila.
DataTable tabla = new DataTable("Clientes");;
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("CompanyName", typeof(string));
DataRow fila;
//Crea una nueva fila usando LoadDataRow
fila = tabla.LoadDataRow(
new object[] {"NUECI", "CompanyName Inicial" }, true);
//Modifica el estado de la fila
//fila.RowState devuelve Modified
//El valor 'Original' de la fila es "CompanyName Inicial"
fila["CompanyName"] = "CompanyName Nuevo";
//Llama a BeginEdit y modifica CompanyName
fila.BeginEdit();
fila["CompanyName"] = "CompanyName ms nuevo an";
//Muestra las diferentes versiones de la columna
Console.WriteLine("Propuesto: {0}", fila["CompanyName", DataRowVersion.Proposed]);
Console.WriteLine("Actual: {0}", fila["CompanyName", DataRowVersion.Current]);
Console.WriteLine("Original: {0}", fila["CompanyName", DataRowVersion.Original]);

Ejecute el cdigo y ver que el valor propuesto es CompanyName ms nuevo an, el actual es
CompanyName Nuevo y el original es CompanyName Inicial.
Puede llamar al mtodo EndEdit para aceptar los cambios. El valor actual de la columna cambiar y ser
el mismo que el propuesto. El valor original se mantiene.
Puede llamar al mtodo CancelEdit para descartar los cambios. Los valores actual y original de la fila
volvern a ser los anteriores a la llamada a BeginEdit.
Tenga en mente que mientras est modificando los contenidos de una fila despus de llamar al mtodo
BeginEdit el mtodo Item devolver de forma predeterminada el valor propuesto para la columna.
104

Escuela de Informtica del Ejrcito

ClearErrors
Para limpiar los errores en un DataRow llame a su mtodo ClearErrors. Este mtodo elimina la
informacin de error del objeto DataRow como un todo, as como la de cada columna de la fila.

Delete
El mtodo Delete no elimina un DataRow realmente de la coleccin Rows de su DataTable. Cuando se
llama a este mtodo ADO.NET marca la fila como borrada de modo que posteriormente se puede eliminar la
fila correspondiente de la base de datos llamando al mtodo Update del DataAdapter.
Si quiere eliminar completamente un DataRow llame a su mtodo Delete e inmediatamente despus a
AcceptChanges. Tambin puede usar el mtodo Remove del objeto DataRowCollection.

GetChildRows
Puede acceder al mtodo GetChildRows para acceder a las filas hijas del DataRow actual. Para usar
este mtodo hay que proporcionar un DataRelation o el nombre de un DataRelation. Tambin puede
proporcionar un valor de la enumeracin DataRowVersion para controlar la versin de los datos hijos que se
recupera. Este mtodo devuelve un array de objetos DataRow.

GetColumnError y SetColumnError
Puede usar los mtodos GetColumnError y SetColumnError respectivamente para obtener y configurar la
informacin de error para una columna concreta en una fila. Puede proporcionar un nombre de columna, su
posicin ordinal dentro del DataTable o el propio objeto DataColumn a cualquiera de los mtodos.
Tambin puede usar SetColumnError para limpiar la informacin de error de una columna concreta
pasando una cadena vaca como segundo parmetro.

GetColumnsInError
Si la propiedad HasErrors
de un objeto DataRow devuelve true puede usar el mtodo
GetColumnsInError para determinar qu columna, o columnas, del DataRow contiene la informacin de
error.

GetParentRow, GetParentRows, SetParentRow


Los mtodos GetParentRow y SetParentRow proporcionan una forma sencilla para examinar o
configurar, respectivamente, la fila madre de la actual por medio de un DataRelation.
Como GetChildRows, el mtodo GetParentRow acepta informacin de relacin ya sea el nombre del
DataRelation o el propio objeto as como un valor de la enumeracin DataRowVersion para controlar la
versin de la fila que devuelve el mtodo. El mtodo GetParentRow devuelve un objeto DataRow.
El mtodo SetParentRow permite cambiar la madre de una fila segn una relacin. Para usar este
mtodo simplemente pase la nueva madre. Si la fila actual se encuentra en una tabla que es hija en varias
relaciones dentro del DataSet debe usar la sobrecarga de este mtodo que permite pasar el objeto
DataRelation como segundo parmetro.

HasVersion
Antes de consultar una versin concreta de un DataRow es conveniente comprobar que esta versin
existe, para no provocar una excepcin. El mtodo HasVersion acepta un valor de la enumeracin
DataRowVersion y devuelve un valor boolean que indica si el DataRow mantiene actualmente esta versin
de los datos.

IsNull
Suponga que est trabajando con un DataRow que contiene informacin de cliente y quiere recuperar el
contenido de la columna ContactName y ponerlo en una variable de cadena. Si intenta leer el contenido del
campo, y este contenido es nulo, se producir una excepcin.
Para evitar este problema tiene dos posibilidades: configurar la base de datos y el DataSet de modo que
no acepte nulos para esta columna, o comprobar si el valor es nulo antes de intentar leerlo.
El mtodo IsNull simplifica la segunda posibilidad. Este mtodo acepta el nombre de una columna, su
posicin ordinal o el propio objeto DataColumn y devuelve un valor booleano que indica si la columna
contiene un valor null.
Este mtodo soporta una sobrecarga que acepta un objeto DataColumn y un valor de la enumeracin
DataRowVersion. Puede usar este mtodo para determinar si una versin concreta de un campo contiene
un valor nulo.

SetAdded y SetModified
En ADO.NET 2.0 la clase DataRow expone dos nuevos mtodos, SetAdded y SetModified. Puede llamar
a estos mtodos para configurar el RowState del DataRow con el estado correspondiente al nombre del
105

ADO.NET 2.0

mtodo. Estos mtodos solo estn disponibles cuando el RowState del DataRow es Unmodified. Llamar a
uno de ellos en cualquier otro estado provoca una excepcin.

5.4.10 Propiedades de la clase UniqueConstraint


La tabla 6.14 muestra las propiedades de uso ms comn de la clase UniqueConstraint.
Tabla 6. 14: Propiedades de la clase UniqueConstraint

Propiedad
Columns
ConstraintName
ExtendedProperties
IsPrimaryKey
Table

Tipo
DataColumn[]
String
PropertyCollection
Boolean
DataTable

Descripcin
Columnas que toman parte en la restriccin
Nombre de la restriccin
Contiene una coleccin de propiedades y valores dinmicos
Indica si la restriccin es la clave principal de la tabla
Devuelve la tabla a la que pertenece la restriccin

Columns
La propiedad Columns devuelve un array de objetos DataColumn que contiene las columnas que
componen la restriccin. Esta propiedad es de solo lectura.

ConstraintName
Puede usar la propiedad ConstraintName para examinar o configurar el nombre del UniqueConstraint.

ExtendedProperties
La propiedad ExtendedProperties de la clase UniqueConstraint devuelve un objeto PropertyCollection
diseado para contener diferentes objetos. Las clases DataSet, DataColumn, DataRelation y
ForeignKeyConstraint tambin exponen esta propiedad.
Para ms informacin vea la descripcin de esta propiedad en las propiedades de DataSet.

IsPrimaryKey
La propiedad IsPrimaryKey devuelve un valor boolean que indica si el objeto UniqueConstraint es la
clave principal del DataTable. Esta propiedad es de solo lectura; slo se puede indicar si la restriccin es
clave principal en el constructor.
Tambin se puede establecer la clave principal de un DataTable por medio de su propiedad PrimaryKey.

Table
La propiedad Table devuelve el DataTable al que pertenece la UniqueConstraint. Es de solo lectura.

5.4.11 Propiedades de la clase ForeignKeyConstraint


La tabla 6.15 muestra las propiedades de uso habitual de la clase ForeignKeyConstraint.
Tabla 6. 15: Propiedades de la clase ForeignKeyConstraint

Propiedad
AcceptRejectRule

Tipo
AcceptRejectRule

Columns
ConstraintName
DeleteRule

DataColumn[ ]
String
Rule

ExtendedProperties
RelatedColumns
RelatedTables
Table
UpdateRule

PropertyCollection
DataColumn[ ]
DataTable
DataTable
Rule

Descripcin
Controla si los efectos de una llamada a los mtodos AcceptChanges o
RejectChanges de una fila madre se propagan en cascada a las filas hijas.
Devuelve las columnas de la tabla hija que componen la restriccin
Contiene el nombre de la restriccin.
Controla cmo o si el borrado de una fila madre se propaga en cascada a
las filas hijas
Contiene una coleccin de valores y propiedades dinmicos.
Devuelve las columnas de la tabla madre que componen la restriccin
Devuelve la tabla madre en la restriccin
Devuelve la tabla hija en la restriccin
Controla cmo o si la actualizacin de una fila madre se propaga en cascada
a las filas hijas.

AcceptRejectRule, DeleteRule, UpdateRule


Las propiedades AcceptRejectRule, DeleteRule y UpdateRule controla cmo o si los cambios en una fila
madre se propagan en cascada a las filas hijas.
La propiedad AcceptRejectRule acepta valores de la enumeracin AcceptRejectRule; el valor
predeterminado es None, de modo que si se llama a AcceptChanges o RejectChanges sobre una fila las
filas hijas no se ven afectadas. Si se utiliza el valor Cascade la accin se propaga en cascada a las filas
hijas definidas por el objeto ForeignKeyConstraint.
Las propiedades DeleteRule y UpdateRule se comportan de una forma similar, pero aceptan valores de
la enumeracin Rule. El valor predeterminado de ambas propiedades es Cascade, lo que significa que los
cambios hechos en una fila se propagan en cascada a las filas hijas.
Por ejemplo, si llama al mtodo Delete de un DataRow est llamando implcitamente al mtodo Delete de
sus filas hijas. De forma similar, si cambia el valor de una columna clave en un DataRow cambiar
implcitamente el contenido de la columna correspondiente en las filas hijas.
106

Escuela de Informtica del Ejrcito

Puede cambiar las propiedades DeleteRule y UpdateRule a None si no quiere que los cambios e
propaguen. Tambin puede asignar el valor SetNull; con este valor se asignar null a las columnas
relacionadas en las filas hijas si se borra o modifica el contenido de la columna en la fila madre. El valor
SetDefault tiene un comportamiento similar, salvo que devuelve las columnas relacionadas en las filas hijas
a su valor predeterminado.

Columns y RelatedColumns
La propiedad Columns devuelve un array de objetos DataColumn que contiene las columnas de la tabla
hija que toman parte en la restriccin. La propiedad RelatedColumns devuelve la misma informacin para la
tabla madre. Ambas propiedades son de solo lectura.

ConstraintName
Puede usar la propiedad ConstraintName para examinar o configurar el nombre de la restriccin.

ExtendedProperties
La propiedad ExtendedProperties de la clase ForeignKeyConstraint devuelve un objeto
PropertyCollection diseado para contener diferentes objetos. Las clases DataSet, DataColumn,
DataRelation y UniqueConstraint tambin exponen esta propiedad.
Para ms informacin vea la descripcin de esta propiedad en las propiedades de DataSet.

RelatedTable y Table
La propiedad Table devuelve el DataTable hijo de la restriccin. La propiedad RelatedTable devuelve el
DataTable padre de la restriccin. Ambas propiedades son de solo lectura.

107

ADO.NET 2.0

108

Escuela de Informtica del Ejrcito

Trabajo con datos relacionales

Las bases de datos raramente son estructuras independientes. Si examina las tablas del ejemplo
Northwind de SQL Server ver que estn interrelacionadas; ninguna tabla es independiente. No todas las
bases de datos tienen un porcentaje tan alto de tablas relacionadas, pero todas contienen relaciones.
Cuando construya aplicaciones se encontrar con escenarios en los que querr mostrar o acceder mediante
programacin datos de tablas que en la base de datos estn relacionadas.
Cuando trabaje con datos de mltiples DataTable probablemente necesite cuatro tipos de caractersticas
para navegacin, validacin, agregacin y propagacin en cascada.
Los usuarios querrn desplazarse entre tablas para localizar filas relacionadas, como los pedidos de un
cliente concreto. Querrn validad sus datos para asegurarse de que no crean filas hurfanas en la base de
datos. Las aplicaciones a menudo precisarn informacin agregadas, por ejemplo para ver el nmero total
de elementos en un pedido y su coste total. Y cuando se modifique una fila madre querr que los cambios
se propaguen en cascada a las filas hijas, por ejemplo, si se borra un pedido probablemente querr borrar
las filas del pedido.
En este captulo veremos cmo usar la clase DataRelation de ADO.NET para trabajar con datos de
objetos DataTable relacionados. Tambin discutiremos con mayor detalle las caractersticas de la clase
ForeignKeyConstraint. Veremos como usar DataRelation para desplazarnos entre filas y generar
informacin agregada con DataColumn basados en expresiones. Tambin veremos cmo usar la clase
ForeignKeyConstraint par validad datos y propagar cambios.

6.1 Introduccin al acceso a datos relacional


Obviamente, ADO.NET no ha inventado el acceso a datos relacional. ADO.NET va precedido por otras
formas de procesar datos de tablas relacionadas. Vamos a repasar algunos de los mtodos ms habituales
de trabajar con tablas relacionales y las compararemos rpidamente con el uso de un objeto DataRelation.

6.1.1

Consultas de unin

Las consultas de unin son previas a todas las tecnologas de acceso a datos de Microsoft. Son una
forma estndar y sencilla para recuperar datos de mltiples tablas en una sola consultas. La siguiente
consulta recupera datos de las tablas Customers, Orders y Order Details de la base de datos Northwind.
SELECT C.CustomerID, C.CompanyName, C.ContactName, C.Phone, O.OrderID, O.EmployeeID,
O.OrderDate, D.ProductID, D.Quantity, D.UnitPrice
FROM Customers C, Orders O, [Order Details] D
WHERE C.CustomerID = O.CustomerID AND O.OrderID = D.OrderID

Las ventajas de este tipo de consulta incluyen:


Son un estndar ampliamente aceptado. Todo programador sabe como usar consultas de unin
Devuelven sus resultados en una nica estructura.
Son fciles de filtrar. Si quiere datos solo para clientes de un pas concreto sencillamente aada un
filtro a la consulta.
Y entre sus desventajas se encuentran:
Pueden devolver datos redundantes. Si un cliente tiene 100 pedidos, la informacin de cliente
comn aparecer 100 veces.
Los resultados son difciles de actualizar. Es difcil para un modelo de acceso a datos como
ADO.NET saber cmo interpretar cambios en los resultados de una consulta de unin. Por ejemplo, si
borra una fila, esto significa que quiere borrar solo la fila correspondiente en la tabla hija, o tambin
la de la tabla madre?. Si aade una nueva fila, dnde quiere insertar la nueva fila?.
Es difcil mantener los resultados sincronizados. Si modifica una fila madre, por ejemplo
cambiando el nombre de contacto de un cliente, tendr que enviar el cambio a la base de datos y
ejecutar de nuevo la consulta para que el cambio se refleje en el resultado.

6.1.2

Consultas separadas

Como las consultas de unin siempre ha sido difciles de actualizar usando tecnologas de acceso a
datos, muchos desarrolladores usan consultas separadas para recuperar datos de cada una de las tablas y
colocarlos en estructuras separadas.
Las ventajas de usar consultas separadas incluyen:
Devuelven un total de datos que el de las consultas de unin.
Son ms aptas para actualizaciones. Como est modificando una estructura que se corresponde
con una sola tabla, es fcil interpretar los cambios a la hora de modificar la base de datos.
109

ADO.NET 2.0

Son aptas para varios orgenes de datos. Puede usar este mtodo si las tablas relacionadas se
encuentran en varios sistemas de bases de datos.
Entre sus inconvenientes se encuentran:
Necesitan cdigo de sincronizacin. Para localizar los pedidos de un cliente concreto debe aplicar
un filtro al conjunto de resultados y escribir cdigo para mantener los conjuntos de resultados
sincronizados.
Son difciles de filtrar. Construir consultas contra tablas hijas que recuperen solo las filas
relacionadas con las filas recuperadas de la tabla madre puede ser un reto. Examinaremos este tema
ms adelante.

6.1.3

Objetos DataRelation

Entre las ventajas de usar objetos DataRelation se encuentran:

Devuelven un total de datos menor que la consulta de unin.


Simplifican la localizacin de datos relacionados.
Hacen innecesario el cdigo de sincronizacin complejo.
Pueden manejar escenarios de actualizacin avanzados. Por ejemplo, puede enviar nuevos
clientes antes que los nuevos pedidos, pero borrar los pedidos existentes antes de borrar los clientes.
O si tiene una serie de pedidos y detalles de pedido pendientes puede obtener los valores de
autoincremento generados por los nuevos pedidos antes de enviar sus detalles. Trataremos ambos
casos en el Captulo 10.
Son dinmicos. Puede crear, modificar y borrar objetos DataRelation mediante programacin antes
o despus de consultar las tablas.
Soportan la propagacin de cambios en cascada. Puede controlar si los cambios en una fila se
propagan a las filas hijas usando propiedades de la limitacin de clave externa asociada con el
DataRelation.
Soportan la creacin de jerarquas a partir de diferentes orgenes de datos.
La desventaja principal del uso de objetos DataRelation es:
Son difciles de filtrar. Desafortunadamente, los objetos DataRelation no simplifican la obtencin
solo de las filas hijas correspondientes a las filas madre. Veremos como manejar esta situacin ms
adelante.

6.2 Trabajar con objetos DataRelation en el cdigo


Puede recorrer mltiples tablas de datos, validar y agregar datos y propagar cambios en cascada usando
su propio cdigo, pero puede realizar todas estas funciones rpida y fcilmente con la ayuda del objeto
DataRelation de ADO.NET. Vamos a ver cmo crear y usar objetos DataRelation en el cdigo.

6.2.1

Crear objetos DataRelation

La clase DataRelation tiene unas cuantas caractersticas importantes, que puede configurar en sus
constructores. Cuando se crea un DataRelation se debe especificar un nombre de modo que se pueda
localizar el objeto en su coleccin, as como las columnas madres e hijas en que se basa la relacin. Para
simplificar la creacin el DataRelation tiene constructores separados que aceptan objetos DataColumn
simples o arrays de DataColumn.
El ejemplo estndar de creacin de una relacin se apoya en objetos DataTable que contienen
informacin de cliente y de pedido. Puede usar el siguiente fragmento de cdigo para crear este
DataRelation. En breve veremos cdigo que usa el DataRelation para recorrer DataRow relacionados.
//Crea un nuevo DataSet y aade objetos DataTable y DataColumn
DataSet ds = new DataSet();
DataTable tabla;
tabla = ds.Tables.Add("Clientes");
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("CompanyName", typeof(string));
tabla = ds.Tables.Add("Pedidos");
tabla.Columns.Add("OrderID", typeof(int));
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("OrderDate", typeof(DateTime));
//Aade una relacin entre las dos tablas
DataRelation relacin = new DataRelation("relClientePedido",
ds.Tables["Clientes"].Columns["CustomerID"],
ds.Tables["Pedidos"].Columns["CustomerID"]);
ds.Relations.Add(relacin);

110

Escuela de Informtica del Ejrcito

Si quiere definir una relacin basada en mltiples columnas puede usar un constructor de DataRelation
que acepta un array de objetos DataColumn como se muestra en el siguiente fragmento.
//Crea un nuevo DataSet y aade objetos DataTable y DataColumn
DataSet ds = new DataSet();
DataTable tablaMadre, tablaHija;
tablaMadre = ds.Tables.Add("TablaMadre");
tablaMadre.Columns.Add("ColumnaMadre1", typeof(string));
tablaMadre.Columns.Add("ColumnaMadre2", typeof(string));
tablaHija = ds.Tables.Add("TablaHija");
tablaHija.Columns.Add("ColumnaHija1", typeof(string));
tablaHija.Columns.Add("ColumnaHija2", typeof(string));
//Crea arrays de columnas en los que se basar la relacin
DataColumn[] columnasMadre, columnasHija;
columnasMadre = new DataColumn[]{tablaMadre.Columns["ColumnaMadre1"],
tablaMadre.Columns["ColumnaMadre2"]};
columnasHija = new DataColumn[]{tablaHija.Columns["ColumnaHija1"],
tablaHija.Columns["ColumnaHija2"]};
//Crea el DataRelation
DataRelation relacin = new DataRelation("relVariasColumnas",
columnasMadre, columnasHija);
ds.Relations.Add(relacin)

El DataRelation tambin tiene un par de constructores cuyas firmas se corresponden con las que
acabamos de examinar, pero que adems admiten un cuarto parmetro para indicar si crear restricciones
para imponer integridad referencial en base a la nueva relacin. De forma predeterminar, al crear un nuevo
DataRelation se aaden restricciones a los objetos DataTable si no existen ya. Veremos esta funcionalidad
en breve.
Una vez creado un DataRelation debe aadirlo a la coleccin Relations del DataSet. Como al crear
objetos DataTable y DataColumn puede crear un nuevo DataRelation y aadirlo a la coleccin Relations del
DataSet en una sola llamada; en nuestro primer ejemplo podramos sustituir la creacin y el aadido por:
ds.Relations.Add("relClientePedido", ds.Tables["Clientes"].Columns["CustomerID"],
ds.Tables["Orders"].Columns["CustomerID"]);

6.2.2

Localizar datos relacionados

Uno de los usos principales de la clase DataRelation es localizar datos relacionados en tablas diferentes.
Sin embargo DataRelation no se encarga de esta tarea directamente. Esta funcionalidad est disponible por
medio de los mtodos GetChildRows, GetParentRow y GetParentRows de la clase DataRow. Cmo entra
DataRelation en la ecuacin?. Cuando se llama a cualquiera de estos mtodos es necesario especificar
como parmetro un objeto DataRelation.

Mtodo GetChildRows de la clase DataRow


La localizacin de las filas hijas en otro DataTable es directa. Simplemente llame al mtodo
GetChildRows de su DataRow y pase el nombre del objeto DataRelation que define la relacin entre los
objetos DataTable. Tambin puede pasar el propio objeto DataRelation en lugar del nombre del objeto. El
mtodo GetChildRows devuelve los datos relacionados como un array de objetos DataRow.
El siguiente fragmento de cdigo construye un DataSet con informacin bsica de cliente y pedido con
un DataRelation, llena el DataSet por medio de DataAdapter, llama al mtodo GetChildRows y recorre los
datos que devuelve.
//Crea un nuevo DataSet y aade objetos DataTable y DataColumn
DataSet ds = new DataSet();
DataTable tabla;
tabla = ds.Tables.Add("Clientes");
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("CompanyName", typeof(string));
tabla = ds.Tables.Add("Pedidos");
tabla.Columns.Add("OrderID", typeof(int));
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("OrderDate", typeof(DateTime));
//Aade un objeto DataRelation entre las dos tablas
DataRelation relacin;
relacin = ds.Relations.Add("relClientePedido",
ds.Tables["Clientes"].Columns["CustomerID"],
ds.Tables["Pedidos"].Columns["CustomerID"]);

111

ADO.NET 2.0

//Rellena el DataSet
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT CustomerID, CompanyName FROM Customers;" +
"SELECT OrderID, CustomerID, OrderDate FROM Orders";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.TableMappings.Add("Table", "Clientes");
adaptador.TableMappings.Add("Table1", "Pedidos");
adaptador.Fill(ds);
//Recorre los clientes
foreach(DataRow filaCliente in ds.Tables["Clientes"].Rows) {
Console.WriteLine("{0} - {1}", filaCliente["CustomerID"],
filaCliente["CompanyName"]);
//Recorre los pedidos relacionados
foreach(DataRow filaPedido in filaCliente.GetChildRows(relacin))
Console.WriteLine("\t{0} {1:dd/MM/yyyy}", filaPedido["OrderID"],
filaPedido["OrderDate"]);
Console.WriteLine();
}

Mtodo GetParentRow de la clase DataRow


Los objetos DataRelation permiten no solo profundizar en una jerarqua, sino tambin subir por ella. La
clase DataRow expone un mtodo GetParentRow al que puede llamar para localizar la fila madre de una fila
en base a un DataRelation dentro del DataSet. Como GetChildRows, GetParentRow acepta un objeto
DataRelation o una cadena que contiene el nombre del DataRelation que quiere utilizar. El siguiente cdigo
recorre las filas hijas (pedidos) y muestra informacin sobre la fila madre correspondiente (cliente). Por
brevedad, asumimos que se ha usado el ejemplo anterior para crear y cargar el DataSet.
DataRow filaCliente;
//Recorre los pedidos;
foreach(DataRow filaPedido in ds.Tables["Pedidos"].Rows){
//Localiza la fila madre relacionada
filaCliente = filaPedido.GetParentRow("relClientePedido");
Console.WriteLine("{0} {1:dd/MM/yyyy} {2}", filaPedido["OrderID"],
filaPedido["OrderDate"], filaCliente["CompanyName"]);
}

Acceder a varias filas madre con el mtodo GetParentRows de DataRow


En la mayora de las situaciones un DataRow tendr como mximo una sola fila madre en el DataTable
relacionado. Sin embargo, si el DataTable no impone restricciones, una fila de la tabla hija puede hacer
referencia a varias filas de la DataTable madre. Si est trabajando en este escenario y quiere acceder a
todas las DataRow madre, el mtodo GetParentRows acepta los mismos parmetros que los mtodos
GetChildRows y GetParentRow, pero devuelve un array de DataRow.
El siguiente fragmento de cdigo es un ejemplo de cmo puede usar esta caracterstica. Como no hay
relacin entre las tablas de una base de datos SQL Server que lleve de forma natural a esta posibilidad, nos
inventaremos una para nuestra necesidad. Vamos a invertir la relacin estndar entre clientes y pedidos de
modo que el cliente sea la tabla hija y GetParentRows devuelva varios pedidos.
// Crea un nuevo DataSet y aade objetos DataTable y DataColumn
// Como anterior
//Aade un objeto DataRelation entre las dos tablas
DataRelation relacin;
relacin = ds.Relations.Add("relClientePedido",
ds.Tables["Pedidos"].Columns["CustomerID"],
ds.Tables["Clientes"].Columns["CustomerID"]);
// Rellena el DataSet
// Como anterior
// Recorre los clientes
foreach(DataRow filaCliente in ds.Tables["Clientes"].Rows) {
Console.WriteLine("{0} - {1}", filaCliente["CustomerID"],
filaCliente["CompanyName"]);
//Recorre los pedidos relacionados
foreach(DataRow filaPedido in filaCliente.GetParentRows(relacin))
Console.WriteLine("\t{0} {1:dd/MM/yyyy}", filaPedido["OrderID"],
filaPedido["OrderDate"]);
Console.WriteLine();
}

112

Escuela de Informtica del Ejrcito

Elegir la versin de los datos que ve


Imagine que ya ha creado una aplicacin que permite que los usuarios recuperen datos de su base de
datos y los modifiquen. Pero los empleados que van a usar la aplicacin son propensos a errores. De modo
que la aplicacin utiliza alguna funcionalidad expuesta por el DataSet para almacenar los cambios en un
archivo en lugar de enviarlos a la base de datos (veremos como en el Captulo 11).
Como resultado, debe crear una segunda aplicacin que permita que los supervisores revisen los
cambios pendientes de la primera aplicacin. Esta aplicacin de auditora mostrara tanto el valor original
como el propuesto para las filas modificadas en el DataRow.
Anteriormente vimos que es posible examinar las versiones actual y origina de columnas de un
DataRow. Los mtodos GetChildRows, GetParentRow y GetParentRows de DataRow tambin permiten
suministrar un valor de la enumeracin DataRowVersion para indicar a qu versin de los datos puede
acceder.
De modo que si quiere recorrer los clientes y mostrar los valores originales de cada pedido hecho por el
cliente, puede reemplazar el cdigo de recorrido por el siguiente:
//Recorre los clientes
foreach(DataRow filaCliente in ds.Tables["Clientes"].Rows) {
Console.WriteLine("{0} - {1}", filaCliente["CustomerID"],
filaCliente["CompanyName"]);
//Muestra el estado original de los pedidos hijos relacionados
foreach(DataRow filaPedido in
filaCliente.GetChildRows(relacin, DataRowVersion.Original))
Console.WriteLine("\t{0} {1:dd/MM/yyyy}", filaPedido["OrderID"],
filaPedido["OrderDate"]);
Console.WriteLine();
}

6.2.3

Validar datos con objetos DataRelation

Ahora que ha aprendido cmo usar objetos DataRelation para recorrer los datos de DataTable
relacionados vamos a ver una de las principales funciones de los objetos DataRelation validar datos.
Cuando se define una relacin entre dos objetos DataTable por lo general se quiere asegurar de que no
se permiten datos hurfanos en las DataTable hijas es decir, quiere impedir que los usuarios introduzcan
en la tabla de pedidos una fila que no corresponda a una tabla de la fila de clientes. Puede usar un
DataRelation para imponer restricciones en los objetos DataTable relacionados.

Crear restricciones
De forma predeterminada, cuando se crea un objeto DataRelation tambin se crea una UniqueConstraint
en el DataTable madre y una ForeignKeyConstraint en la DataTable hija. El siguiente fragmento crea un
DataRelation y muestra el nmero de restricciones definidas en ambas tablas.
//Crea un nuevo DataSet y aade objetos DataTable y DataColumn
DataSet ds = new DataSet();
DataTable tabla;
tabla = ds.Tables.Add("Clientes");
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("CompanyName", typeof(string));
tabla = ds.Tables.Add("Pedidos");
tabla.Columns.Add("OrderID", typeof(int));
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("OrderDate", typeof(DateTime));
Console.WriteLine("Antes de aadir la relacin");
Console.WriteLine("La tabla madre tiene {0} restricciones",
ds.Tables["Clientes"].Constraints.Count);
Console.WriteLine("La tabla hija tiene {0} restricciones",
ds.Tables["Pedidos"].Constraints.Count);
Console.WriteLine();
//Aade un DataRelation entre las dos tablas
ds.Relations.Add("relClientePedido",
ds.Tables["Clientes"].Columns["CustomerID"],
ds.Tables["Pedidos"].Columns["CustomerID"]);
Console.WriteLine("Despus de aadir la relacin");
Console.WriteLine("La tabla madre tiene {0} restricciones",
ds.Tables["Clientes"].Constraints.Count);
Console.WriteLine("La tabla hija tiene {0} restricciones",
ds.Tables["Pedidos"].Constraints.Count);

113

ADO.NET 2.0

Si el DataSet ya contiene limitaciones que se corresponden con las UniqueConstraint y


ForeignKeyConstraint que el DataRelation creara implcitamente, el DataRelation simplemente las usa.

Restricciones de clave externa y valores null


Tal vez le sorprende saber que incluso definiendo una restriccin de clave externa es posible tener datos
hurfanos tanto en la base de datos como en el DataSet. No me cree?. Ejecute la siguiente consulta
contra la base de datos Northwind:
UPDATE Orders SET CustomerID = NULL WHERE CustomerID = ALFKI

La consulta tendr xito, y ahora tendr pedidos en la tabla Orders que no pertenecen a ningn cliente
de la tabla Customers. Para devolver las filas a su estado anterior ejecute la siguiente consulta:
UPDATE Orders SET CustomerID = ALFKI WHERE CustomerID IS NULL

Para probar que existe una restriccin de clave externa en la tabla Orders, ejecute la siguiente consulta,
que fracasar si la tabla Customers no tiene una fila cuyo CustomerID sea ZZZZZ:
UPDATE Orders SET CustomerID = ZZZZZ WHERE CustomerID = ANTON

Las filas que contienen valores null en al menos una de las columnas que forman parte de la clave
externa estn exentas de la restriccin. Igualmente, el DataSet permite filas hijas si los valores de las
DataColumn usadas para definir la relacin contienen DBNull.Value. Tenga esto en mente cuando defina el
esquema de su base de datos y su DataSet.

Sin restricciones
Ha aprendido que cuando crea un DataRelation ADO.NET de forma predeterminada se asegura de que
el DataSet contiene un UniqueConstraint y un ForeignConstraint cuyas firmas corresponden con las del
DataRelation. Si estas restricciones ya existen dentro del DataSet, el nuevo DataRelation las usa; en caso
contrario ADO.NET las crea implcitamente.
Sin embargo, hay otra opcin. Anteriormente, cuando discutimos los constructores de la clase
DataRelation mencionamos que hay constructores que permiten especificar que no quiere que ADO.NET
cree restricciones para su DataRelation. Estos constructores se parecen a los que ya hemos usado, pero
reciben un parmetro booleano adicional que indica si se quiere crear restricciones. Puede usar estos
constructores cuando cree un DataRelation pero no quiera tener las restricciones correspondientes en el
DataSet. En este ejemplos se crea una relacin entre clientes y pedidos pero sin crear restricciones:
ds.Relations.Add("relClientePedido",
ds.Tables["Clientes"].Columns["CustomerID"],
ds.Tables["Pedidos"].Columns["CustomerID"], false);

6.2.4

Objetos DataRelation autoreferencia

En ocasiones las tablas madre e hija en una relacin son la misma. Vea por ejemplo la tabla Employees
de la base de datos Northwind. Esta tabla tiene una columna EmployeeID que contiene el identificador del
empleado, y una columna ReportsTo que contiene el ID del jefe del empleado. La tabla Employees tiene
definida una restriccin de clave externa definida sobre la columna ReportsTo para asegurar que solo
acepta valores de la columna EmployeeID.
El siguiente ejemplo recupera datos de la tabla de empleados en un DataSet y crea un DataRelation
autoreferencia.
DataSet ds = new DataSet();
DataTable tabla;
tabla = ds.Tables.Add("Empleados");
tabla.Columns.Add("EmployeeID", typeof(int));
tabla.Columns.Add("LastName", typeof(string));
tabla.Columns.Add("ReportsTo", typeof(int));
ds.Relations.Add("relEmpleadoJefe", tabla.Columns["EmployeeID"],
tabla.Columns["ReportsTo"]);

Crear la relacin es solo media batalla. El objetivo es mostrar los empleados en forma de rbol. Recorrer
la jerarqua para mostrar los empleados de un mismo jefe es un poco complicado, especialmente si nunca
ha escrito cdigo recursivo.
El siguiente fragmento de cdigo rellena el DataTable creado en el fragmento anterior y recorre su
contenido para localizar y mostrar el empleado de nivel ms alto. En la tabla Employees esta es la fila cuya
columna ReportsTo vale null.
Despus de localizar el empleado de mayor rango, el cdigo muestra sus subordinados inmediatos, con
un tabulador delante de su nombre. Despus de mostrar el nombre de un empleado usa recursin para
mostrar sus subordinados inmediatos. El proceso continua hasta que no hay subordinados que mostrar:
114

Escuela de Informtica del Ejrcito

static void Main(string[] args) {


//Rellena el DataTable con informacin de empleados
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT EmployeeID, LastName + ', ' + FirstName AS " +
"EmployeeName, ReportsTo FROM Employees";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataSet ds = new DataSet();
DataTable tabla = ds.Tables.Add("Empleados");
adaptador.Fill(tabla);
DataRelation relacin = ds.Relations.Add("relEmpleadoJefe",
tabla.Columns["EmployeeID"], tabla.Columns["ReportsTo"]);
//Muestra la jerarqua de empleados
foreach(DataRow fila in tabla.Rows)
if(fila.IsNull("ReportsTo"))
MuestraFila(fila, relacin, "");
}
static void MuestraFila(DataRow fila, DataRelation relacin, string indentado) {
Console.WriteLine(indentado + fila["EmployeeName"]);
foreach(DataRow filaHija in fila.GetChildRows(relacin))
MuestraFila(filaHija, relacin, indentado + "\t");
}

En nuestro ejemplo el empleado de rango ms alto es el que tiene null en la columna ReportsTo. Este es
un convenio utilizado en la base de datos Northwind, pero que no tiene por que darse en otros casos. Otra
posibilidad es que el usuario se haga referencia a si mismo. Cuando se emplee este tipo de relaciones debe
conocerse el convenio utilizado para el elemento de rango ms alto y adaptar el cdigo en consecuencia.

6.2.5

Relaciones varios a varios

La mayor parte de las relaciones en bases de datos son de uno a varios. Un cliente puede hacer varios
pedidos. Un pedido puede tener varios detalles de pedido. Un empleado puede tener varios subordinados
directos. Existen relaciones varios a varios, pero son un poco ms difciles de definir en una base de datos.
Para comprender porqu vamos a examinar las tablas de ttulos y autores de la base de datos pubs de SQL
Server.
La relacin entre los datos de estas dos tablas se puede considerar varios a varios, porque un autor
puede haber escrito varios ttulos y un ttulo puede tener varios autores. Sin embargo, estas dos tablas no
estn relacionadas por medio de una restriccin de clave externa porque este tipo de restriccin precisa una
clave nica. Este nivel de indireccin impide que una fila hija tenga varias filas madre en la tabla
relacionada, lo que significa que no tenemos una relacin varios a varios directa.
La base de datos pubs contiene otra tabla llamada titleauthor que ayuda a crear una relacin varios a
varios indirecta. La tabla titleauthor tiene una clave principal compuesta formada por las columnas au_id y
title_id las columnas de clave principal de las tablas authors y titles respectivamente.
Supongamos que tenemos un ttulo con dos autores. La tabla titleauthor contendr dos filas para este
ttulo, una por cada autor. De esta forma puede usar la tabla titleauthor para encontrar los valores de clave
para todos los autores de un ttulo concreto. De forma similar puede usar la tabla para encontrar el valor de
la clave principal para todos los ttulos escritos por un autor, solo o en colaboracin.
El siguiente cdigo recupera datos de las tres tablas. Aade objetos DataRelation entre las tablas
authors y titleauthor y entre titles y titleauthor. A continuacin recorre las filas de autores, mostrando cada
autor y usando los dos objetos DataRelation para mostrar sus ttulos.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=pubs;" +
"Integrated Security=true";
string consulta = "SELECT Au_ID, Au_LName, Au_FName FROM Authors;" +
"SELECT Title_ID, Title FROM Titles;" +
"SELECT Au_ID, Title_ID FROM TitleAuthor;";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.TableMappings.Add("Table", "Autores");
adaptador.TableMappings.Add("Table1", "Titulos");
adaptador.TableMappings.Add("Table2", "TituloAutor");
DataSet ds = new DataSet();
adaptador.Fill(ds);
ds.Relations.Add("relAutorTitulo", ds.Tables["Autores"].Columns["Au_ID"],
ds.Tables["TituloAutor"].Columns["Au_ID"], false);

115

ADO.NET 2.0

ds.Relations.Add("relTituloAutor", ds.Tables["Titulos"].Columns["Title_ID"],
ds.Tables["TituloAutor"].Columns["Title_ID"], false);
foreach(DataRow filaAutor in ds.Tables["Autores"].Rows) {
Console.WriteLine("{0}, {1}", filaAutor["Au_LName"], filaAutor["Au_FName"]);
foreach(DataRow filaTituloAutor in filaAutor.GetChildRows("relAutorTitulo")) {
DataRow filaTitulo = filaTituloAutor.GetParentRow("relTituloAutor");
Console.WriteLine("\t{0}", filaTitulo["Title"]);
}
Console.WriteLine();
}

6.2.6

Objetos DataRelation en columnas basadas en expression

Al describir la clase DataColumn aprendimos a usar su propiedad Expression para mostrar el resultado
de operaciones sobre la base de datos. Tambin puede usar objetos DataColumn basados en expresin
junto con objetos DataRelation para calcular informacin agregada como el nmero de filas hijas y las
sumas y promedios de datos hijos.
El siguiente fragmento de cdigo incluye dos ejemplos de uso de DataRelation en la propiedad
Expression de un objeto DataColumn. El cdigo primero crea un DataRelation entre los objetos DataTable
de pedidos y detalles de pedido y despus aade el DataColumn Total al DataTable de detalles de pedido.
Finalmente el cdigo aade dos objetos DataColumn basados en expresin que utilizan la nueva relacin.
El primero de estos objetos DataColumn devuelve el nmero de filas hijas. Para obtener esta informacin
se utiliza el siguiente valor para la propiedad Expression:
Count(Child.ProductID)

Puede usar esta sintaxis para hacer referencia a datos hijos si el DataTable tiene solo un DataTable hijo.
Si tiene varios objetos DataTable hijos relacionados puede usar la siguiente forma:
Count(Child(NombreRelacin).ProductID);

La ltima DataColumn basada en expresin creada por el cdigo devuelve la suma de la columna Total
en el DataTable hijo. El valor de la propiedad Expression de este objeto DataColumn es similar al anterior:
Sum(Child.Total);
//Crea y llena un DataSet con informacin de pedido de un cliente
DataSet ds = new DataSet();
string cadcon = @"Data Source=.\SQLExpress;Integrated Security=true;" +
"Initial Catalog=Northwind";
string consulta = "SELECT OrderID, OrderDate FROM Orders WHERE CustomerID='ALFKI';" +
"SELECT D.OrderID, D.ProductID, D.UnitPrice, D.Quantity FROM [Order Details] D, " +
"Orders O WHERE D.OrderID = O.OrderID AND O.CustomerID = 'ALFKI';";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.TableMappings.Add("Table", "Pedidos");
adaptador.TableMappings.Add("Table1", "Detalles");
adaptador.Fill(ds);
//Aade columnas basadas en expresin
DataRelation relacin;
relacin = ds.Relations.Add("relPedidoDetalle",
ds.Tables["Pedidos"].Columns["OrderID"], ds.Tables["Detalles"].Columns["OrderID"]);
ds.Tables["Detalles"].Columns.Add("Total", typeof(Decimal), "Quantity * UnitPrice");
ds.Tables["Pedidos"].Columns.Add("NumElementos", typeof(int),
"Count(Child.ProductID)");
ds.Tables["Pedidos"].Columns.Add("TotalPedido", typeof(Decimal), "Sum(Child.Total)");
//Muestra datos usando las columnas basadas en expresin
foreach(DataRow filaPedido in ds.Tables["Pedidos"].Rows){
Console.WriteLine("OrderID: {0} NumElementos: {1} TotalPedido: {2:C}",
filaPedido["OrderID"], filaPedido["NumElementos"], filaPedido["TotalPedido"]);
foreach(DataRow filaDetalle in filaPedido.GetChildRows(relacin))
Console.WriteLine("\tCantidad: {0,2} Precio: {1,7:C} Total: {2,7:C}",
filaDetalle["Quantity"], filaDetalle["UnitPrice"], filaDetalle["Total"]);
Console.WriteLine();
}

Puede usar objetos DataColumn basados en expresin para obtener informacin sobre la DataTable
madre en una relacin. Anteriormente en este captulo utilizamos una relacin varios a varios entre autor y
ttulo en la base de datos pubs. El objetivo del cdigo era mostrar los ttulos escritos o participados por cada
autor.
116

Escuela de Informtica del Ejrcito

Podemos simplificar ligeramente este cdigo creando en la tabla intermedia (titleauthor) columnas
calculadas para recuperar informacin de autor y de ttulo de las tablas relacionadas.
//Crea y llena un DataSet con informacin de autor y de ttulo
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=pubs;" +
"Integrated Security=true";
string consulta = "SELECT Au_ID, Au_LName, Au_FName FROM Authors;" +
"SELECT Title_ID, Title FROM Titles;" +
"SELECT Au_ID, Title_ID FROM TitleAuthor;";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.TableMappings.Add("Table", "Autores");
adaptador.TableMappings.Add("Table1", "Titulos");
adaptador.TableMappings.Add("Table2", "TituloAutor");
DataSet ds = new DataSet();
adaptador.Fill(ds);
ds.Relations.Add("relAutorTitulo", ds.Tables["Autores"].Columns["Au_ID"],
ds.Tables["TituloAutor"].Columns["Au_ID"], false);
ds.Relations.Add("relTituloAutor", ds.Tables["Titulos"].Columns["Title_ID"],
ds.Tables["TituloAutor"].Columns["Title_ID"], false);
//Usa columnas basadas en expresin y la tabla intermedia de la relacin
//para mostrar informacin de autor y de ttulo
string cadExpresin;
DataTable tabla = ds.Tables["TituloAutor"];
cadExpresin = "Parent(relTituloAutor).Title";
tabla.Columns.Add("Ttulo", typeof(string), cadExpresin);
cadExpresin = "Parent(relAutorTitulo).Au_LName + ', ' + " +
"Parent(relAutorTitulo).Au_FName";
tabla.Columns.Add("Autor", typeof(string), cadExpresin);
foreach(DataRow filaAutor in ds.Tables["Autores"].Rows) {
Console.WriteLine("{0} {1}", filaAutor["Au_LName"], filaAutor["Au_FName"]);
foreach(DataRow filaTituloAutor in filaAutor.GetChildRows("relAutorTitulo"))
Console.WriteLine("\t{0}", filaTituloAutor["Ttulo"]);
Console.WriteLine();
}

Puede consultar la lista completa de funciones agregadas utilizables en la propiedad Expression en la


documentacin MSDN.

6.2.7

Propagar cambios

En ocasiones los cambios hechos en una fila deben, o deberan, repercutir en datos relacionados. Por
ejemplo, cuando se borra un pedido probablemente se deban borrar los elementos de pedido asociados.
Diferentes sistemas de bases de datos permiten gestionar esta situacin de formas diferentes. La tabla
Order Details de la base de datos Northwind usa una restriccin de clave externa para impedir que los
usuarios borren filas de la tabla Orders si hay filas relacionadas en la tabla Order Details. SQL Server 2000
aadi el soporte para propagacin de cambios usando una restriccin de clave externa. Puede definir sus
restricciones de clave externa de modo que cuando un usuario borre o actualice una fila el cambio se
propague automticamente a las filas relacionadas.
La clase ForeignKeyConstraint de ADO.NET tiene caractersticas similares. Expone las propiedades
DeleteRule y UpdateRule que puede usar para controlar lo que sucede cuando se modifican filas de datos
en una tabla madre con una restriccin de clave externa.

Propiedades DeleteRule y UpdateRule de la clase ForeignKeyConstraint


Las propiedades DeleteRule y UpdateRule aceptan valores de la enumeracin Rule en el espacio de
nombres System.Data. De forma predeterminada ambas propiedades tienen el valor Cascade. Esto significa
que cuando se borra una fila en un DataTable tambin se borran las filas hijas en el DataTable relacionado.
Si se modifica el valor de la columna del DataTable padre sobre la cual est definida la restriccin de clave
externa por ejemplo, si se cambia el valor de la columna CustomerID en el DataTable de clientes se
actualizar el valor de la columna en las filas relacionadas del DataTable hijo.
Esta propiedad tambin puede tener los valores None, SetDefault o SetNull. Si la propiedad DeleteRule
tiene el valor None impide el borrado de datos que afecten al DataTable hijo. Si quiere que el proceso de
borrar una fila madre cambie las columnas implicadas en la restriccin en la tabla hija a null, utilice el valor
SetNull en la propiedad DeleteRule de ForeignKeyConstraint. Si usamos el valor SetNull en la propiedad
UpdateRule un cambio en las columnas participantes en la restriccin en la fila madre har que las
columnas correspondientes en las filas hijas cambien a null. El valor SetDefault en las reglas de
actualizacin o de borrado devuelve las columnas de la tabla hija a su valor predeterminado.
117

ADO.NET 2.0

6.2.8

Apartarse de las consultas de unin

Muchos desarrolladores confan en las consultas de unin para recuperar varias tablas. Puede usar un
DataTable para almacenar los resultados de una consulta que devuelve datos de varias tablas. Por lo
general, no lo recomiendo. Como ver en el Captulo 9, la clase SqlDataAdapter est diseada para
examinar los cambios almacenados en un nico DataTable y enviarlos a una tabla especfica de la base de
datos. Por tanto, si quiere modificar los contenidos de sus objetos DataTable querr separar los datos en
objetos diferentes paralelos a las tablas de la base de datos.

6.3 Crear objetos DataRelation en Visual Studio


Ahora que comprende las caractersticas principales de la clase DataRelation vamos a ver cmo crear
objetos de esta clase usando Visual Studio.

6.3.1

Aadir un DataRelation a un DataSet con tipo

Trataremos los DataSet con tipo en el Captulo 8. Si ha trabajado con ellos en versiones anteriores de
Visual Studio le agradar saber que el diseador ahora consulta a SQL Server la informacin de clave
externa para crear automticamente DataRelation cuando se incluyen tablas relacionadas.

6.3.2

Aadir un DataRelation a un DataSet sin tipo

Tambin puede aadir objetos DataRelation a DataSet sin


tipo. Anteriormente hemos visto como aadir un DataSet a un
diseador, como un formulario Windows, y despus aadir
objetos DataTable y DataColumn al nuevo DataSet. Aadir
DataRelation es igual de fcil.
Seleccione el DataSet en la bandeja del diseador de
componentes y seleccione la propiedad Relations en la
ventana de propiedades. Ver un botn a la derecha de la
propiedad; al pulsarlo se abrir el editor de la coleccin de
relaciones.
El editor de la coleccin de relaciones le permite aadir,
editar y eliminar objetos DataRelation. Si elige agregar un
nuevo DataRelation ver el dilogo de edicin de relacin de
la figura 7.1.
El dilogo de edicin de relacin refleja los constructores
de la clase DataRelation, permitiendo especificar los objetos
DataTable y DataColumn de la nueva relacin. Tambin
puede especificar valores para las propiedades UpdateRule,
DeleteRule y AcceptRule de la ForeignKeyConstraint
asociada con la nueva relacin.

Fig.7 1: Editor de relacin

6.4 Referencia del objeto DataRelation


La informacin de referencia del objeto DataRelation no es especialmente interesante. El objeto no
expone mtodos ni eventos solamente propiedades.

6.4.1

Propiedades de la clase DataRelation

La mayor parte de las propiedades de la clase DataRelation son de solo lectura; slo puede configurar
sus valores usando los constructores. La tabla 7.1 describe las propiedades de uso habitual.
Tabla 7. 1: Propiedades de la clase DataRelation

Propiedad
ChildColumns
ChildKeyConstraint
ChildTable
DataSet
ExtendedProperties
Nested

Tipo
DataColumn[ ]
ForeignKeyConstraint
DataTable
DataSet
PropertyCollection
Boolean

ParentColumns
ParentKeyConstraint

DataColumn[ ]
UniqueConstraint

ParentTable
RelationName

DataTable
String

Descripcin
Indica las columnas hijas que componen la relacin. De solo lectura
Indica la restriccin de clave externa de la tabla hija. De solo lectura.
Indica la tabla hija en la relacin. De solo lectura.
Indica el DataSet en el que reside la DataRelation. De solo lectura
Coleccin de propiedades dinmicas.
Indica si las filas hijas son elementos hijos cuando se almacena el
DataSet como XML.
Indica las columnas madre que definen la relacin. De solo lectura.
Indica la restriccin nica en la tabla madre de la relacin. De solo
lectura.
Indica la tabla madre en la relacin. De solo lectura.
Nombre de la relacin.

ChildColumns
La propiedad ChildColumns devuelve un array que contiene el objeto u objetos DataColumn de la
DataTable hija en el DataRelation. Es de solo lectura.
118

Escuela de Informtica del Ejrcito

ChildKeyConstraint
La propiedad ChildKeyConstraint devuelve el ForeignKeyConstraint al que hace referencia el
DataRelation. Si ha creado un DataRelation que no usa restricciones devolver null. Es de solo lectura.

ChildTable
Devuelve el DataTable hijo en la relacin. Es de solo lectura.

DataSet
Devuelve el DataSet en el que reside este DataRelation. Es de solo lectura.

ExtendedProperties
Esta propiedad se comporta como la homloga en DataSet, DataTable y DataColumn. Permite
almacenar informacin adicional.

Nested
La propiedad Nested controla la ubicacin de los contenidos del DataTAble hijo cuando se almacena el
DataSet en formato XML usando su mtodo WriteXml. Es una de las pocas propiedades de lectura y
escritura de DataRelation.
Si la propiedad Nested vale false, que es el valor predeterminado, los datos del DataTable hijo se
separarn de los datos en el DataTable padre. Como puede ver, sin conocer la informacin de esquema del
DataSet, no se sabe que hay una relacin entre los dos objetos DataTable.
Si se asigna a la propiedad Nested el valor true ADO.NET incrusta los datos de la tabla hija entre los
datos de la tabla madre.

ParentColumns
Devuelve un array que contiene los objetos DataColumn de la tabla madre de la relacin. Es de solo
lectura.

ParentKeyConstraint
Devuelve el UniqueConstraint al que hace referencia el DataRelation. Si se crea un DataRelation que no
usa restricciones esta propiedad devuelve null. Es de solo lectura.

ParentTable
Devuelve el DataTable padre en la relacin. Es de solo lectura.

RelationName
Esta propiedad es de lectura y escritura y se puede usar para establecer o recuperar el nombre del
DataRelation.

119

ADO.NET 2.0

120

Escuela de Informtica del Ejrcito

Ordenar, buscar y filtrar

En captulos anteriores hemos aprendido a usar un SqlDataAdapter para obtener los resultados de
nuestras consultas y cargarlos en un objeto DataSet. Tambin hemos visto cmo examinar los resultados
de estas consultas recorriendo los objetos DataRow de un DataTable.
Pero cmo localizar una fila concreta en un DataTable?. Cmo aplicar un filtro de modo que solo sean
visibles las filas que satisfacen un criterio concreto?. Y cmo controlar el orden en que se muestran o
acceden las filas?.
Este captulo responder estas preguntas y ms. Cubre el mtodo Find de la clase DataRowCollection y
el mtodo Select de la clase DataTable y presenta las clases DataView y DataRowView.

7.1 Caractersticas de bsqueda y filtrado de DataTable


La clase DataTable expone dos mtodos que puede usar para localizar datos en base a criterios de
bsqueda. Un mtodo, Find, permite localizar una fila en base a los valores de su clave principal. El otro,
Select, acta ms como un filtro, devolviendo mltiples filas de datos segn criterios de bsqueda ms
flexibles.

7.1.1

Localizar una fila por su clave principal

Cuando se est consultando informacin a la base de datos a menudo se quieren recuperar filas
especficas en base a los valores de la clave principal, usando consultas como esta:
SELECT CustomerID, CompanyName, ContactName, Phone
FROM Customers WHERE Customer = ALFKI

Tambin puede localizar un DataRow en un DataTable en base a los valores de clave principal de la fila.
Aunque el mtodo Find est diseado para objetos DataTable, en realidad est expuesto por la clase
DataRowCollection. El mtodo Find acepta un objeto que contiene el valor de la clave principal que quiere
localizar. Como los valores de clave principal son nicos, este mtodos devuelve como mximo una fila. El
siguiente fragmento intenta localizar una fila de cliente segn su clave principal y determina si la ha
encontrado.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT CustomerID, CompanyName FROM Customers" +
"WHERE CustomerID LIKE A%";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tabla = new DataTable("Clientes");
adaptador.Fill(tabla);
tabla.PrimaryKey = new DataColumn[]{tabla.Columns["CustomerID"]};
DataRow fila = tabla.Rows.Find("ALFKI");
if(fila == null)
Console.WriteLine("No se ha encontrado la fila");
Else
Console.WriteLine(fila["CompanyName"]);

Tcnicamente, un DataTable puede contener varias filas con los mismos valores de clave principal. Si
asigna false a la propiedad EnforeConstraints del DataSet el DataTable no lanzar una excepcin si se viola
la restriccin de clave principal. En un escenario como este, el mtodo Find devolver la primera fila que
encuentre con los valores indicados.
El mtodo Find est sobrecargado para escenarios en los que la clave principal del DataTable consta de
varios objetos DataColumn. A menudo esto se denomina clave compuesta. Si est trabajando con una clave
compuesta pase al mtodo Find un array de objetos cuyos elementos correspondan a las DataColumn que
forman la clave. Por ejemplo, la clave principal de la tabla Order Details est basada en las columnas
OrderID y ProductID, por tanto la primera entrada en el array de objetos debe ser el valor para la columna
OrderID y la segunda el valor para la columna ProductID, como se ve a continuacin.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT OrderID, ProductID, Quantity, UnitPrice " +
"FROM [Order Details]";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tabla = new DataTable("Detalles");
adaptador.Fill(tabla);
tabla.PrimaryKey = new DataColumn[]{tabla.Columns["OrderID"],
tabla.Columns["ProductID"]};

121

ADO.NET 2.0

object[] criterio = new object[]{10643, 28};


DataRow fila = tabla.Rows.Find(criterio);
if(fila == null)
Console.WriteLine("No se ha encontrado la fila");
else
Console.WriteLine("{0} {1}", fila["Quantity"], fila["UnitPrice"]);

7.1.2

Bsquedas ms dinmicas

Localizar una fila en base a su clave principal es muy eficiente, pero no todas las bsquedas son tan
inmediatas. Qu pasa si quiere encontrar todos los clientes de los Estados Unidos que no estn en
Seattle?. Puede usar el mtodo Select de la clase DataTable para localizar filas basadas en este tipo de
criterios. Supongamos que recupera todos los contenidos de la tabla Customers y los almacena en un
DataTable.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT CustomerID, CompanyName, City, Country FROM Customers";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tabla = new DataTable("Clientes");
adaptador.Fill(tabla);
string filtro = "Country = USA AND City <> Seattle";
foreach(DataRow fila in tabla.Select(filtro))
Console.WriteLine("{0,-35} {1}, {2}",
fila["CompanyName"], fila["City"], fila["Country"]);

7.1.3

Bsquedas con comodines

ADO.NET permite realizar bsquedas con comodines. Puede usar % * como comodines al comienzo o
al final de una cadena de bsqueda. Por ejemplo, usando el siguiente filtro en el mtodo Select de
DataTable obtendr todos los clientes que residen en New Hampshire, New Jersey, New Mexico y New
York:
"State LIKE New %"

ADO.NET no permite usar comodines de un solo carcter.

7.1.4

Delimitadores

Habr observado que hasta ahora hemos especificado criterios basados en columnas de tipo cadena. En
todos los casos el valor est encerrado entre comillas simples. Este proceso parece sencillo en un ejemplo,
pero puede suponer un problema para los desarrolladores.
No puede limitarse a encerrar una cadena entre comillas sencillas en su criterio de bsqueda. Bueno; en
realidad si puede, pero no debe. Supongamos que la aplicacin permite que un usuario localice a un
empleado en base a su apellido. Cuando la aplicacin pide el apellido al usuario, ste escribe OMalley; si
se limita a rodear el valor con comillas sencillas, la cadena ser como esta:
"LastName = OMalley"

Como en la mayor parte de las consultas de base de datos, si el delimitador aparece dentro de la cadena
literal debe duplicarlo. En este caso, el criterio de bsqueda sera:
"LastName = OMalley"

Si compone el criterio de bsqueda dinmicamente, debe asegurarse de buscar el delimitador dentro de


la cadena. Use el mtodo Replace de la clase String para estas situaciones. El siguiente fragmento
compone una cadena para el mtodo Select y usa el mtodo Replace de String para reemplazar las
comillas sencillas por dos comillas sencillas en la cadena:
string apellido = "OMalley";
string criterio = string.Format("LastName = {0}", apellido.Replace(, ));

Cmo se delimitan fechas en el criterio de bsqueda?. Debe rodear la fecha con smbolos de
almohadilla (#), como se ve a continuacin.
"OrderDate >= #01/01/2002# AND OrderDate < #02/01/2002#"

En algunos casos necesitar delimitar los nombres de columna dentro del criterio de bsqueda por
ejemplo cuando contienen un espacio o algn otro carcter no alfanumrico, o porque el nombre de
columna es una palabra reservada como LIKE o SUM. ADO.NET utiliza corchetes como delimitador de
nombres de columna. Por tanto, si quiere buscar el valor 3 en una columna llamada Columna con Espacio
deber usar la forma:
"[Columna con Espacio] = 3"

122

Escuela de Informtica del Ejrcito

Y si el nombre de columna contiene corchetes?. Puede usar el carcter de escape (\) antes del
corchete de cierre en la cadena de criterio. Por ejemplo si el nombre de columna es Nombre]muy[Raro y
quiere buscar filas con el valor 5 en esta columna, puede usar la siguiente cadena de criterio:
string criterio = @"[Nombre\]muy[Raro] = 5";

7.1.5

Mtodos Select adicionales

Como muchos mtodos en el modelo de objetos de ADO.NET, el mtodo Select est sobrecargado.
Puede proporcionar solamente una cadena de bsqueda, pero tambin puede incluir un criterio de
ordenacin y un parmetro para controlar el estado de las filas que busca, por ejemplo, solo filas aadidas o
solo filas modificadas.

Incluir un criterio de ordenacin


En nuestro fragmento de ejemplo inicial del mtodo Select buscamos en un DataTable que contena
informacin de clientes para buscar clientes de los Estados Unidos que no estaban en Seattle. Podemos
controlar el orden de los objetos DataRow que devuelve Select usando una de las firmas sobrecargadas.
Estas son las lneas relevantes para uso de este mtodo; se supone que se ha cargado correctamente el
DataSet:
string filtro = "Country = USA AND City <> Seattle;
string orden = City DESC;
foreach(DataRow in tabla.Select(filtro, orden))
Console.WriteLine("{0,-35} {1}, {2}",
fila["CompanyName"], fila["City"], fila["Country"]);

Especificar el estado de las filas


Puede usar una versin sobrecargada del mtodo Select para especificar un valor de la enumeracin
DataViewRowState. Vea este valor como un filtro aadido al criterio de bsqueda. Suponga que quiere
examinar solamente las filas modificadas y borradas en un DataTable. El siguiente fragmento modifica los
DataRow cuya columna Country tiene el valor USA gracias al mtodo Select. Despus vuelve a usar este
mtodo, pero los parmetros filtro y ordenacin son cadenas vacas, el valor predeterminado. Si el cdigo
usara solo estos parmetros, el mtodo devolvera todas las filas del DataTable. Sin embargo, se usa el
parmetro de estado para indicar que solo queremos las filas modificadas.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT CustomerID, CompanyName, City, Country FROM Customers";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tabla = new DataTable("Clientes");
adaptador.Fill(tabla);
Console.WriteLine("La tabla contiene {0} filas", tabla.Rows.Count);
Console.WriteLine();
foreach(DataRow fila in tabla.Select("Country = USA"));
row["CompanyName"] = "Valor nuevo";
DataViewRowState estadoVista = DataViewRowState.ModifiedCurrent;
Console.Writeline("Filas mofidicadas:");
foreach(DataRow fila in tabla.Select("","", estadoVista))
Console.WriteLine("\t{0}", fila["CompanyName", DataRowVersion.Original]);

Recuerde que para filas borradas solo puede examinar la versin original de la fila.

7.2 Que es un objeto DataView


El mtodo Select de la clase DataTable es potente y flexible, pero no siempre es la mejor solucin. Tiene
dos importantes limitaciones. Primero, como acepta criterios de bsqueda muy dinmicos no es muy
eficiente. Adems, ni los formularios Windows ni Web soportan vinculacin con el resultado devuelto por el
mtodo Select un array de objetos DataRow. La solucin de ADO.NET es la clase DataView.
La clase DataView de ADO.NET es a grandes rasgos equivalente a una tabla de una base de datos, por
lo que se puede asumir que es similar a una vista en bases de datos. La clase DataView no mantiene su
propia copia de los datos. Cuando se accede a datos por medio de un DataView los datos que se obtienen
proceden del DataTable correspondiente.
A diferencia de una vista de base de datos, un DataView no es simplemente una consulta SQL. Los
objetos DataView de ADO.NET permiten filtrar, ordenar y buscar en los contenidos de objetos DataTable.
No se puede usar un DataView para unir datos entre dos objetos DataTable. Los objetos DataView soportan
filtrado de filas en base a criterios dinmicos, pero slo pueden acceder a un nico DataTable, y todas las
columnas del DataTable estn disponibles por medio del DataView.
123

ADO.NET 2.0

7.3 Objetos DataView en cdigo


La clase DataView ofrece funcionalidades similares a las del mtodo Select de la clase DataTable.
Vamos a examinar esta funcionalidad y a compararla con el mtodo Select.

7.3.1

Crear objetos DataView

Para usar un objeto DataView para ver los datos de un DataTable debe asociar la vista con la tabla.
Puede especificar el DataTable de una de dos formas: configurando la propiedad Table del objeto DataView
o usando el constructor de DataView. Los dos fragmentos siguientes son equivalentes:
DataTable tabla = new DataTable("NombreTabla")
DataView vista;
//Crear el DataView y asociarlo con el DataTable
vista = new DataView();
vista.Table = tabla;
//Las dos operaciones en un solo paso
Vista = new DataView(tabla);

Si asigna un DataTable a la propiedad Table de un DataView, el DataTable debe tener su propiedad


TableName configurada con algo diferente a una cadena vaca, el valor predeterminado. Esta restriccin no
se aplica cuando se usa el constructor de DataView.
La clase DataView tiene tambin un constructor cuya firma se parece ms al mtodo Select de
DataTable. Este constructor configura las propiedades Table, RowFilter, Sort y RowStateFilter del DataView
en una sola lnea de cdigo. Suponiendo que tenemos un DataTable llamado tabla cargado a partir de la
tabla de cliente, y un DataView llamado vista, los dos siguientes fragmentos son equivalentes:
DataViewRowState estado =
DataViewRowState.ModifiedOriginal | DataViewRowState.Deleted;
//Configura las propiedades por separado
DataView vista = new DataView();
vista.Table = tabla;
vista.RowFilter = "Country = USA";
vista.Sort = "City DESC";
vista.RowStateFilter = estado;
//Lo hace todo en el constructor
vista = new DataView(tabla, "Country = USA", "City DESC", estado);

7.3.2

Propiedad RowStateFilter

La propiedad RowStateFilter acepta valores de la enumeracin DataViewRowState, que se describen en


la tabla 8.1. Puede ver esta enumeracin como una combinacin de la propiedad RowState del objeto
DataRow y la enumeracin DataRowVersion.
Tabla 8. 1: Enumeracin DataRowVersion

Valor
Added
CurrentRows
Deleted
ModifiedCurrent
ModifiedOriginal
None
OriginalRows
Unchanged

7.3.3

Descripcin
Se incluyen las filas aadidas
Se incluyen las filas no borradas. Predeterminado
Se incluyen las filas borradas
Se incluyen las filas modificadas; se ve el valor actual
Se incluyen las filas modificadas; se ve el valor original
No se incluye ninguna fila
Se incluyen las filas borradas, modificadas y no modificadas; se ve el valor original
Se incluyen las filas no modificadas

Clase DataRowView

Si usa el mtodo Select de la clase DataTable y especifica ModifiedOriginal el mtodo devolver solo
filas modificadas. Pero como ya ha visto en el fragmento de cdigo anterior que ilustra el mtodo Select an
tenemos que especificar si queremos recuperar valores originales de la fila al leer las columnas de los
DataRow devueltos.
Este paso extra no es necesario cuando se usa DataView, porque el DataView devuelve datos usando
su propia clase especializada: DataRowView. El DataRowView ofrece en gran medida la misma
funcionalidad que DataRow. Expone una propiedad predeterminada Item que puede usar para acceder a los
contenidos de una columna proporcionando o un nombre de columna o su ndice. Puede examinar y
modificar los contenidos de una fila usando la propiedad Item, pero slo tendr disponible una versin de los
datos la especificada en la propiedad DataRowVersion de la clase DataView.
El siguiente fragmento muestra cmo usar la clase DataView para obtener un objeto DataRowView y
cmo usar la clase DataRowView para examinar los datos de una fila.
124

Escuela de Informtica del Ejrcito

DataView vista = new DataView(tabla);


DataRowView filaVista = vista[0];
Console.WriteLine(fila["CompanyName"]);

7.3.4

Examinar todas las filas mediante un DataView

El uso de un DataView para acceder a datos en un DataTable es ligeramente diferente al acceso directo
al DataTable. El DataTable expone sus filas de datos por medio de la propiedad Rows, lo que le permite
recorrerlas usando un bucle foreach. La clase DataView no tiene una propiedad correspondiente, pero an
puede acceder a los DataRowView con este mismo bucle.
La clase DataView tambin expone una propiedad Count que devuelve el nmero de filas visibles. Puede
usar esta propiedad para construir un bucle para recorrer las filas.
foreach(DataRowView filaVista in vista)
Console.WriteLine(filaVista["CompanyName"]);

7.3.5

Buscar datos en un DataView

Ya ha visto cmo soporta la clase DataView el filtrado usando las propiedades RowFilter y
RowStateFilter. Tambin permite la bsqueda usando los mtodos Find y FindRows. Estos mtodos son
similares al mtodo Find de la coleccin Rows de la clase DataTable.

Mtodo Find
Una vez configurada la propiedad Sort de un objeto DataView puede llamar a su mtodo Find para
localizar una fila en base a las columnas especificadas en la propiedad Sort. Como con el mtodo Find del
objeto DataRowCollection, puede proporcionar un nico valor o un array de valores.
Sin embargo, el mtodo Find de la clase DataView no devuelve un objeto DataRow o DataRowView, sino
un entero que corresponde al ndice de la fila deseada en el DataView. Si no encuentra la fila solicitada
devuelve el valor -1.
El siguiente fragmento de cdigo muestra cmo usar el mtodo Find de la clase DataView para localizar
un cliente en base a la columna ContactName. Primero, se configura la propiedad Sort con la columna
usada en la bsqueda, despus pase el valor que busca al mtodo Find. El cdigo utiliza el valor devuelto
por Find para comprobar si se ha encontrado la fila deseada.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT CustomerID, Country, ContactName FROM Customers";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tabla = new DataTable("Clientes");
adaptador.Fill(tabla);
DataView vista = new DataView(tabla);
vista.Sort = "ContactName";
int ndice = vista.Find("Fran Wilson");
if(ndice == 1)
Console.WriteLine("No se encuentra la fila");
else
Console.WriteLine(vista[ndice]["CompanyName"]);

Mtodo FindRows
El mtodo Find de la clase DataRowCollection realiza un bsqueda en base a la columna o las columnas
especificados en la propiedad PrimaryKey del objeto DataTable. Como una clave principal est asociada
con una restriccin de unicidad, el criterio especificado lo satisfar como mximo una fila.
El mtodo Find de la clase DataView realiza bsquedas en base a la columna o columnas especificadas
en la propiedad Sort del objeto DataView. Mltiples filas de datos pueden tener los mismos valores para las
columnas usadas para ordenar los datos. Por ejemplo, puede ordenar segn la columna Country, y varias
filas pueden tener el valor Spain en esta columna. Pero no puede usar el mtodo Find de la clase DataView
para localizar todos los clientes de Espaa porque el mtodo Find solo devuelve un entero.
Afortunadamente, la clase DataView tambin expone un mtodo FindRows. El mtodo FindRows se
utiliza igual que el mtodo Find, pero devuelve un array de objetos DataRowView que contiene las filas que
cumplen los criterios especificados.
El siguiente fragmento sustituye a la parte del anterior donde se utiliza Find.

125

ADO.NET 2.0

vista.Sort = "Country";
DataRowView[] filas = vista.FindRows("Spain");
if(filas.Length == 9)
Console.WriteLine("No se encuentra ninguna fila");
else
foreach(DataRowView fila in filas)
Console.WriteLine("ContactName");

7.3.6

Modificar objetos DataRowView

La modificacin de una fila de datos usando un objeto DataRowView es similar a la modificacin de


contenidos de un objeto DataRow. La clase DataRowView expone los mtodos BeginEdit, EndEdit,
CancelEdit y Delete, igual que la clase DataRow.
La creacin de una nueva fila de datos usando un objeto DataRowView es ligeramente diferente a la
creacin de un nuevo DataRow. El DataView tiene un mtodo AddNew que devuelve un nuevo objeto
DataRowView. La nueva fila en realidad no se aade a la DataTable subyacente hasta que no se llama al
mtodo EndEdit del objeto DataRowView.
El siguiente fragmento muestra cmo crear, modificar y borrar una fila de datos usando la clase
DataRowView.
DataTable tabla = new DataTable("Clientes");
tabla.Columns.Add("CustomerID", typeof(string));
tabla.Columns.Add("CompanyName", typeof(string));
tabla.Columns.Add("ContactName", typeof(string));
DataView vista = new DataView(tabla);
//Aadir una nueva fila
DataRowView fila = vista.AddNew();
fila["CustomerID"] = "ABCDE";
fila["CompanyName"] = "Compaa Nueva";
fila["ContactName"] = "Contacto Nuevo";
fila.EndEdit();
//Modificar una fila
fila.BeginEdit();
fila["CompanyName"] = "Modificada";
fila.EndEdit();
//Borrar una fila
fila.Delete();

7.3.7

Usar un DataView para crear un nuevo DataTable

ADO.NET 2.0 aade a la clase DataView el mtodo ToTable. Este mtodo devuelve un DataTable que
contiene solo las filas visibles por medio de la propiedad RowFilter de DataView, y ofrece varias
sobrecargas. Puede usar las diferentes sobrecargas para controla la propiedad TableName del nuevo
DataTable, los DataColumn que contendr y si el DataTable resultante contendr o no solamente filas
nicas en base a estos DataColumn.
Vamos a empezar examinando cdigo que recupera informacin de todos los clientes en un DataTable,
usa un DataView para localizar solo los clientes de un pas concreto, Espaa, y despus el mtodo ToTable
para crear un nuevo DataTable que contiene solo estos clientes.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT CustomerID, CompanyName, City, Country FROM Customers";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataTable tablaTodos = new DataTable("Clientes");
adaptador.Fill(tablaTodos);
//Crea una DataView solo con los clientes de Espaa
DataView vista = new DataView(tablaTodos);
vista.RowFilter = "Country = 'Spain'";
//Crea un nuevo DataTable basado en el DataView
DataTable tablaEspaoles = vista.ToTable("ClientesEspaa");
Console.WriteLine("TableName: {0}", tablaEspaoles.TableName);
Console.WriteLine("Filas: ");
foreach(DataRow fila in tablaEspaoles.Rows)
Console.WriteLine("\t{0}, {1}", fila["City"], fila["Country"]);

El fragmento anterior muestra solo las columnas City y Country, pero el DataTable creado con la llamada
a ToTable tambin contiene otras columnas CustomerID y CompanyName. Podemos controlar las
126

Escuela de Informtica del Ejrcito

columnas en el DataTable resultante usando una de las dos sobrecargas del mtodo ToTable que recibe un
array de cadenas con los nombres de las columnas que se deben incluir. Ambas sobrecargas incluyen un
parmetro boolean para indicar si se quiere que el DataTable incluya solo valores nicos en las columnas
de la coleccin. El fragmento anterior devuelve varios clientes de Madrid.
El siguiente cdigo usa el mtodo ToTable de la clase DataView para restringir las columnas que
contiene el DataTable resultante. El DataTable original contiene DataColumn para CustomerID,
CompanyName, City y Country. El DataTable resultante contiene DataColumn solo para City y Country. El
fragmento de cdigo usa el parmetro Distinct del mtodo ToTable para asegurar que se devuelven solo
combinaciones nicas de City y Country.
La restriccin de unicidad se aplica a toda la fila en el nuevo DataTable. En otras palabras, la
combinacin de City y Country debe ser nica. Existe un Toledo en Espaa y varios en Estados Unidos. Si
en la tabla original estuvieran presentes estas ciudades aparecera un registro para Toledo Espaa y un
registro para Toledo Estados Unidos.
En el siguiente ejemplo, la creacin y carga de tablaTodos y la creacin y filtrado de vista son iguales
que en el anterior.
//Crea un nuevo DataTable basado en el DataView
//Contiene solo las columnas City y Country
//y solo combinaciones nicas de estas columnas
DataTable tablaEspaoles = vista.ToTable("ClientesEspaa", true,
new string[] { "City", "Country"});
Console.WriteLine("TableName: {0}", tablaEspaoles.TableName);
Console.WriteLine("Columnas: ");
foreach(DataColumn columna in tablaEspaoles.Columns)
Console.WriteLine("\t{0}", columna.ColumnName);
Console.WriteLine();
Console.WriteLine("Filas: ");
foreach(DataRow fila in tablaEspaoles.Rows)
Console.WriteLine("\t{0}, {1}", fila["City"], fila["Country"]);

7.4 Crear objetos DataView en Visual Studio


Crea un objeto DataView es mucho ms sencillo que crear un DataTable. No tiene que aadir columnas
y tipos de datos. Simplemente se asigna al DataView un DataTable y se configuran las propiedades
necesarias (RowFilter, RowStateFilter, Sort, etc.).
En Visual Studio 2005 el DataView no est disponible en la caja de herramientas de forma
predeterminada. Sin embargo, puede pulsar con el botn derecho sobre la caja de herramientas, pulsar
Elegir elementos y seleccionar DataView en la lista de componentes del marco de trabajo .NET para
volver a colocarlo.
Una vez que el DataView est disponible en la caja de herramientas, puede aadir un nuevo objeto
DataView al diseador arrastrndolo. Tambin puede pulsar dos veces sobre el elemento.
Una vez creado el nuevo DataView puede configurar sus propiedades por medio de la ventana de
propiedades.

7.5 Referencia
7.5.1

Propiedades de la clase DataView

La tabla 8.2 muestra las propiedades de la clase DataView.


Tabla 8. 2: Propiedades de la clase DataView

Propiedad
AllowDelete
AllowEdit

Tipo
Boolean
Boolean

AllowNew
ApplyDefaultSort

Boolean
Boolean

Count
DataViewManager
IsInitialized
Item

Integer
DataViewManager
Boolean
DataRowView

RowFilter

String

RowStateFilter

DataViewRowState

Descripcin
Especifica si las filas del DataView se pueden borrar. Predeterminado true.
Especifica si las filas del DataView se pueden modificar. Predeterminado
true.
Especifica si se pueden aadir filas al DataView. Predeterminado true.
Especifica si se usa el orden predeterminado (clave principal).
Predeterminado false.
Devuelve el nmero de filas visible en el DataView. De solo lectura.
Devuelve una referencia al DataViewManager del DataView. Slo lectura.
Indica si se ha inicializado el DataView.
Devuelve un DataRowView que encapsula una fila de datos visible por
medio del DataView. Slo lectura.
Contiene el filtro que especifica qu filas del DataTable son visibles por
medio del DataView. Predeterminado, cadena vaca.
Especifica qu versin de las filas es visible por medio del DataView.
Predeterminado: DataViewRowState.CurrentRows.

127

ADO.NET 2.0

Tabla 8. 2: Propiedades de la clase DataView

Propiedad
Sort

Tipo
String

Table

DataTable

Descripcin
Especifica el criterio de ordenacin de las filas visibles por medio del
DataView. Predeterminado: cadena vaca.
Devuelve el DataTable con el que est vinculado el DataView.

AllowDelete, AllowEdit, AllowNew


Los objetos DataView a menudo se usan en combinacin con controles vinculados. Las propiedades
AllowDelete, AllowEdit y AllowNew simplifican el proceso de restringir los tipos de cambios que el usuario
puede hacer usando controles vinculados. En lugar de configurar propiedades en cada uno de los controles
vinculados puede configurar estas propiedades en el DataView.
De forma predeterminada estas propiedades tienen el valor true.

ApplyDefaultSort
La propiedad ApplyDefaultSort tiene de forma predeterminada el valor false. Si se cambia a true se
ordenan los contenidos del DataView segn los valores de la clave principal del DataTable correspondiente.
Al hacer este cambio la propiedad Sort del DataView se configura con las columnas de la clave principal del
DataTable.

Count e Item
La propiedad Item es el indexador de DataView y devuelve un objeto DataRowView cuando se le pasa
un entero que representa el ndice del mismo. La propiedad Count devuelve el nmero total de filas visibles
por medio del DataRowView. Ambas propiedades combinadas nos permiten crear un bucle para recorrer el
DataView.

DataViewManager
Si crea el DataView usando el mtodo CreateDataView de una instancia de un DataViewManager la
propiedad DataViewManager devolver el objeto DataViewManager que cre el DataView. En otro caso
devolver un DataViewManager sin inicializar.

IsInitialized
La clase DataView soporta la propiedad IsInitialized como parte de su implementacin del interface
ISupportInitializeNotification. Para ms informacin vea la descripcin de este interface tras los mtodos
BeginInit y EndInit de la clase DataSet.

RowFilter
La propiedad RowFilter es similar a la clusula WHERE de una consulta SQL. Slo las filas que
satisfacen los criterios indicados en esta propiedad sern visibles por medio de la vista. El valor
predeterminado es una cadena vaca.

RowStateFilter
La propiedad RowStateFilter afecta a los datos visibles por medio de un DataView por medio de dos
formas. Filtra las filas en base a su RowState y controla la versin de la fila que es visible por medio del
DataView. La propiedad RowStateFilter acepta valores y combinacin de valores de la enumeracin
DataRowViewState como se describi anteriormente en este estado.
Puede configurar la propiedad RowStateFilter usando el constructor de la clase DataView. El valor
predeterminado es CurrentRows, lo que hace que la vista muestre la versin actual de todas las filas del
DataTable que satisfagan el criterio especificado por la propiedad RowFilter.

Sort
La propiedad Sort controla el criterio de ordenacin de los datos visibles por medio del DataView, de una
forma similar a la clusula ORDER BY de una consulta SQL. Puede crear un criterio de ordenacin basado
en una sola columna o en una combinacin de columnas. De forma predeterminada las columnas se
ordenan de forma ascendente. Para ordenar las columnas en orden descendente utilice la palabra clave
DESC tras el nombre de la columna. Recuerde delimitar el nombre de columna si contiene algn carcter no
alfanumrico, por ejemplo un espacio, o si el nombre de columna es una palabra reservado.
El valor predeterminado de esta propiedad es una cadena vaca, con lo que el DataView mostrar su
contenido en el mismo orden en que aparecen en el DataTable subyacente. Puede configurar esta
propiedad mediante el constructor del DataView.

Table
Puede usar la propiedad Table de la clase DataView para establecer o consultar de qu DataTable
obtiene el DataView los datos que muestra. Si se cambia el valor de Table las propiedades RowFilter y
RowStateFilter recuperan sus valores predeterminados respectivos.
Tambin puede configurar la propiedad Table usando los constructores de la clase DataView.
128

Escuela de Informtica del Ejrcito

7.5.2

Mtodos de la clase DataView

La tabla 8.3 recoge los mtodos de la clase DataView.


Tabla 8. 3: Mtodos de la clase DataView

Mtodo
AddNew
BeginInit
CopyTo
Delete
EndInit
Find
FindRows
ToTable

Descripcin
Crea un nuevo objeto DataRowView
Almacena temporalmente los cambios al objeto DataView
Copia objetos DataRow en un array
Marca un DataRowView como borrado
Consigna los cambios almacenados en el objeto DataView
Busca una fila de datos en el DataView
Busca mltiples filas de datos en un DataView
Crea un nuevo DataTable que contiene solo las filas visibles actualmente por medio del DataView

AddNew y Delete
Puede usar los mtodos AddNew y Delete para aadir y eliminar filas de datos en el DataTable
subyacente. El mtodo AddNew devuelve un nuevo objeto DataRowView; una vez establecidos sus valores
puede llamar al mtodo EndEdit para aadir la fila de datos al DataTable subyacente.
Puede usar el mtodo Delete para borrar una fila si conoce el ndice de la fila dentro del DataView. Si
tiene una referencia al DataRow o al DataRowView puede llamar al mtodo Delete del objeto DataRow o
DataRowView. Recuerde que el mtodo Delete simplemente marca la fila como borrada. Para eliminar la fila
del DataTable debe llamar al mtodo AcceptChanges del DataRow o del DataTable o DataSet que lo
contienen, o enviar el cambio a la base de datos usando un SqlDataAdapter.

BeginInit y EndInit
La clase DataView soporta los mtodos BeginInit y EndInit como parte de su implementacin del
interface ISupportInitializeNotification. Para ms informacin vea la explicacin tras los mtodos BeginInit y
EndInit de DataSet.

CopyTo
La clase DataView expone un mtodo CopyTo que se comporta de forma similar al del objeto Array.
Puede copiar los objetos DataRowView disponibles por medio del DataView a un array usando el mtodo
CopyTo.

Find y FindRows
El DataView le permite localizar una o ms filas de datos usando sus mtodos Find y FindRows. Ambos
mtodos estn sobrecargados para aceptar un solo valor o un array de valores. El DataView usa los valores
especificados para buscar en su contenido en base a las columnas especificadas en la propiedad Sort. El
mtodo Find devuelve el ndice de la primera aparicin de la combinacin de valores especificadas o -1 si
no la encuentra. El mtodo FindRows devuelve un array de objetos DataRowView que cumplen la condicin
especificada.

ToTable
El mtodo ToTable crea una tabla que contiene las filas correspondientes a los DataRowView visibles
por medio de este DataView. Este mtodo permite controlar qu DataColumn aparecen en el DataTable
resultante y exigir unicidad para estas columnas.

7.5.3

Eventos de la clase DataView

La tabla 8.4 muestra los eventos de la clase DataView.


Tabla 8. 4: Eventos de la clase DataView

Evento
Initialized
ListChanged

Descripcin
Se lanza cuando se llama al mtodo EndInit de DataView
Se lanza cuando cambia el contenido del DataView

Initialized
La clase DataView soporta el evento Initialized como parte de su implementacin del interface
ISupportInitializeNotification. Para ms informacin vea a continuacin de la descripcin de los mtodos
BeginInit y EndInit de la clase DataSet.

ListChanged
La clase DataView soporta el evento ListChanged como parte de su implementacin del interface
IbindingList. Este evento se lanza cuando el contenido del DataView cambia por ejemplo cuando se
aade, borra o modifica una fila visible por medio del DataView, cuando un SqlDataAdapter carga la tabla
subyacente o cuando cambian las propiedades RowFilter, RowStateFilter, Sort o Table.

129

ADO.NET 2.0

7.5.4

Propiedades de la clase DataRowView

Todas las propiedades de la clase DataRowView, salvo la propiedad Item o indexador, son de solo
lectura. La tabla 8.5 resume estas propiedades.
Tabla 8. 5: Propiedades de la clase DataRowView

Propiedad
DataView
IsEdit
IsNew
Item
Row
RowVersion

Tipo
DataView
Boolean
Boolean
Object
DataRow
DataRowVersion

Descripcin
Devuelve el DataView al que pertenece el DataRowView
Indica si la fila se est modificando actualmente.
Indica si la fila es nueva, an sin aadir.
Establece o lee el contenido de una columna
Devuelve el objeto DataRow correspondiente al DataRowView
Devuelve el RowVersion del DataRow correspondiente al DataRowView.

DataView
Obtiene el DataView al que pertenece el DataRowView.

IsEdit e IsNew
Puede usar las propiedades IsEdit e IsNew para determinar si el objeto DataRowView se est
modificando actualmente y qu tipo de modificacin se est haciendo.
Cuando se est en proceso de modificacin de una nueva fila, es decir, se ha creado la fila con
DataView.AddNew pero no se ha llamado a EndEdit para aadir la fila al DataTable subyacente, IsNew
devolver true e IsEdit devolver false. Si est modificando una fila que ya existe en la tabla IsEdit
devolver true e IsNew devolver false.

Item
La propiedad Item de la clase DataRowView ofrece una funcionalidad muy similar a la correspondiente
de la clase DataRow. Puede usarla para modificar o examinar los contenidos de una columna de datos de la
fila. Puede acceder a la columna mediante su nombre o su ndice.

Row
Esta propiedad devuelve el DataRow que corresponde con el DataRowView. La clase DataRowView no
ofrece toda la funcionalidad disponible por medio de la clase DataRow; por ejemplo, la clase DataRowView
no expone mtodos como AcceptChanges o GetChanges. Si necesita trabajar con caractersticas del
interface DataRow puede usar la propiedad Row de DataRowView.

RowVersion
Si est trabajando con una fila de datos usando el interface DataRowView y quiere determinar qu
versin de los datos est viendo por medio de la propiedad Item puede comprobar la propiedad
RowVersion. Esta propiedad devuelve un valor de la enumeracin DataRowVersion.

7.5.5

Mtodos de la clase DataRowView

La tabla 8.6 resume los mtodos que expone la clase DataRowView.


Tabla 8. 6: Mtodos de la clase DataRowView

Mtodo
BeginEdit
CancelEdit
CreateChildView
Delete
EndEdit

Descripcin
Comienza el proceso de editar la fila
Cancela los cambios pendientes en la fila
Crea un DataView que contiene solo las filas hijas de la actual
Marca una fila como borrada
Guarda los cambios pendientes en la fila

BeginEdit, CancelEdit, EndEdit


Los mtodos BeginEdit, CancelEdit y EndEdit de la clase DataRowView trabajan de la misma forma que
los correspondientes de la clase DataRow. Si llama al mtodo BeginEdit antes de modificar la fila sus
cambios no se consignan en la fila hasta que no se llama a EndEdit. Si quiere descartar los cambios puede
llamar a CancelEdit.

CreateChildView
El mtodo CreateChildView permite especificar el nombre de una relacin o un objeto DataRelation y
devuelve un nuevo DataView que utiliza esta relacin como filtro.

Delete
El mtodo Delete permite marcar una fila como borrada. La fila no se eliminar del DataTable hasta que
no se llame a AcceptChanges o se enven los cambios a la base de datos usando un SqlDataAdapter.

130

Escuela de Informtica del Ejrcito

DataSet con tipo y TableAdapter

En este capitulo estudiar caractersticas integradas en Visual Studio que pueden simplificar el proceso
de cargar un DataSet y acceder a sus contenidos. Los DataSet con tipo, que aparecieron con Visual Studio
2002 y la versin 1.0 del marco de trabajo .NET, extienden el DataSet bsico y hacen que las tablas y
columnas estn disponibles por medio de propiedades y mtodos. Los TableAdapter, que aparecen en
Visual Studio 2005, complementan los DataSet con tipo, ofreciendo mtodos Fill y Update pensados para
DataTable especficas dentro de un DataSet con tipo.
Este captulo cubre las caractersticas y escenarios principales tanto para DataSet con tipo como para
TableAdapter. A lo largo del camino aprender cmo aadir su propio cdigo a estas clases para ampliar su
funcionalidad. Como con muchas clases generadas por el diseador, los DataSet con tipo y los
TableAdapter tienen sus propias fortalezas y debilidades. Este captulo ayudar a identificar tanto unas
como otras de modo que pueda tomar una decisin informada sobre cundo, o si, usar estas
caractersticas.

8.1 DataSet con tipo


A lo largo de los tres captulos anteriores aprendi cmo crear y usar objetos DataSet. El cdigo utilizado
para acceder a los contenidos de un DataSet mediante programacin consiste en acceder a propiedades de
los diferentes objetos, utilizando ndices. Los DataSet con tipo permiten acceder a cada elemento por su
nombre. En el fragmento siguiente, las dos lneas acceden a la columna CompanyName de la tabla
Customers de un DataSet, el primero ordinario y el segundo con tipo.
datos.Tables[Customers].Rows[0][CompanyName];
datos.Customers[0].CompanyName;

Un DataSet con tipo es una clase que hereda de la clase DataSet y que incluye propiedades y mtodos
basados en el esquema especificado. Esta clase tambin contiene otras clases para los objetos DataTable y
DataRow.
Estas clases, combinadas con IntelliSense, permiten escribir el cdigo de acceso a datos de forma mas
eficiente. Por ejemplo, cada clase DataTable con tipo incluye mtodos Add y Find adaptados. Si est
trabajando con la tabla de clientes, el mtodo Add aceptar informacin correspondiente a las columnas de
la tabla, y el mtodo Find aceptar valores correspondientes a la clave principal.
Tambin es ms fcil crear interfaces de usuario en Visual Studio usando DataSet con tipo. El esquema
para un DataSet con tipo existe en tiempo de diseo, de modo que se pueden vincular controles en tiempo
de diseo sin tener que escribir cdigo.
Visual Studio 2005 ha llevado el concepto de DataSet con tipo ms all aadiendo los TableAdapter,
clases que a grandes rasgos recuerdan a SqlDataAdapter con tipo. Puede usar TableAdapter para cargar
rpidamente un DataSet con tipo con los resultados de una consulta, o para enviar los cambios pendientes
a la base de datos. El TableAdapter sabe cmo conectar con la base de datos, ejecutar la consulta para
recuperar datos y enviar los cambios pendientes.

8.2 Crear objetos DataSet con tipo


Existen dos formas bsicas de crear un DataSet con tipo. Una implica escribir cdigo y usar una
herramienta de lnea de comando incluida con el SDK del marco de trabajo .NET. La otra, mucho ms
simple, utiliza el entorno de desarrollo Visual Studio y no precisa abrir una ventana de lnea de comando.

8.2.1

La forma difcil

El SDK del marco de trabajo .NET incluye una utilidad de lnea de comando llamada herramienta de
definicin de esquema XML que ayuda a generar archivos de clase en base a archivos de esquema XML.
Puede usar esta herramienta junto con el mtodo WriteXmlSchema de la clase DataSet para traducir su
DataSet a un DataSet con tipo.

Usar el mtodo WriteXmlSchema de la clase DataSet


En el Captulo 11 examinaremos con detalle el mtodo WriteXmlSchema. Puede usar este mtodo para
crear un archivo que contiene informacin de esquema (tablas, columnas, restricciones y relaciones) de su
DataSet. El siguiente fragmento de cdigo crea un DataSet usando columnas de las tablas Customers y
Orders de la base de datos Northwind. Tambin aade un DataRelation entre los dos DataTable antes de
escribir el esquema del DataSet en un archivo.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT CustomerID, CompanyName FROM Customers;" +
"SELECT OrderID, CustomerID, OrderDate FROM Orders";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
DataSet ds = new DataSet("DataSetNorthwind");

131

ADO.NET 2.0

adaptador.TableMappings.Add("Table", "Clientes");
adaptador.TableMappings.Add("Table1", "Pedidos");
adaptador.FillSchema(ds, SchemaType.Mapped);
ds.Relations.Add("relClientesPedidos",
ds.Tables["Clientes"].Columns["CustomerID"],
ds.Tables["Pedidos"].Columns["CustomerID"]);
ds.WriteXmlSchema(@"C:\DataSetNorthwind.xsd");

Por lo general est desaconsejado el uso del mtodo FillSchema en cdigo de produccin; se usa en
este caso para facilitar la carga del esquema.

Usar la herramienta de definicin de esquema XML


La herramienta de definicin de esquema XML se denomina XSD.exe. La herramienta puede generar
archivos de clase en base a archivos de esquema XML, .xsd o .xdr. Tambin puede crear archivos de
esquema XML a partir de archivos de librera, .dll, o ejecutables, .exe.
En el fragmento anterior guardamos el esquema de un DataSet en un archivo .xsd. Ahora vamos a usar
la herramienta de definicin de esquema XML para generar un archivo de clase en base a este esquema.
Abra una ventana de smbolo del sistema de Visual Studio y escriba:
C:\>XSD.exe DataSetNorthwind.XSD /d

El primer parmetro es la ruta del archivo de esquema. El segundo indica que la clase que queremos
crear deriva de la clase DataSet. Existe un tercer parmetro que permite indicar el lenguaje de destino; si no
se usa se asume C#, que es lo que queremos. Esta herramienta admite ms opciones que se pueden
consultar en la documentacin del SDK.
Ahora simplemente agregue el nuevo archivo de clase a su proyecto. Desde este momento puede
declarar y crear variables del tipo NorthwindDataSet en su cdigo. El nombre de la clase est basado en la
propiedad DataSetName del DataSet usado para generar el archivo de esquema.

8.2.2

La forma fcil

Crear un DataSet con tipo en Visual Studio es mucho ms fcil que lo que hemos visto. No hay que
escribir cdigo y, lo mejor de todo, no hay ventana de smbolo del sistema.
Para demostrar lo fcil que es crear un DataSet con tipo en Visual Studio vamos a construir la misma
clase que creamos usando cdigo y la ventana de smbolo el sistema en la seccin anterior. Como el
anterior, el nuevo DataSet incluir dos objetos DataTable y una DataRelation.
Los pasos implicados sern a grandes rasgos equivalentes a los que seguimos en la primera aplicacin
ADO.NET, salvo que nos saltaremos el asistente de configuracin de origen de datos.
Primero, cree una nueva aplicacin de consola. Agregue un nuevo elemento al proyecto. Seleccione
Conjunto de datos y cambie el nombre a NorthwindDataSet.xsd antes de pulsar Agregar.
Ahora ver un diseador vaco llamado NorthwindDataSet.xsd. Ahora aadiremos tablas y columnas
desde el Explorador de servidores a este diseador. Para esto es necesario tener una conexin con la base
de datos establecida en el Explorador de servidores.
En el Explorador de servidores seleccione el nodo Conexiones de datos. Pulse con el botn derecho
sobre este nodo y seleccione Agregar conexin. Tambin puede pulsar el botn Conectar con base de
datos justo encima del nodo Conexiones de datos. Si es la primera vez que lo hace ver un dilogo que le
pide informacin sobre el tipo de base de datos con el que quiere conectar.
Este captulo se centra en trabajar con la base de datos Northwind en la instalacin local de SQL Server
Express. Seleccione Microsoft SQL Server y pulse aceptar. Ver el dilogo Agregar conexin.
Para conectar con la instancia local de SQL Express seleccione o escriba .\SQLExpress en la lista
desplegable Nombre del servidor. El carcter . es un atajo que se traduce a la mquina local; puede usar
en su lugar (local). El carcter \ separa el nombre de la mquina del nombre de la instancia de servidor
SQL Server; si la instalacin es normal el nombre de instancia es SQLExpress o sqlexpress.
El dilogo asume que va a conectar con la base de datos usando Autenticacin de Windows. De forma
predeterminada las instalaciones de SQL Server 2005 y SQL Express exigen que los usuarios conecten
usando sus credenciales Windows en lugar de pedir un nombre de usuario y contrasea. Si quiere conectar
usando esta segunda opcin, denominada autenticacin de SQL Server marque la opcin correspondiente y
proporcione el nombre de usuario y la contrasea.
El dilogo tambin proporciona una lista desplegable con los catlogos disponibles en el servidor. Para
conectar con Northwind seleccione este catlogo en la lista. Puede usar la opcin Asociar con un archivo de
base de datos para proporcionar su propio archivo de base de datos.
132

Escuela de Informtica del Ejrcito

Existen otras opciones cuando se conecta con un servidor SQL Server. Pulsando el botn Avanzadas
puede acceder a propiedades adicionales de la conexin tamao de paquete, informacin de
agrupamiento de conexiones, etc. Puede usar el botn Probar conexin para intentar conectar con la base
de datos usando la informacin proporcionada. Una vez especificada toda la informacin necesaria pulse
Aceptar. La nueva conexin esta disponible en el Explorador de servidores.
Ahora que tiene una conexin en el Explorador de servidores vamos a ver cmo aadir informacin al
DataSet con tipo. Expanda el nodo de conexin y su carpeta Tablas y la tabla Customers. Puede
seleccionar la tabla Customers y arrastrarla al diseados; esto aadira un DataTable al DataSet con tipo e
incluira todas las columnas de la tabla. Aadir todas las columnas es sencillo, pero puede ser excesivo si
no le interesan todas. Otra opcin es seleccionar las columnas que nos interesan, manteniendo pulsado el
botn Control, y arrastrar al diseador solamente estos nodos. Seleccione las columnas CustomerID y
CompanyName y adalas al DataSet con tipo.
Realizada esta accin ver un nuevo elemento en su DataSet con tipo. La parte superior del elemento es
el DataTable Customers, que contiene DataColumn para las columnas seleccionadas. La parte inferior es
un TableAdapter, una clase que contiene, no deriva de, un DataAdapter, en este caso un SqlDataAdapter.
Hablaremos de TableAdapter ms adelante en este captulo.
Ahora vamos a aadir al DataSet informacin de pedido. Expanda la tabla Orders en el explorador de
servidores; seleccione las columnas OrderID, CustomerID y OrderDate y arrstrelas al diseador. Ver
como se aade al diseador la nueva tabla, y la relacin entre ambas.
Y esto es todo. Ahora puede crear instancias de la nueva clase DataSet con tipo en su cdigo.
Cuando ha generado su DataSet con tipo Visual Studio ha recorrido estos pasos para crear la nueva
clase:
1. Crea una instancia de la clase DataSet.
2. Llama al mtodo FillSchema de los objetos SqlDataAdapter creados implcitamente al arrastrar las
columnas del Explorador de servidores al diseador.
3. Llama al mtodo WriteXmlSchema del DataSet.
4. Aade el archivo .xsd al proyecto
5. Usa la herramienta de definicin de esquema XML para generar la clase DataSet con tipo en base al
archivo .xsd.
6. Aade el nuevo archivo de clase al proyecto.
Pero dnde est el archivo de clase para el DataSet con tipo?. El archivo de esquema del DataSet
(NorthwindDataSet.xsd) tendr asociados tres archivos. Si no ve estos archivos bajo el nodo del DataSet
con tipo pulse el botn Mostrar todos los archivos en la parte superior del Explorador de soluciones. El
primer archivo ser el archivo de clase generado por el diseador para el DataSet con tipo
NorthwindDataSet.Designer.cs. Los otros archivos tendrn extensiones .xsc y .xss, y contendrn opciones
de diseo del DataSet en el diseador.
El archivo de clase contiene en realidad muchas clases. Est la clase principal, NorthwindDataSet, que
deriva de DataSet. Esta clase expone dos objetos derivados de DataTable, CustomersDataTable y
OrdersDataTable. Cada una de estas dos clases expone filas por medio de clases especficas de cada tabla
derivadas de DataRow, CustomersRow y OrdersRow.

8.3 Usar DataSet con tipo


Los objetos DataSet con tipo simplifican el proceso de desarrollo, facilitando escribir cdigo para acceder
y modificar los contenidos del DataSet. Vamos a ver algunos ejemplos que comparan el trabajo con un
DataSet estndar sin tipo con el trabajo con un DataSet con tipo.

8.3.1

Aadir una fila

Cada una de las clases para objetos DataTable de su DataSet ofrece dos formas de aadir una nueva
dila al DataTable. La llamada a New<NombreTabla>Row devuelve un nuevo DataRow con tipo
correspondiente al DataTable. Ahora puede establecer valores para las columnas de la fila usando
propiedades del DataRow con tipo:
//Con tipo
NorthwindDataSet ds = new NorthwindDataSet();
NorthwindDataSet.CustomersDataTable tablaClientes = ds.Customers;
NorthwindDataSet.CustomersRow filaCliente = tablaClientes.NewCustomersRow();
filaCliente.CustomerID = CIANU;
filaCliente.CompanyName = Compaa Nueva;
tablaClientes.AddCustomersRow(filaCliente);

133

ADO.NET 2.0

//Sin tipo. Se omite la creacin del DataSet


DataRow filaCliente = tablaClientes.NewRow();
filaCliente[CustomerID] = CIANU;
filaCliente[CompanyName] = Compaa Nueva;
tablaClientes.Rows.Add(filaCliente);

Las ventajas de usar el DataRow con tipo en este ejemplo pueden no estar claras cuando se ve el cdigo
escrito. Vindolo en Visual Studio se hacen evidentes. Al escribir el primero de los ejemplos anteriores se
puede ver cmo IntelliSense contempla las propiedades CustomerID y CompanyName en el objeto
filaCliente; sin embargo, en el segundo caso no existe ningn indicio respecto a cuales son los nombres de
las columnas en la fila. Adems, incluso escribiendo ms los nombres de las columnas en el primer caso se
producira un error en tiempo de compilacin, mientras que en el segundo el error saltara en ejecucin.
Como el mtodo Add de DataRowCollection, el mtodo Add<NombreTabla>Row del DataTable con tipo
est sobrecargado. El siguiente fragmento usa el DataTable Customers del DataSet con tipo que creamos al
principio del captulo:
//Con tipo
tablaClientes.AddCustomerRow(CIANU, Compaa Nueva);
//Sin tipo
tablaClientes.Rows.Add(new object[]{CIANU, Compaa Nueva});

De nuevo, gracias a IntelliSense, esta caracterstica impresiona ms al escribir el cdigo. En Visual


Studio, aparecen los nombres de los parmetros al escribir la llamada al mtodo, lo que nos evita errores en
el orden de las columnas. Adems, la versin con tipo exige que el valor para cada columna tenga el tipo
adecuado en tiempo de diseo, mientras que la versin sin tipo no los comprueba hasta la ejecucin.

8.3.2

Localizar una fila

Con un DataSet sin tipo puede usar el mtodo Find de la coleccin Rows del objeto DataTable para
localizar una fila concreta en base al valor, o valores, de su clave principal. El uso de este mtodo puede
llevar a confusin, especialmente si el DataTable tiene una clave principal compuesta. Por ejemplo, la tabla
Order Details de la base de datos Northwind usa como clave principal la combinacin de las columnas
OrderID y ProductID. El cdigo que usa Find sobre este DataTable puede tener este aspecto:
DataRow filaDetalle;
filaDetalle = tablaDetalle.Find(new object[]{10245, 7});

Este cdigo funciona, pero resulta confuso de escribir. Ms importante, es difcil de leer, lo que complica
su mantenimiento. Si tiene alguna experiencia con la base de datos Northwind recordar que la clave
principal de la tabla Order Details est definida por las columnas OrderID y ProductID, y que los valores que
recibe Find estn en este orden. Pero quien no tenga este conocimiento tendr dificultades para escribir y
mantener este cdigo.
Los DataSet con tipo hace que este cdigo sea mucho ms fcil de leer y mantener porque cada
DataTable de un DataSet con tipo expone su propio mtodo Find, si la tabla tiene definida una clave
principal. De hecho, el nombre del mtodo Find incluye el nombre o nombres de la columna o columnas que
forman parte de la clave. El siguiente fragmento asume que se ha creado y cargado un DataTable
correspondiente a la tabla Order Details.
filaDetalle = tablaDetalle.FindByOrderIDProductID(10245, 7 );

De nuevo, este mtodo comprueba en tiempo de diseo los tipos de los valores utilizados.

8.3.3

Editar una fila

La edicin de filas en un DataSet con tipo es similar a la de un DataSet estndar. Sigue existiendo la
opcin de usar los mtodos BeginEdit, EndEdit y CancelEdit. Sin embargo, ahora puede acceder a los
valores de las columnas del DataRow usando propiedades del DataRow con tipo:
//Con tipo
filaCliente.CompanyName = Modificado;
//Sin tipo
filaCiente[CompanyName] = Modificado;

8.3.4

Datos nulos

Anteriormente hemos utilizado la funcin IsNull de la clase DataRow para comprobar valores nulos, y
DBNull.Value para asignar valores nulos a columnas. El mtodo IsNull es til, pero es necesario pasarle el
nombre, ndice o DataColumn que se quiere comprobar.
Los DataSet con tipo tambin facilitan el trabajo con valores nulos. Cada DataRow con tipo que tiene
columnas que aceptan valores nulos tienen dos nuevos mtodos para cada una de estas columnas, uno
134

Escuela de Informtica del Ejrcito

para comprobar si su valor es nulo y otro para poner su valor a nulo. El siguiente fragmento trabaja con la
columna OrderDate de una fila de informacin de pedido, por lo que estos mtodos se llaman
IsOrderDateNull y SetOrderDateNull. El cdigo comprueba si el valor de la columna OrderDate en la fila
actual es nulo. Si el valor no es nulo, lo hace nulo.
//Con tipo
if(!filaPedido.IsOrderDateNull())
filaPedido.SetOrderDateNull();
//Sin tipo
if(!filaPedido.IsNull(OrderDate))
filaPedido[OrderDate] = DBNull.Value;

8.3.5

Datos jerrquicos

La clase DataRow expone dos mtodos que permiten recorrer datos jerrquicos GetChildRows y
GetParentRow. Estos mtodos necesitan que les proporcione o el nombre de la relacin o el propio
DataRelation.
Si su DataSet con tipo contiene objetos DataRelation tambin contendr mtodos que le permiten
recorrer datos jerrquicos sin necesidad de especificar la relacin o su nombre. En el DataSet que hemos
creado en este captulo Visual Studio aadi automgicamente" un DataRelation que relaciona los objetos
DataTable Customers y Orders. Como resultado, la clase CustomersDataTable expone un mtodo
GetOrdersRows y la clase OrdersDataTable expone un mtodo GetCustomersRow.
El siguiente fragmento usa el mtodo GetOrdersRow para mostrar todos los clientes y los pedidos de
cada cliente.
//Con tipo
foreach(NorthwindDataSet.CustomersRow filaCliente in ds.Customers){
Console.WriteLine(Pedidos de {0}, filaCliente.CompanyName);
foreach(NorthwindDataSet.OrdersRow filaPedido in filaCliente.GetOrdersRows())
Console.WriteLine(\t{0} {1:d}, filaPedido.OrderID, filaPedido.OrderDate);
}
//Sin tipo
DataRelation relacin = ds.Relations[FK_Orders_Customers];
foreach(DataRow filaCliente in ds.Tables[Customers].Rows){
Console.WriteLine(Pedidos de {0}, filaCliente[CompanyName]);
foreach(DataRow filaPedido in filaCliente.GetChildRows(relacin))
Console.WriteLine(\t{0} {1:d},
filaPedido[OrderID], filaPedido[OrderDate]);
}

8.3.6

Otras caractersticas de DataSet, DataTable y DataRow

Las clases que son parte del DataSet con tipo derivan de las clases DataSet, DataTable y DataRow.
Esto significa que puede tratarlas igual que sus contrapartidas sin tipo.
Por ejemplo, las clases DataSet con tipo no tienen mtodos propios para leer y escribir datos XML e
informacin de esquema. Pero como estas clases derivan de la clase DataSet exponen mtodos como
ReadXML y WriteXml. Por tanto, para todas las dems tareas, como usar un DataSet con tipo junto con un
DataView, puede tratar el DataSet con tipo igual que cualquier otro.

8.3.7

Aadir su propio cdigo

El componente que genera los DataSet con tipo no puede anticipar todas sus necesidades. Tal vez
quiera aadir su propio cdigo para proporcionar funcionalidad adicional. En esta seccin veremos un
ejemplo de cmo puede aadir al DataSet con tipo cdigo para mejorar su rendimiento. El aumento de
rendimiento real en el ejemplo es pequeo, pero esto no es lo importante. El objetivo es comprender cmo
puede aadir funcionalidad.
Recordar que al tratar el trabajo con datos relacionales discutimos las diferentes sobrecargas del
mtodo GetChildRows de la clase DataRow. Puede pasar un objeto DataRow o una cadena que se
corresponde con la propiedad RelationName de un objeto DataRelation. Si pasa una cadena est forzando
al DataRow a buscar implcitamente el DataRelation en la coleccin de relaciones del DataSet. En otras
palabras, en el siguiente ejemplo el primer fragmento se ejecuta ms rpidamente que el segundo porque
la bsqueda basada en cadena se hace una sola vez.
DataSet ds = new DataSet();
string nombreRelacion = RelPedidosClientes;
int numPedidos;
//Asume que el DataSet contiene tablas de clientes y de pedidos

135

ADO.NET 2.0

//Mejor rendimiento
DataRelation relacion = ds.Relations[nombreRelacion];
foreach(DataRow filaCliente in ds.Tables[Customers].Rows)
int numPedidos = filaCliente.GetChildRows(relacion).Length;
//Peor rendimiento
foreach(DataRow filaCliente in ds.Tables[Customers].Rows)
int numPedidos = filaCliente.GetChildRows(nombreRelacion).Length;

En trminos generales el cdigo dentro del DataSet con tipo es ms eficiente. Por ejemplo, el cdigo que
devuelve el valor de una columna en un DataRow por medio de una propiedad con tipo realiza una
bsqueda usando una referencia al DataColumn en lugar del nombre de la columna.
Desafortunadamente, el cdigo para acceder a filas relacionadas utiliza bsquedas basadas en cadena
usando el nombre de la relacin. Puede ver este cdigo seleccionando el mtodo GetOrderRows en el
ejemplo de la seccin Datos jerrquicos, pulsando con el botn derecho y seleccionando Ir a definicin.
El cdigo generado por el diseador hace mucho en cada lnea de cdigo, por lo que puede ser difcil de
seguir. En el siguiente fragmente he hecho la lgica ms fcil de seguir. El cdigo primero localiza el
DataRelation necesario usando una bsqueda basada en cadena y lo pasa al mtodo GetChildRows del
DataRow base. Finalmente, el cdigo moldea el array de objetos DataRow que devuelve GetChildRows a
un array de DataRow con tipo. FK_Orders_Customers es el nombre que asigna Visual Studio al crear la
relacin automticamente.
public OrdersRow[] GetOrdersRows(){
DataRelation relacion;
relacion = this.Table.ChildRelations[FK_Orders_Customers];
return ((OrderRows[])(base.GetChildRows(relacion)));
}

Podemos hacerlo mejor. El DataSet con tipo ya tiene un campo privado que contiene el DataRelation,
llamado relationFK_Orders_Customers, de modo que podemos modificar este cdigo para acceder al
DataSet del DataRow, moldear el DataSet al DataSet con tipo y acceder al campo privado para evitar la
bsqueda basada en cadena:
public OrdersRow[] GetOrdersRows{
NorthwindDataSet ds = (NorthwindDataSet)this.Table.DataSet;
DataRelation relacion = ds.relationFK_Orders_Customers;
return ((OrdersRow[])(base.GetChildRows(relacion)));
}

Podramos simplemente aplicar los cambios al cdigo generado por el diseador. Pero como este cdigo
lo genera Visual Studio, cualquier cambio se perder la siguiente vez que cambie la estructura del DataSet
con tipo. En vez de esto, aadiremos el cdigo a una clase parcial para el DataSet con tipo. Tambin
tendremos que dar al mtodo un nuevo nombre de modo que no entre en conflicto con el generado por el
diseador. Como ambos mtodos pertenecen a la misma clase no podemos reemplazar al generado.
Para aadir el nuevo mtodo pulse con el botn derecho sobre NortwhindDataSet.xsd en el explorador
de soluciones y seleccione Ver cdigo para crear la clase parcial. Una vez que incluya el cdigo siguiente
en esta clase podr llamar al nuevo mtodo en lugar de a GetOrderRows en su clase CustomersRow.
partial public class NorthwindDataSet{
partial public class CustomersRow{
public OrdersRow[] leePedidos(){
NorthwindDataSet ds = (NorthwindDataSet)this.Table.DataSet;
DataRelation relacion = ds.relationFK_Orders_Customers;
return ((OrdersRow[])(base.GetChildRows(relacion)));
}
}
}

8.4 Cundo usar DataSet con tipo


Los DataSet con tipo exponen mucha ms funcionalidad que los DataSet estndar, pero estas
caractersticas avanzadas no siempre son necesarias. Si no va a usar las caractersticas extra, tal vez sea
mejor que use la versin bsica; los componentes simples por lo general son ms rpidos que los que
exponen ms funcionalidades.
Con esto en mente, vamos a ver cules son las ventajas de usar DataSet con tipo.

8.4.1

Ventajas en tiempo de diseo

Ya he tratado las ventajas de tiempo de diseo ms obvias de los objetos DataSet con tipo: escribir
cdigo para acceder a datos en un DataSet con tipo es mucho ms fcil que con un DataSet estndar.
136

Escuela de Informtica del Ejrcito

Tambin hay que escribir menos cdigo porque el DataSet con tipo contiene cdigo para crear el
esquema y los objetos DataTable, DataColumn, DataRelation y Constraint en el cdigo de inicializacin de
la clase. Si est usando un DataSet sin tipo tiene tres opciones para aadir informacin de esquema al
DataSet: escribir el cdigo, cargar el esquema desde un archivo xsd usando el mtodo ReadXmlSchema del
objeto DataSet o usar el mtodo FillSchema de un DataAdapter. De estas tres, el uso de FillSchema es el
que menos cdigo necesita en tiempo de diseo, pero es una opcin que hay que evitar siempre que sea
posible.
Si est creando una aplicacin Windows o Web que use vinculacin con datos, ser mucho ms fcil
enlazar sus controles en tiempo de diseo si usa un DataSet con tipo. Como el DataSet con tipo incluye
informacin de esquema, Visual Studio puede proporcionar una lista de tablas y columnas con la que
enlazar el control.
Los DataSet con tipo no son solo para vinculacin de datos. Los desarrolladores que crean aplicaciones
multi capa pueden obtener ventajas en tiempo de diseo usando DataSet con tipo. Cuando se aade una
referencia a una librera de clases o servicio Web que devuelve un DataSet con tipo el proyecto tendr su
propia copia del archivo xsd y el archivo de clase del DataSet con tipo. De esta forma la aplicacin cliente
puede aprovechar las ventajas en tiempo de diseo de los objetos DataSet con tipo.

8.4.2

Ventajas en tiempo de ejecucin

Cules son las implicaciones en tiempo de ejecucin del uso de objetos DataSet con tipo?. Cmo
afectan al rendimiento de las aplicaciones?. No solo es ms fcil escribir el cdigo para acceder a los
contenidos del DataSet con tipo, sino que el cdigo tambin puede mejorar el rendimiento de su aplicacin.
El siguiente fragmento muestra la forma estndar de asignar el contenido de una columna a una cadena
usando tanto un DataSet con tipo como uno sin tipo.
string nombreCia;
//Sin tipo
DataSet dsSinTipo = new DataSet();
strNombreCia = (string)dsSinTipo.Tables[Customers].Rows[0][CompanyName];
//Con tipo
NorthwindDataSet dsTipo = new NorthwindDataSet();
strNombreCia = dsTipo.Customer[0].CompanyName;

El cdigo con tipos tiene mejor rendimiento; exactamente cunto, depende de su cdigo. Los mtodos de
comprobacin de nulos propios de cada campo tambin mejoran el rendimiento respecto a la comprobacin
genrica.
As mismo, cuando usamos DataSet sin tipo, tenemos dos formas de acceder a una columna por medio
del objeto DataRow. Una de ellas, por medio de ndice, proporciona buen rendimiento pero es difcil de
mantener, la otra, por medio de una cadena que representa el nombre de columna, es fcil de mantener
pero tiene mal rendimiento. Al usar DataSet con tipo obtenemos lo mejor de ambas formas, accediendo
mediante propiedades del DataRow con tipo.
Se puede lograr un rendimiento incluso superior usando DataSet con tipo y utilizando de forma
inteligente las posibilidades de bsqueda, por ejemplo, obteniendo una referencia a DataColumn antes de
entrar en el bucle de lectura de registros y usando esta referencia en cada vuelta. Pero escribir este cdigo
lleva tiempo y se debe planificar cuidadosamente. El uso de DataSet con tipo nos proporciona una mayor
facilidad a la hora de escribir cdigo a la vez que mantiene un elevado rendimiento.

8.4.3

Consideraciones adicionales

Los objetos DataSet con tipo pueden simplificar la codificacin y ayudarle a conservar su cordura. Estos
son algunos otros temas que debe considerar si decide usar objetos DataSet con tipo.

Hacer cambios en la estructura


Si necesita cambiar la estructura de un DataSet con tipo aadiendo o cambiando objetos DataColumn
tendr que volver a generarlo. Tenga esto en mente si crea una aplicacin multicapa en la que la capa
media devuelva DataSet con tipo. Si vuelve a generar el DataSet con tipo que devuelve la capa media
tambin tendr que volver a genera la aplicacin cliente despus de refrescar la referencia al objeto de capa
media. Si lo que cambia es la estructura de los datos que devuelve el servidor probablemente tendr que
cambiar el cdigo cliente que accede a esta estructura, independientemente de que use o no DataSet con
tipo.

Convertir objetos DataSet


Como los DataSet con tipo heredan de la clase DataSet estndar, el siguiente cdigo que accede a un
DataSet con tipo por medio del interface DataSet estndar es vlido:
137

ADO.NET 2.0

NorthwindDataSet dsConTipo = new NorthwindDataSet();


DataSet dsSinTipo = (DataSet)dsConTipo;

Sin embargo, slo puede moldear un DataSet sin tipo a DataSet con tipo si el DataSet sin tipo se cre
originalmente como instancia del mismo DataSet con tipo. Si no es as, el intento de moldeo lanzar una
InvalidCastException. El siguiente fragmento puede aclarar esta idea:
NorthwindDataSet dsConTipo1, dsConTipo2;
DataSet dsSinTipo;
//Este cdigo funciona
dsConTipo1 = new NorthwindDataSet();
dsSinTipo = (DataSet)dsConTipo1;
dsConTipo2 = (NorthwindDataSet)dsSinTipo;
//Este cdigo lanza una excepcin
dsSinTipo = new DataSet();
dsConTipo2 = (NorthwindDataSet)dsSinTipo;

Qu pasa si tiene un DataSet sin tipo y quiere acceder a su contenido usando una clase DataSet con
tipo?. Si el DataSet sin tipo se cre como tal desde el principio no puede moldear el objeto a ningn DataSet
con tipo. Sin embargo, puede usar el mtodo Merge del DataSet con tipo para importar los datos del
DataSet sin tipo:
NorthwindDataSet dsConTipo = new NorthwindDataSet();
DataSet dsSinTipo = new DataSet();
dsConTipo.Merge(dsSinTipo);

El mtodo Merge tambin es til si necesita mover datos entre instancias de dos clases DataSet con tipo
diferentes. Tambin puede usar los mtodos WriteXml y ReadXml para este movimiento de datos si incluye
el esquema XML en las llamadas a estos dos mtodos.

Caractersticas sin tipo de objetos DataSet con tipo


Supongamos que su aplicacin usa objetos DataSet con tipo y quiere enviar uno de ellos a su servidor
de capa media para enviar los cambios a la base de datos. Puede usar el mtodo GetChanges del DataSet
con tipo para crear un nuevo DataSet que contiene solo las filas modificadas. Sin embargo, el mtodo
GetChanges devuelve un DataSet sin tipo, pero este DataSet sin tipo se puede moldear al tipo original:
NorthwindDataSet dsConTipoCompleto = new NorthwindDataSet();
//Rellena el DataSet y trabaja con l
DataSet dsSinTipo = dsConTipoCompleto.GetChanges();
NorthwindDataSet dsConTipoSoloModificado = (NorthwindDataSet)dsSinTipo;

El DataSet con tipo tiene otros mtodos que devuelven datos sin tipo. Por ejemplo, el mtodo Select
devuelve un array de objetos DataRow. No puede moldear el array a un array de objetos DataRow con tipo,
pero s puede moldear los objetos DataRow sin tipo individuales a su contrapartida con tipo.
Al DataView se le aplican reglas similares. No puede acceder a su contenido directamente por medio de
las clases con tipo, pero puede convertir el DataRow que devuelve la propiedad Row de los objetos
DataRowView a una clase con tipo:
NorthwindDataSet dsConTipo = new NorthwindDataSet();
//Rellena el DataSet y trabaja con l
DataView vistaClientes = new DataView(dsConTipo.Clientes);
NorthwindDataSet.CustomersRow filaCliente =
(NorthwindDataSet.CustomersRow)vistaClientes(0).Row;

8.4.4

Aadir manualmente tables y columnas

Aunque arrastrar y soltar columnas, o tablas enteras, desde el explorador de servidores es la forma ms
sencilla de aadir esquema a un DataSet con tipo, no es la nica forma.

Crear un DataTable manualmente


Hay varias formas de aadir un nuevo DataTable a su DataSet con tipo. Puede hacerlo sealando la
opcin Agregar del men Datos en Visual Studio y seleccionando DataTable. Tambin puede pulsar con el
botn derecho sobre el DataSet con tipo y seleccionar Agregar y DataTable en el men de contexto.
Tambin puede arrastrar un objeto DataTable desde la lengeta DataSet de la caja de herramientas.
Una vez aadido el DataTable puede cambiar su nombre pulsando sobre el predeterminado en la parte
superior. Tambin puede hacerlo por medio de la ventana de propiedades.

138

Escuela de Informtica del Ejrcito

Aadir DataColumn
Ahora que tiene un nuevo DataTable querr aadirle columnas. Seleccione el DataTable y use o el men
Datos de Visual Studio o el men de contexto que se muestra al pulsar sobre la tabla con el botn derecho y
en ambos casos seleccione Agregar y DataColumn.

Configurar las propiedades de DataColumn


El diseador de DataSet con tipo facilita la consulta y modificacin de propiedades de los nombres de
DataColumn, pero normalmente ser necesario modificar ms propiedades. La forma ms sencilla es
seleccionar el DataColumn y abrir la ventana de propiedades.

Establecer la clave principal de una tabla


Para establece la propiedad PrimaryKey de un DataTable seleccione el, o los, DataColumn que van a
componer la clave. A continuacin pulse con el botn derecho sobre uno de ellos o seleccione el men
Datos, y en ambos casos pulse Establecer clave principal. Si posteriormente quiere modificar o eliminar la
clave principal dispondr de ambas opciones por medio del men.

Aadir relaciones
Cuando se crea un nuevo DataTable arrastrando columnas desde el Explorador de servidores Visual
Studio aade DataRelation automticamente en base a la informacin de clave externa que recupera de la
base de datos. Pero si est creando las tablas manualmente tambin deber crear las relaciones
manualmente.
Para aadir un DataRelation pulse con el botn derecho sobre cualquier elemento del DataSet con tipo,
o abra el men Datos, y seleccione Agregar y Relacin. Esta accin abrir el dilogo de relacin.
En la parte superior de este dilogo puede indicar el nombre de la nueva relacin, y justo debajo puede
seleccionar la tabla primaria, madre, y la secundaria, hija, de la relacin. Una vez seleccionadas las tablas,
en la parte central puede seleccionar las columnas que definen la relacin.
La mitad inferior del dilogo permite controlar si la accin crear un DataRelation, una
ForeignKeyConstraint o ambos. La opcin predeterminada es crear solo un DataRelation una opcin que
puede crear problemas para muchos desarrolladores, como veremos en breve. Si especifica que se cree
una ForeignKeyConstraint podr especificar valores para sus propiedades UpdateRule, DeleteRule y
AcceptRejectRule. Los valores predeterminados son respectivamente Cascade, Cascade y None.
Utilizar DataRelation sin ForeignKeyConstraint puede mejorar el rendimiento del cdigo. Si sabe que va a
trabajar con datos de solo lectura y que los datos se obtienen solamente de una base de datos que ya los
ha validado usando una restriccin de clave externa similar, no necesita el ForeignKeyConstraint.
Pero si va a permitir cambios en los datos del DataSet debe tener ForeignKeyConstraint asociados con
sus DataRelation. Es el ForeignKeyConstraint el que propaga los cambios entre tablas. Vamos a examinar
dos escenarios en los que no tener un ForeignKeyConstraint asociado con un DataRelation puede causar
problemas.
Si borra una fila madre en un DataSet las filas hijas se marcarn automticamente como borradas si y
slo si el DataRelation est asociado con un ForeignKeyConstraint cuya DeleteRule tenga el valor Cascade.
Si no es este el caso, al enviar las modificaciones a la base de datos es posible que esta no admita los
cambios, ya que la fila madre borrada tiene filas hijas en la base de datos.
Puede surgir otro problema si consulta la tabla hija. Supongamos que el DataSet contiene pedidos e
informacin de pedidos para un cliente concreto. El usuario marca un pedido como pendiente de borrar y
usa el mtodo Compute de la tabla de detalle de pedidos para calcular el total de todos los pedidos del
cliente. Como al no exist ForeignKeyConstraint el borrado del pedido no se propaga a los detalles, el valor
devuelto por el mtodo Compute incluir el pedido borrado.
De forma similar, los cambios en un valor clave de una fila madre se propagarn automticamente a las
filas hijas si y slo si el DataRelation est asociado con un ForeignKeyConstraint cuyo UpdateRule tenga el
valor Cascade. Por el momento supongamos que est trabajando con valores de autoincremento en su
DataSet y tiene un DataRelation que no est asociado con un ForeignKeyConstraint. Cuando recupere los
nuevos valores generados por la base de datos para las filas madre recin enviadas, estos valores no se
propagarn a las filas hijas relacionadas. Los valores de la columna de clave externa de las filas hijas
seguirn siendo los que gener ADO.NET; con un poco de suerte estos valores no existirn en la base de
datos y la actualizacin fallar a causa de una violacin de clave externa. Si no tiene suerte, los valores
generados por ADO.NET existirn en la base de datos, pero correspondern a un registro diferente de la
tabla madre; la operacin se ejecutar, pero las filas hijas estarn relacionadas con una madre errnea.

139

ADO.NET 2.0

8.4.5

Mejorar los valores predeterminados

Ya hemos dicho que es posible mejorar el cdigo escrito por Visual Studio. Vamos a ver un par de reas
en los que los DataSet con tipo puede aprovechar estas mejoras.

Asociar DataRelation autogenerados con ForeignKeyConstraint


Los DataRelation que crea automticamente Visual Studio cuando se arrastran tablas y columnas desde
el Explorador de servidores no estn asociados con ForeignKeyConstraint. Le recomiendo que cree esta
asociacin manualmente.
Pulse con el botn derecho del ratn sobre cada una de las DataRelation y seleccione Editar relacin.
Ver el dilogo de relacin. Seleccione la opcin Tanto relacin como restriccin Foreign Key. Tambin
deber seleccionar Cascade para Regla de actualizacin y Regla de eliminacin; deje None en Regla de
aceptacin o rechazo.

Forzar a las columnas de auto incremento a generar valores negativos


Al tratar los problemas que pueden surgir por la no existencia de ForeignKeyConstraint se cit que el
peor escenario es aquel que en el que el valor usado por las filas hijas para establecer la relacin existe en
la base de datos, pero corresponde a una fila madre diferente de la pretendida.
La mejor forma de evitar este escenario es hacer que ADO.NET genere valores negativos para los auto
incrementos. Para conseguirlo asigne -1 a las propiedades AutoIncrementSeed y AutoIncrementStep.
Simplemente seleccione la columna y asigne estos valores en la ventana de propiedades.

8.5 Introduccin a TableAdapter


Al construir el DataSet al principio de este captulo pudo ver que cada DataTable creado mediante
arrastrar y soltar creaba tambin un TableAdapter. Vamos a examinar este objeto ms de cerca.
Un TableAdapter proporciona parte de la lgica asociada con las clases DataAdapter. Puede usar un
TableAdapter para cargar un DataTable en un DataSet con tipo, o para enviar los cambios pendientes que
almacena el DataTable a la base de datos. Trataremos la actualizacin de la base de datos con
SqlDataAdapter en los Captulos 9 y 10. Los TableAdapter ofrecen sobrecargas similares del mtodo
Update de modo que la informacin que se trata en los captulos citados tambin se aplica a los
TableAdapter.

8.5.1

Crear un TableAdapter

Hay dos formas principales de crear un TableAdapter arrastrar y soltar elementos desde el Explorador
de servidores o usar el sistema de mens de Visual Studio.

Arrastrar y soltar
Cuando arrastr las columnas CustomerID y CompanyName de la tabla Customers desde el Explorador
de servidores al diseador del DataSet Visual Studio aadi un DataTable para la tabla Customers con
DataColumn para las columnas CustomerID y CompanyName. Visual Studio tambin cre un TableAdapter
configurado para ejecutar la consulta
SELECT CustomerID, CompanyName FROM Customers

De forma similar, al arrastrar columnas de la tabla Orders se aadi un DataTable con las columnas
correspondientes y un TableAdapter para recuperar estas columnas desde la tabla SQL Server al
DataTable.

Aadir un TableAdapter usando el men de Visual Studio


Tambin puede usar los mens Visual Studio para aadir un TableAdapter. Simplemente seleccione
Datos, seale Agregar y pulse TableAdapter. Tambin puede pulsar con el botn derecho, sealar Agregar
y pulsar TableAdapter. Cualquiera de estas acciones lanzar el asistente de configuracin de TableAdapter
que le guiar por este proceso.
La primera pgina del asistente le pide informacin sobre la conexin. Puede seleccionar las conexiones
disponibles en el Explorador de servidores en la lista desplegable del asistente. Tambin puede pulsar el
botn Nueva conexin, que lanzar el dilogo que discutimos al crear una conexin en el Explorador de
servidores.
Cuando seleccione una conexin que contiene informacin sensible, como contraseas, el asistente le
permite controlar si quiere o no guardar esta informacin en la cadena de conexin. El botn + junto al texto
Cadena de conexin en la parte inferior permite mostrar la cadena de conexin correspondiente a la
conexin seleccionada.
Una vez especificada la informacin de conexin el asistente le pide que elija el tipo de comando que
debe ejecutar el TableAdapter. Puede elegir usar instrucciones SQL, crear nuevos procedimientos
almacenados o usar procedimientos almacenados existentes. Las dos opciones finales solo estn
140

Escuela de Informtica del Ejrcito

disponibles solamente si est trabajando con una base de de datos SQL Server. La segunda opcin crear
procedimientos almacenados para recuperar, insertar, actualizar y borrar filas de datos en base a una
consulta SQL. La tercera opcin permite seleccionar procedimientos almacenados para las mismas
acciones.
Si especifica que quiere que el DataAdapter ejecute una sentencia SQL la siguiente pgina del asistente
le pedir esta consulta. Puede escribir la sentencia por si mismo o pulsar el botn Generador de consultas
para usar esta herramienta. El primer paso del generador le permite seleccionar una tabla, vista, funcin o
sinnimo.
Una vez seleccionada una tabla como base para la consulta SQL el Generador de consultas le presenta
una forma grfica para definirla. Puede seleccionar las columnas que quiere recuperar y aadir criterios de
ordenacin y bsqueda. El Generador le muestra la consulta correspondiente y le permite ejecutarla. Una
vez satisfecho con la consulta pulse el botn Aceptar.
El asistente de configuracin de TableAdapter ofrece opciones avanzadas para escenarios que implican
actualizaciones. Puede acceder a estas opciones pulsando el botn Opciones avanzadas en la pgina el
asistente que pide la sentencia SQL. Las opciones que se muestran en este caso las discutiremos al tratar
las actualizaciones.
Una vez proporcionada la sentencia SQL el asistente le permite controlar los mtodos disponibles en su
TableAdapter. De forma predeterminada el asistente crea un mtodo Fill que rellena un DataTable existente
con los resultados de la consulta. El asistente tambin crea un mtodo GetData que devuelve una nueva
instancia del DataTable que contiene los resultados de la consulta. El asistente permite especificar si quiere
o no estos mtodos.
De forma predeterminada el asistente tambin crea mtodo DBDirect si la consulta hace referencia a una
sola tabla con una clave principal y si la consulta incluye todas las columnas que forman la clave principal.
Estos mtodos Insert, Update y Delete permiten enviar nuevas filas o modificar las existentes sin la
necesidad de tener un DataRow correspondiente en el DataSet con tipo.
Finalmente el asistente muestra los resultados, explicando la funcionalidad que el asistente proporciona
al DataAdapter.

8.5.2

Usar un DataAdapter

Instanciar un TableAdapter en tiempo de ejecucin es sencillo. Las clases TableAdapter no precisan


inicializacin adicional. Con un SqlDataAdapter, por ejemplo, es necesario proporcionar informacin de
consulta y de conexin ya sea por medio del constructor o utilizando propiedades. Cuando se crea el
TableAdapter en tiempo de diseo ya se proporciona toda esta informacin. Como resultado, cuando se
instancia el TableAdapter ya est listo para usar.

Fill y ClearBeforeFill
Los TableAdapter facilitan la carga de un DataSet con tipo. Basta con instanciar el TableAdapter y pasar
una instancia del DataTable con tipo al mtodo Fill:
NorthwindDataSet dsNorthwind = new NorthwindDataSet();
NorthwindDataSetTableAdapters.CustomersTableAdapter daClientes;
//Crea el TableAdapter
daClientes = new NorthwindDataSetTableAdapters.CustomersTableAdapter();
//Carga el DataSet
daClientes.Fill(dsNorthwind.Customers);
//Muestra los resultados
foreach(NorthwindDataSet.CustomersRow filaCliente in dsNorthwind.Customers)
Console.WriteLine(filaCliente.CompanyName);

Como puede deducir del fragmento anterior, los espacios de nombres residen en un espacio de nombres
separado basado en el nombre del DataSet con tipo.
Igual que en el caso de SqlDataAdapter el mtodo Fill de TableAdapter devuelve un entero que
corresponde al nmero de filas recuperadas.
El mtodo Fill de TableAdapter se diferencia ligeramente del mtodo Fill de SqlDataAdapter. A diferencia
del mtodo de SqlDataAdapter el de TableAdapter no est sobrecargado; solamente acepta una instancia
del DataTable con el que est asociado.
TableAdapter tiene una propiedad ClearBeforeFill con el valor predeterminado true. Si esta propiedad
tiene el valor true cuando se llama al mtodo Fill se eliminan todas las filas del DataTable antes de cargarlo
con los resultados de la consulta. Si lo que quiere es aadir nuevos contenidos a la tabla asigne false a esta
propiedad.
141

ADO.NET 2.0

GetData
TableAdapter expone un mtodo GetData que devuelve un nuevo DataSet con tipo que contiene los
resultados de la consulta. Usando este mtodo puede ahorrar algunas lneas de cdigo al no tener que
crear la instancia del DataTable.

Controlar la conexin
De forma predeterminada cada TableAdapter usa su propio objeto Connection. Incluso si tiene varios
TableAdapter diseados para comunicarse con la misma base de datos, cada uno sigue teniendo su propio
objeto conexin. Sin embargo, TableAdapter expone una propiedad Connection que puede usar la conexin
utilizada para comunicarse con la base de datos. La disponibilidad de esta propiedad est controlada por el
diseador del DataSet con tipo. Seleccione un TableAdapter en el diseador y ver en la ventana de
propiedades la propiedad ConnectionModifier. De forma predeterminada est marcada como internal, por lo
que estar disponible para clases dentro del ensamblado, pero no desde otros ensamblados.
Tambin puede crear su propio SqlConnection y asignarla a uno o varios TableAdapter usando la
propiedad Connection.
Recuerde que de forma predeterminada un SqlDataAdapter abre su conexin cuando se llama al mtodo
Fill y la vuelve a cerrar cuando termina, si la conexin est inicialmente cerrada; lo mismo sucede con
TableAdapter. Es una buena medida abrir la conexin explcitamente antes de llamar al mtodo Fill de todos
los TableAdapter que la emplean, y cerrarla a continuacin.

Actualizacin
El TableAdapter tiene un mtodo Update sobrecargado que acepta una instancia del DataSet o
DataTable con tipo, un nico DataRow o un array de DataRow. Estas sobrecartas se corresponden a grosso
modo con las del mtodo Update de SqlDataAdapter. El TableAdapter tiene una sobrecarga adicional,
denominada mtodo de DBDirect que trataremos en breve.
El mtodo Update de TableAdapter se utiliza igual que el de SqlDataAdapter, que discutiremos en los
Captulos 9 y 10.

Mtodos de DBDirect
Un SqlDataAdapter est diseado para enviar los cambios almacenados en los DataRow a una base de
datos usando consultas parametrizadas. Un TableAdapter permite modificar el contenido de una base de
datos sin tener que crear un DataRow.
Si el asistente de configuracin de TableAdapter es capaz de encontrar la clave principal de la tabla en la
consulta generar la lgica para enviar inserciones, actualizaciones y borrados pendientes a la base de
datos. Esta lgica estar almacenad en InsertCommand, UpdateCommand y DeleteCommand del
SqlDataAdapter contenido en el TableAdapter.
El TableAdapter tambin hace que esta lgica est disponible por medio de los mtodos Insert, Update y
Delete diseados especficamente para el DataTable con tipo. Por ejemplo, el siguiente cdigo inserta una
nueva fila en la tabla de clientes de los ejemplos anteriores. A continuacin borra la misma fila, de modo que
se pueda ejecutar varias veces sin provocar un error.
NorthwindDataSetTableAdapters.CustomersTableAdapter adpClientes;
adpClientes = new NorthwindDataSetTableAdapters.CustomersTableAdapter();
//Inserta una nueva fila en la tabla Customers de la base de datos
adpClientes.Insert("CIANUE", "Compaa Nueva");
//Borra la fila anterior de la base de datos
adpClientes.Delete("CIANUE", "Compaa Nueva");

El mtodo Insert ejecuta una consulta INSERT parametrizada, usando los parmetros del mtodos como
valores de la consulta. En el ejemplo anterior los parmetros se corresponden con las DataColumn
CustomerID y CompanyName del DataTable con tipo.
Visual Studio denomina a estos mtodos parametrizados de TableAdapter mtodos DBDirect, porque los
valores se envan directamente a la base de datos sin crear un DataRow.
Los parmetros del mtodo DBDirect dependen de los parmetros de la lgica de actualizacin
almacenada en el SqlDataAdapter interno del TableAdapter. Los parmetros de salida de la lgica de
actualizacin se trasladan a parmetros out de los mtodos DBDirect. Se realizan comprobaciones de
concurrencia el mtodo Update recibe los valores originales y actuales de las columnas. Los valores para
columnas de tipos por valor que acepten nulos se convierten en tipos anulables de .NET 2.0.

BaseClass
En tiempo de diseo puede controlar la clase base de la que deriva TableAdapter por medio de la
propiedad BaseClass en la ventana de propiedades. Esto parece una gran idea; puede hacer que todos su
142

Escuela de Informtica del Ejrcito

TableAdapter deriven de una misma clase base. Sin embargo, an estoy por encontrar un uso eficaz para
esta opcin.

8.5.3

Aadir ms consultas

Mientras que un SqlDataAdapter solo permite una consulta de seleccin el SqlCommand especificado
en la propiedad SelectCommand un TableAdapter puede contener varias. Vamos a ver cmo aadir ms
consultas al TableAdapter y cmo puede usar estas consultas adicionales.
La clase OrdersTableAdapter que creamos arrastrando y soltando columnas desde la tabla Orders del
explorador de servidores recupera todas las filas de la tabla Orders. Vamos a aadir una consulta
parametrizada que devuelve solo los pedidos de un cliente concreto.
Abra la clase NorthwindDataSet en el diseador de DataSet y seleccione OrdersTableAdapter. Pulse con
el botn derecho y seleccione Agregar consulta. De esta forma volver a abrir el asistente de configuracin
de TableAdapter que ya vio anteriormente.
En la primera pgina del asistente indique que quiere usar instrucciones SQL. En la segunda
especifique SELECT que devuelve filas. Modifique la consulta que muestra el asistente aadiendo al final
WHERE CustomerID = @CustomerID. En la siguiente pgina del asistente cambie los nombre de los
mtodos Fill y GetData por FillPorIDCliente y GetPorIDCliente.
Ahora el TableAdapter contiene dos consultas una que devuelve todas las filas de pedido y una que
devuelve solo los pedidos para un cliente concreto. Si examina el cdigo generado para el TableAdapter
ver que tiene una propiedad privada llamada CommandCollection que es un array de SqlCommand. Cada
vez que llama a uno de los mtodos Fill o GetData el TableAdapter asigna el SqlCommand apropiado a la
propiedad SelectCommand del SqlDataAdapter antes de llamar al mtodo Fill.
Ahora podemos usar la nueva consulta:
NorthwindDataSetTableAdapters.OrdersTableAdapter adpPedidos;
NorthwindDataSet.OrdersDataTable tablaPedidos;
adpPedidos = new NorthwindDataSetTableAdapters.OrdersTableAdapter();
tablaPedidos = adpPedidos.GetDataPorIDCliente("ALFKI");
foreach(NorthwindDataSet.OrdersRow filaPedido in tablaPedidos)
Console.WriteLine("{0} - {1} - {2:d}", filaPedido.OrderID,
filaPedido.CustomerID, filaPedido.OrderDate);

Tenga en cuenta que los nuevos mtodos pueden no estar disponibles hasta que no se genere el
proyecto.

8.5.4

Aadir su propio cdigo

El cdigo generado por el diseador para los TableAdapter reside en el mismo archivo que el cdigo
para el DataSet con tipo. Como el DataSet con tipo, el cdigo para el TableAdapter utiliza clases parciales,
por lo que puede aadir su propio cdigo sin preocuparse de que Visual Studio lo sobrescriba.
Si, por ejemplo, quiere aadir una propiedad pblica que devuelve el SqlDataAdapter correspondiente a
un TableAdapter puede utilizar el siguiente cdigo.
namespace NorthwindDataSetTableAdapters{
partial class CustomersTableAdapter{
public SqlDataAdapter LeeAdaptador{
this.Adapter.SelectCommand = this.CommandCollection[0];
return this.Adapter;
}
}
}

8.5.5

Limitaciones de TableAdapter

Los TableAdapter puede facilitar la carga de un DataSet, pero tienen algunas limitaciones

Componente, no DbDataAdapter
Los TableAdapter no derivan de la clase base de los DataAdapter, DbDataAdapter, sino de la clase
Component del espacio de nombres System.ComponentModel.
Esta forma de trabajo tiene dos inconvenientes. No hay forma de escribir cdigo que acepte un
TableAdapter como parmetro y llame a mtodos o acceda a propiedades del DataAdapter salvo por medio
de reflexin de .NET una caracterstica en cierto modo similar al enlace retrasado de COM. Adems,
algunas caractersticas que estn inmediatamente accesibles en cualquier DataAdapter no estn
disponibles directamente en los TableAdapter. Usar TableAdapter en escenarios en los que necesita
acceder a propiedades del DataAdapter puede ser un problema.
143

ADO.NET 2.0

Por ejemplo, suponga que ha accedido a la propiedad Connection del TableAdapter y ha creado una
SqlTransaction. Cmo puede asociar las consultas del TableAdapter con la transaccin?.
Afortunadamente, este es un escenario que se puede manejar aadiendo un archivo de clase parcial para el
TableAdapter:
namespace NorthwindDataSetTableAdapters{
partial class CustomersTableAdapter{
internal void PoneTransaccion(SqlTransaction txn){
foreach(SqlCommand cmd in this.CommandCollection)
cmd.Transaction = txn;
this.Adapter.InsertCommand.Transaction = txn;
this.Adapter.UpdateCommand.Transaction = txn;
this.Adapter.DeleteCommand.Transaction = txn;
}
}
}

En otros casos puede no haber una solucin sencilla. Por ejemplo, suponga que crea un TableAdapter
que encapsula un OleDbDataAdapter que se comunica con una base de datos Access. Puede que necesite
recuperar valores generados por la base de datos antes de enviar nuevos valores a la base de datos. Si
usara un OleDbDataAdapter verdadero podra aadir cdigo para controlar el evento RowUpdated.
Desafortunadamente, los TableAdapter no exponen eventos.

Lgica de refresco no compatible con actualizaciones por lotes


Habilitar la actualizacin por lotes con un TableAdapter puede ser un reto. Aunque algunas propiedades
del SqlDataAdapter almacenado dentro de un TableAdapter estn disponibles por medio del diseador de
Visual Studio, UpdateBatchSize no es una de ellas. Las clases parciales no proporcionan una solucin
sencilla a dnde colocar el cdigo para cambiar valores generados por el diseado. Salvo que quiera
cambiar manual mente el cdigo del archivo de clase parcial generado por el diseador para el
TableAdapter, la mejor solucin es exponer el SqlDataAdapter subyacente por medio de un mtodo o
propiedad, como la propiedad LeeAdaptador mostrada anteriormente.
Si est trabajando con una base de datos SQL Server el asistente de configuracin de TableAdapter
incluir automticamente lgica para refrescar el contenido de sus DataRow despus de inserciones y
actualizaciones contra la base de datos. El asistente devuelve esta informacin aadiendo a las consultas
INSERT o UPDATE un SELECT que devuelve los contenidos actuales de la fila en la base de datos. Esta
caracterstica es muy til si la base de datos puede generar valores para las filas por medio de
predeterminados, disparadores o columnas con auto incremento.
Aunque esta aproximacin funciona bastante bien en escenarios en los que se envan cambios a la base
de datos fila a fila, que era la nica forma de hacerlo en ADO.NET por medio de un SqlDataAdapter hasta a
versin 2.0, este mtodo no es compatible con las nuevas caractersticas de actualizacin por lotes. Durante
las actualizaciones por lotes solo se permite devolver valores en forma de parmetros de salida.

8.6 Elegir su camino


Los objetos DataSet con tipo pueden ayudarle a crear su aplicacin ms rpidamente. Tambin ayudan
a escribir con mayor facilidad cdigo ms eficiente. Pero no proporcionan el mayor rendimiento posible.
Puede crear una aplicacin ms rpida si usa DataSet sin tipo junto con cdigo bien diseado.
Si decide usar DataSet con tipo los TableAdapter pueden ayudarle a simplificar el proceso de
recuperacin de datos y envo de cambios pendientes. Aunque los TableAdapter ocultan parte de la
complejidad de los SqlDataAdapter, tambin ocultan parte de la funcionalidad a la que tal vez quiera
acceder directamente.
Entonces cul es la eleccin correcta?.
Todo depende de las necesidades de su aplicacin. Si el rendimiento y el control de la aplicacin son las
ms altas prioridades, debe usar solo DataSet sin tipo y SqlDataAdapter. Sin embargo, si vale la pena
perder un poco de rendimiento a cambio de ahorrar horas de desarrollo, debe pensar en usar DataSet con
tipo y TableAdapter.

144

Escuela de Informtica del Ejrcito

Enviar cambios a la base de datos

ADO.NET proporciona a los programadores una potencia y control sin precedentes sobre el envo de
cambios. Sin embargo, pocos desarrolladores comprenden realmente cmo aprovechar esta potencia y este
control efectivamente.
Muchos de los fragmentos de cdigo que he visto se basan en la clase SqlCommandBuilder para
generar lgica de actualizacin. En ocasiones el fragmento incluye un aviso que indica que el lector debe
crear su propia lgica de actualizacin en lugar de usar esta, pero estos fragmentos raras veces explican
por qu o cmo hacerlo.
Mientras ms comprenda cmo puede usar ADO.NET para enviar actualizaciones, ms cmodo se
sentir generando su propia lgica y enviando cambios por medio de procedimientos almacenados. Este
captulo le ayudar a comprender cmo usar un SqlDataAdapter para enviar los cambios pendientes desde
el DataSet a la base de datos. A lo largo del camino tambin aprender cmo y cundo usar herramientas
para ahorrar tiempo sin sacrificar rendimiento o control.
Si ha estado leyendo los captulos de este libro seguidos, ya se sentir cmodo con la creacin de
DataSet con y sin tipo para almacenar los datos que devuelven los objetos SqlDataAdapter. Tambin debe
estar cmodo con la modificacin de los contenidos de un DataSet. Este captulo le ayudar a comprender
los aspectos bsicos del uso de objetos SqlDataAdapter para enviar los cambios que almacena el DataSet a
la base de datos.
Vamos a examinar un pedido de la base de datos
Northwind. La figura 10.1 muestra la consulta utilizada
en el administrador de SQL Server para recuperar
informacin de un pedido. Supongamos que el cliente
quiere modificar su pedido; ya no quiere tofu por que
ha dejado de venderlo. Ahora quiere ms botellas de
salsa y algunas cajas de te. El representante deber
buscar el pedido y modificarlo usando una aplicacin
escrita usando .NET.

Fig.10. 1: Contenido de un pedido

Esta escribiendo la aplicacin que utilizar el representante para modificar pedidos. Gracias a lo que ha
aprendido hasta ahora sabe cmo leer el contenido de los pedidos de un cliente y colocarlo dentro de un
DataSet usando un SqlDataAdapter parametrizado. Tambin sabe cmo modificar los datos que contiene el
DataSet segn las instrucciones del cliente. Pero la modificacin del DataSet no supone modificar la base
de datos. Ahora es necesario cubrir este paso.
Tambin hemos visto que SqlDataAdapter expone un mtodo Update que puede usar para enviar los
cambios pendientes a la base de datos. Por tanto, puede escribir una aplicacin que emplee cdigo como el
siguiente para intentar enviar los cambios al pedido:
//Recupera los contenidos del pedido en un DataTable
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT OrderID, ProductID, Quantity, UnitPrice " +
"FROM [Order Details] WHERE OrderID = @OrderID ORDER BY ProductID";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.SelectCommand.Parameters.AddWithValue("@OrderID", 10503);
DataTable tabla = new DataTable("Detalles");
adaptador.Fill(tabla);
//Modifica el contenido del pedido
//Borra la fila de tofu
DataRow filaBorrar = tabla.Rows[0];
filaBorrar.Delete();
//Duplica la cantidad de salsa
DataRow filaActualizar = tabla.Rows[1];
filaActualizar["Quantity"] = (short)(filaActualizar["Quantity"]) * 2;
//Aade una fila para Te
DataRow filaInsert = tabla.Rows.Add(new object[] { 10503, 1, 24, 18});
//Enva los cambios
try {
adaptador.Update(tabla);
Console.WriteLine("Cambios enviados por xito");
} catch(Exception ex) {
Console.WriteLine("Intento de actualizar fallido: {0}", ex.Message);
}

145

ADO.NET 2.0

Este cdigo se compila correctamente, pero no enva con xito los cambios a la base de datos. Recibir
un mensaje de excepcin con el texto: Update requiere que DeleteCommand sea vlido cuando se pasa la
coleccin DataRow con filas eliminadas.
Los desarrolladores novatos con ADO.NET a menudo se desconciertan con esta excepcin.
SqlDataAdapter permite enviar cambios a la base de datos, pero es necesario especificar la lgica a
emplear para estos envos.
Tiene dos opciones para aadir esta lgica: puede escribir su propio cdigo o decir explcitamente a
ADO.NET que genere la lgica de actualizacin. Existe una tercera opcin que es un hbrido de ambos
puede usar una herramienta de generacin de cdigo como Visual Studio para generar la lgica de
actualizacin en su aplicacin. Este captulo explora las tres opciones.
Como el cdigo que vamos a examinar en este captulo est diseado para modificar el contenido de un
pedido concreto de la base de datos puede que encuentre til tener fragmentos de cdigo simples que
muestren el contenido del pedido y que restablezcan su contenido. Estos son los procedimientos a los que
puede llamar para realizar estas operaciones.
static void MuestraPedido() {
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
using(SqlConnection conexin = new SqlConnection(cadcon)) {
conexin.Open();
string consulta = "SELECT P.ProductName, D.Quantity, D.UnitPrice " +
"FROM [Order Details] D JOIN Products P ON D.ProductID = P.ProductID " +
"WHERE D.OrderID = @OrderID";
int idPedido = 10503;
SqlCommand comando = new SqlCommand(consulta, conexin);
comando.Parameters.AddWithValue("@OrderID", idPedido);
Console.WriteLine("Contenido del pedido {0}", idPedido);
using(SqlDataReader lector = comando.ExecuteReader()) {
while(lector.Read())
Console.WriteLine("\t{0,-32}\t{1}\t{2:c}",
lector.GetString(0), lector.GetInt16(1), lector.GetDecimal(2));
lector.Close();
}
Console.WriteLine();
conexin.Close();
}
}
static void RestablecePedido() {
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
using(SqlConnection conexin = new SqlConnection(cadcon)) {
conexin.Open();
//Borra el contenido actual del pedido
string consulta = "DELETE [Order Details] WHERE OrderID = @OrderID";
SqlCommand comando = new SqlCommand(consulta, 10503);
comando.ExecuteNonQuery();
consulta = "INSERT INTO [Order Details] " +
"(OrderID, ProductID, Quantity, UnitPrice) VALUES " +
"(@OrderID, @ProductID, @Quantity, @UnitPrice)";
comando.CommandText = consulta;
//Vuelve a aadir el tofu
comando.Parameters.AddWithValue("@ProductID", 14);
comando.Parameters.AddWithValue("@Quantity", 70);
comando.Parameters.AddWithValue("@UnitPrice", 23.25);
comando.ExecuteNonQuery();
//Vuelve a aadir la salsa
comando.Parameters["@ProductID"].Value = 65;
comando.Parameters["@Quantity"].Value = 20;
comando.Parameters["@UnitPrice"].Value = 21.05;
comando.ExecuteNonQuery();
conexin.Close();
}
}

146

Escuela de Informtica del Ejrcito

9.1 Actualizaciones con SqlCommand parametrizados


Vamos a dejar a un lado la clase SqlDataAdapter por un momento. Como en los fragmentos sencillos
anteriores, puede enviar actualizaciones usando SqlCommand parametrizados.

9.1.1

Enviar una nueva fila

Puede ejecutar la siguiente consulta INSERT con parmetros para aadir una nueva fila a la tabla. La
sintaxis es directa. Especifica el nombre de la tabla, una lista de nombres de columna y una lista de
parmetros que representan los valores.
SELECT INTO [Order Details](OrderID, ProductID, Quantity, UnitPrice)
VALUES (@OrderID, @ProductID, @Quantity, @UnitPrice)

Como ya sabemos, es posible crear un SqlCommand para ejecutar esta consulta, asignando la propia
consulta a su propiedad CommandText y los objetos SqlParameter adecuados a su coleccin Parameters.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "INSERT INTO [Order Details] " +
"(OrderID, ProductID, Quantity, UnitPrice) VALUES " +
"(@OrderID, @ProductID, @Quantity, @UnitPrice)";
SqlCommand cmdInsertar = new SqlCommand(consulta, cadcon);
cmdInsertar.Parameters.AddWithValue("@OrderID", filaInsertar["OrderID"]);
cmdInsertar.Parameters.AddWithValue("@ProductID", filaInsertar["ProductID"]);
cmdInsertar.Parameters.AddWithValue("@Quantity", filaInsertar["Quantity"]);
cmdInsertar.Parameters.AddWithValue("@UnitPrice", filaInsertar["UnitPrice"]);
try{
cmdInsertar.ExecuteNonQuery();
filaInsertar.AcceptChanges();
Console.WriteLine("Insercin realizada con xito")
}catch(Exception ex){
Console.Writeline("Insercin fallida: {0}", ex.Message);
}

Como este cdigo solo necesita enviar una fila, toma un atajo el mtodo AddWithValue para crear
cada SqlParameter y configurar su propiedad Value en una sola lnea de cdigo. Si necesitara cdigo para
ejecutar la consulta de insercin varias veces, debera crear los parmetros usando el mtodo Add y
despus configurar la propiedad Value de cada uno de ellos antes de la ejecucin.
Bueno, esto es fcil, pero la insercin de filas nuevas es una cosa sencilla; vamos a ver ahora algo un
poco ms complejo.

9.1.2

Actualizar una fila existente

La sintaxis de la consulta UPDATE es ms compleja. Este es un ejemplo que puede ejecutar para
cambiar una fila en la tabla Order Details:
UPDATE [Order Details]
SET OrderID = @OrderID_N, ProductID = @ProductID_N, Quantity = @Quantity_N,
UnitPrice = @UnitPrice_N
WHERE OrderID = @OrderID_V AND ProductID = @ProductID_V AND Quantity = @Quantity_V
AND UnitPrice = @UnitPrice_V

Modificar valores en la clusula SET


La clusula SET de la consulta UPDATE incluye todos los campos de la fila. Necesitamos realmente
incluir todos los campos?. Si solo queremos modificar el campo Quantity, en teora podramos incluir
solamente este campo. Pero el representante puede querer modificar otras columnas segn las peticiones
de los clientes.
Si no sabe qu columnas del DataRow se van a modificar, tiene dos opciones: incluir todas las columnas
actualizables o comparar los valores de las columnas del DataRow con los originales. La segunda opcin
tiene el atractivo de pasar menos datos a la base de datos y exige un trabajo menor a la base de datos. Sin
embargo, aumenta mucho la complejidad del cdigo, porque no solo es necesario cambiar los valores de los
parmetros de cada fila; tambin implica cambiar el texto de la consulta para incluir solo las columnas que
se han modificado.

Columnas clave en la clusula WHERE


Es la clusula WHERE la que hace que esta consulta sea ms compleja que la consulta INSERT.
Cuando quiere modificar una fila debe proporciona a la base de datos informacin suficiente para localizar la
fila que debe modificar. La clave principal de la tabla Order Details se compone de las columnas OrderID y
ProductID. Como la clave principal impone la unicidad dentro de la base de datos, hacer referencia a estas
columnas en la clusula WHERE basta para garantizar que solamente se modifica una fila.
147

ADO.NET 2.0

Varios SqlParameter por columna


Puede haber observado que los parmetros para las columnas OrderID y ProductID de la clusula
WHERE no son los mismos que en la clusula SET. Con la lgica de actualizacin puede que quiera, o
necesite, enviar varias versiones de una cierta columna en la consulta parametrizada.
Los representantes de ventas pueden haber decidido que la mejor forma de satisfacer la peticin del
cliente para eliminar un elemento del pedido y aadir otro es simplemente modificar el DataRow
correspondiente al elemento no deseado, cambiando los valores de ProductID, Quantity y UnitPrice para
reflejar los valores deseados. En este escenario ser conveniente pasar a la consulta dos versiones
diferentes de la columna ProductID. Necesita pasar el valor original recuperado para la columna ProductID
del DataRow en la clusula WHERE de modo que la base de datos pueda localizar qu fila modificar; para
la clusula SET tendr que pasar la versin actual. Como ya hemos visto, el DataRow permite obtener
ambos valores.
Podemos combinar la consulta UPDATE con lgica para asignar valores de la versiones actuales y
originales de las columnas del DataRow a parmetros de la consulta usando el siguiente cdigo. Tenga en
cuenta que la lgica para enviar el cambio es incompleta, como ver en breve. No use la lgica mostrada en
su cdigo.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
SqlConnection conexin = new SqlConnection(cadcon);
string consulta = "UPDATE [Order Details] " +
"SET OrderID = @OrderID_N, ProductID = @ProductID_N, " +
"Quantity = @Quantity_N, UnitPrice = @UnitPrice_N " +
"WHERE OrderID = @OrderID_V AND ProductID = @ProductID_V AND " +
"Quantity = @Quantity_V AND UnitPrice = @UnitPrice_V";
SqlCommand cmdActualizacion = new SqlCommand(consulta, conexin);
cmdActualizacion.Parameters.AddWithValue("@OrderID_N", filaActualizar["OrderID"]);
cmdActualizacion.Parameters.AddWithValue("@ProductID_N", filaActualizar["ProductID"]);
cmdActualizacion.Parameters.AddWithValue("@Quantity_N", filaActualizar["Quantity"]);
cmdActualizacion.Parameters.AddWithValue("@UnitPrice_N", filaActualizar["UnitPrice"]);
cmdActualizacion.Parameters.AddWithValue("@OrderID_V",
filaActualizar["OrderID", DataRowVersion.Original]);
cmdActualizacion.Parameters.AddWithValue("@ProductID_V",
filaActualizar["ProductID", DataRowVersion.Original]);
cmdActualizacion.Parameters.AddWithValue("@Quantity_V",
filaActualizar["Quantity", DataRowVersion.Original]);
cmdActualizacion.Parameters.AddWithValue("@UnitPrice_V",
filaActualizar["UnitPrice", DataRowVersion.Original]);
try{
cmdActualizacion.ExecuteNonQuery();
filaActualizar.AcceptChanges();
Console.WriteLine("Pedido modificado");
}catch(Exception ex){
Console.WriteLine("Modificacin fallida: {0}", ex.Message);
}

Comprobaciones adicionales de concurrencia


Anteriormente discutimos por qu las columnas que constituyen la clave principal de la tabla OrderID y
ProductID aparecen en la clusula WHERE de la consulta. La clusula WHERE tambin incluye lgica
para comprobar los contenidos de otras columnas Quantity y UnitPrice. La inclusin de estas columnas en
la clusula WHERE dicta lo que sucede si varios usuarios intentan modificar la misma fila.
Por un momento vamos a suponer que este pedido viene de un gran cliente. Incrementar la cantidad de
salsa en el pedido es vital para el negocio del cliente. De hecho, es un pedido tan importante que dos
personas de la compaa llaman a la vez para modificar el pedido. El pedido actualmente es por 20 cajas de
salsa; ContactoA quiere incrementarlo a 40 y ContactoB lo quiere aumentar a 50.
Los dos contactos hablan simultneamente con dos representantes diferentes. Cada uno de los
representantes accede al pedido, hace el cambio solicitado y guarda los cambios. Qu sucede cuando el
segundo representante intenta guardar los cambios que tiene en pantalla?.
Repase la consulta que aparece al principio de esta seccin. Supongamos que la peticin de ContactoA,
que cambia la cantidad de 20 a 40, se graba primero. Usando esta consulta, el cambio tendr xito, pero el
cambio solicitado por ContactoB fallar.
Para comprender por qu falla el segundo cambio examine la clusula WHERE. La consulta comprueba
el valor original de todas las columnas de la tabla. Como ContactoA y ContactoB llaman a la vez, el valor
original de Quantity en la aplicacin de ambos representantes es 20. Cuando el representante que habla
148

Escuela de Informtica del Ejrcito

con ContactoB intenta cambiar el valor, el contenido de la columna Quantity en la base de datos es 40,
como resultado del cambio solicitado por ContactoA. Por tanto, cuando se intenta grabar el cambio
solicitado por ContactoB, no existe ningn detalle de pedido que cumpla la condicin.
Si la clusula WHERE solamente hubiera contemplado la clave principal, la segunda consulta se habra
ejecutado sin problemas y habra reemplazado el cambio hecho por la primera. Esta forma de trabajo se
denomina el ltimo gana por motivos obvios. Esta lgica puede tener sentido en algunos escenarios, pero
por lo general no es la recomendada.
Otro mtodo es usar la clave principal y una columna de versin de fila que cambia en cada
modificacin; la columna de tipo timestamp de SQL Server est pensada para esto. Cada vez que se
modifica el contenido de la fila, el servidor genera un nuevo valor de fecha/hora para el campo.

Determinar el xito o fracaso del intento de actualizacin


El fragmento que mostramos para ejecutar la consulta UPDATE inclua una advertencia; ahora vamos a
explicar por qu.
En el escenario descrito anteriormente, la peticin de cambio de ContactoB no tiene xito. Como el valor
asignado al parmetro para la columna Quantity en la clusula WHERE no corresponde al contenido actual
de la columna en la fila correspondiente de la base de datos la consulta no modifica ninguna fila. Pero la
ejecucin de la consulta no provoca excepcin; la base de datos hace lo que se le ha pedido, modificar
todas las filas que corresponden al criterio especificado. Solo que no hay ninguna fila que corresponda.
Si est enviando cambios utilizando su propia lgica de actualizacin necesita tener en cuenta
escenarios como este. Afortunadamente, puede usar el valor de retorno de ExecuteNonQuery para
determinar cuntas filas se han visto afectadas por la consulta. El resultado deseado es que se vea
afectada exactamente una fila. Vamos a modificar el fragmento anterior para tener esto en cuenta:
try{
int registrosAfectados = cmdActualizacion.ExecuteNonQuery();
if(registrosAfectados ==1){
filaActualizar.AcceptChanges();
Console.WriteLine("Modificada una fila");
}else if(registrosAfectados == 0)
Console.WriteLine("Error No se ha modificado ninguna fila");
else
Console.WriteLine("Advertencia Se han modificado {0} filas",
registrosAfectados);
}catch(Exception ex){
Console.WriteLine("La consulta ha fallado: {0}", ex.Message);
}

Hay algunas circunstancias en la que el mtodo ExecuteNonQuery no devuelve el nmero de filas


modificado por la consulta. Por ejemplo, puede haber configurado el SqlCommand para llamar a un
procedimiento almacenado que realiza varias acciones, como insertar una fila en una tabla de registro para
indicar que el usuario ha llamado al procedimiento almacenado, o puede tener una lgica similar en un
disparador en la base de datos. La lgica adicional puede afectar al valor devuelto por la llamada a
ExecuteNonQuery.
Vamos a asumir que el cdigo llama a un procedimiento almacenado para enviar el cambio al pedido en
lugar de usar su propia consulta. Y vamos a asumir que el procedimiento almacenado inserta una fila en
una tabla de registro para mantener el seguimiento de quin ha llamado al procedimiento y cundo. Si la
llamada actualiza la fila deseada en la tabla ExecuteNonQuery devuelve 2 porque la ha insertado un registro
en una tabla y ha modificado un registro en otra. Si la llamada al procedimiento almacenado no actualiza la
fila, ExecuteNonQuery devuelve 1, porque ha insertado una fila en la tabla de registro.
Puede indicar a SQL Server que devuelva, o no, informacin sobre las filas modificadas usando la opcin
NOCOUNT. Si utiliza SET NOCOUNT ON por delante de la consulta secundaria, la de alta en la tabla de
registro y SET NOCOUNT OFF por detrs de la misma, el procedimiento informar solamente de los
registros afectados por la consulta de modificacin.
Si no puede usar NOCOUNT para que SQL Server informe solamente de las filas afectadas por la
consulta deseada, puede usar la funcin @@ROWCOUNT, que devuelve el nmero de filas afectado por la
ltima consulta. Este valor se puede asignar a un parmetro de salida, que se deber comprobar tras la
ejecucin de la consulta en lugar de usar el valor de retorno de ExecuteNonQuery.
Si utiliza este ltimo mtodo el SqlDataAdapter no podr determinar si un intento de actualizacin ha
tenido xito sin su ayuda. Tendr que controlar el evento RowUpdated y comprobar el valor del parmetro
de salida para determinar si la actualizacin ha tenido xito. Si determina que la actualizacin ha fallado
configure la propiedad Status del parmetro SqlRowUpdatedEventArgs como ErrorsOcurred y asigne a la
149

ADO.NET 2.0

propiedad Errors un nuevo DBConcurrencyException. Si la actualizacin tiene xito asigne Continue a la


propiedad Status y null a la propiedad Errors.

Trabajar con valores nulos


La tabla Customers de la base de datos Northwind contiene una columna Region que acepta cadenas de
hasta 15 caracteres y que permite valores nulos. En la tabla se encuentran filas que tienen un valor nulo
para esta columna. La siguiente consulta SQL permite recuperar estas filas:
SELECT COUNT(CustomerID) FROM Customers WHERE Region = NULL

Si usa esta consulta en ADO.NET o la ejecuta en el analizador de consultas de SQL, devolver cero
filas.
Los valores nulos son un caso especial en el mundo de las bases de datos, en especial cuando se trata
de comparar valores nulos en una consulta. Segn los estndares ANSI no puede comparar valores nulos
usando el operador de igualdad, en su lugar debe usar IS NULL. La siguiente consulta s que devuelve las
filas de la tablas Customers con la regin nula:
SELECT COUNT(CustomerID) FROM Customers WHERE Region IS NULL

Que tienen que ver los valores nulos con el envo de cambios a la base de datos?. Quiere que las
comprobaciones de concurrencia en la clusula WHERE tengan xito siempre que los valores actuales en la
fila de la base de datos correspondan con los valores originales en el DataRow. Si el valor de una columna
es null tanto en la fila de la base de datos como en el DataRow, la comprobacin de igualdad fallar. Este
caso se debe tratar de una forma especial y comprobar si el valor es nulo tanto en la base de datos como en
el DataRow. En otras palabras, cuando una columna acepta valores nulos debemos comprobar por
separado si ambos valores son nulos y si son iguales.
La lgica de actualizacin para la tabla Order Details no necesita comprobar nulos porque ninguna de
sus columnas acepta este valor. Para el ejemplo vamos a suponer que la columna Quantity acepta valores
nulos. Segn los estndares ANSI la siguiente comparacin devolver falso si tanto la columna como el
parmetro tienen el valor nulo:
Quantity = @Quantity_V

Por tanto este escenario se debe tratar de una forma especial. La siguiente lnea de cdigo muestra
cmo debera ser esta lgica:
(@Quantity_V IS NULL AND Quantity IS NULL) OR (Quantity = @Quantity_V)

9.1.3

Borrar una fila existente

El borrado de una fila existente por medio de la sentencia DELETE solamente necesita el nombre de la
tabla y una clusula WHERE similar a la de la sentencia UPDATE. Podemos usar la siguiente sentencia
para borrar una fila existente en la base de datos:
DELETE [Order Details]
WHERE OrderID = @OrderID AND ProductID = @ProductID
AND Quantity = @Quantity AND UnitPrice = @UnitPrice

Podemos usar esta lgica de actualizacin para enviar borrados pendientes con el siguiente cdigo:
string consulta = "DELETE [Order Details] WHERE OrderID = @OrderID AND " +
"ProductID = @ProductID AND Quantity = @Quantity AND UnitPrice = @UnitPrice";
SqlCommand cmdBorrar = new SqlCommand(consulta, conexin);
cmdBorrar.Parameters.AddWithValue("@OrderID",
filaBorrar["OrderID", DataRowVersion.Original]);
cmdBorrar.Parameters.AddWithValue("@ProductID",
filaBorrar["ProductID", DataRowVersion.Original]);
cmdBorrar.Parameters.AddWithValue("@Quantity",
filaBorrar["Quantity", DataRowVersion.Original]);
cmdBorrar.Parameters.AddWithValue("@UnitPrice",
filaBorrar["UnitPrice", DataRowVersion.Original]);
try{
int registrosAfectados = cmdBorrar.ExecuteNonQuery();
if(registrosAfectados == 1){
filaBorrar.AcceptChanges();
Console.WriteLine("xito Borrada una fila");
}else if(registrosAfectados == 0)
Console.Writeline(Error No se ha borrado ninguna fila);
else
Console("Advertencia Se han borrado {0} filas", registrosAfectados);

150

Escuela de Informtica del Ejrcito

}catch(Exception ex){
Console.WriteLine("La consulta ha fallado: {0}", ex.Message);
}

9.1.4

Qu ms puede necesitar

Ya tiene toda la funcionalidad que necesita para enviar los cambios pendientes a la base de datos. Qu
ms puede necesitar?.
En realidad, enviar los cambios requiere un poco ms de cdigo. An necesita:
Crear un mtodo que acepte varios DataRow.
Buscar DataRow con cambios pendientes.
Acceder a un SqlCommand que contenga la lgica de actualizacin que se corresponda con la
propiedad RowState de DataRow.
Asignar valores de las columnas del DataRow a la propiedad Value de los parmetros.
Ejecutar el SqlCommand.
Comprobar el nmero de registros afectados e indicar escenarios de fallo, posiblemente lanzando una
excepcin.
Llamar al mtodo AcceptChanges de DataRow si el envo de los cambios que contiene tiene xito.
En realidad, puede olvidar lo que he dicho antes sobre la necesidad de ms cdigo. Afortunadamente, el
SqlDataAdapter puede ayudar a simplificar el proceso de enviar los cambios. Veamos como.

9.2 Actualizaciones con DataAdapter


El mtodo Update de la clase SqlDataAdapter cubre todos los elementos de la lista anterior.

Crear un mtodo que acepte varios DataRow


El mtodo Update est sobrecargado. Puede pasarle un DataTable o un array de DataRow. Tambin
puede pasar un DataSet; el DataAdapter enviar los cambios pendientes almacenados solo en un
DataTable.

Buscar DataRow con cambios pendientes


El mtodo Update recorre los DataRow enviados, comprobando la propiedad RowState de cada uno.

Acceder a un SqlCommand que contenga la lgica de actualizacin que se


corresponda con la propiedad RowState de DataRow
El SqlDataAdapter exponte propiedades InsertCommand, UpdateCommand y DeleteCommand para
almacenar esta lgica de actualizacin. El mtodo Update comprueba la propiedad RowState de cada
DataRow para determinar qu SqlCommand ejecutar.

Asignar valores de las columnas del DataRow a la propiedad Value de los


parmetros
El DataAdapter se basa en las propiedades configuradas del SqlParameter para determinar cmo
asignar un valor al SqlParameter en base al contenido de la columna correspondiente en el DataRow.
Parameter.SourceColumn. El DataAdapter usa esta propiedad para asociar el SqlParameter con el
DataColumn del mismo nombre en el DataRow.
Parameter.SourceVersion. El DataAdapter usa esta propiedad para determinar si asigna el valor
original o el actual a la propiedad Value del parmetro. La opcin predeterminada es Current.
Parameter.SourceColumnNullMapping. ADO.NET aade a las clases Parameter esta propiedad,
diseada como atajo para ayudar a los desarrolladores a manejar las comprobaciones de
concurrencia con valores nulos.
El proveedor de datos .NET SqlClient soporta parmetros con nombre, lo que permite crear un nico
parmetro para usarlo varias veces en una consulta, como en una comprobacin de concurrencia en
una columna que admite nulos:
(@Columna IS NULL AND Columna IS NULL) OR Columna = @Columna

La base de datos hace corresponder los valores de parmetro proporcionados con los marcadores de
parmetro en la consulta segn sus nombre y no segn sus posiciones.
No todos los proveedores soportan parmetros con nombre. Por ejemplo, los proveedores de datos
.NET para ODBC y OLE DB utilizan parmetros posicionales. Al utilizar parmetros posicionales los
valores se hacen corresponder con los marcadores por su posicin. En este tipo de proveedores se
supone que cada marcador es un parmetro diferente, por lo que no hay forma de indicar que se
151

ADO.NET 2.0

debe reutilizar un parmetro diferente. Como resultado, la comprobacin de concurrencia anterior


necesita dos parmetros separados que reciben el mismo valor desde el DataRow:
(? IS NULL and Columna IS NULL) OR Columna = ?

Aunque esta comparacin tiene sentido y es vlida segn los estndares ANSI el paso del mismo
parmetro varias veces a la base de datos puede no ser la mejor forma de usar los recursos de la
red. Puede determinar si el valor del DataRow es null antes de ejecutar la consulta. Puede pasar a la
base de datos un Boolean o un entero para indicar si el valor es nulo en el DataRow y modificar la
lgica:
(? = 1 AND Columna IS NULL) OR Columna = ?

Si asigna true a la propiedad SourceColumnNullMapping de un Parameter como SqlParameter


obtendr exactamente este comportamiento. El SqlParameter no enviar el valor especificado a la
base de datos cuando se ejecuta la consulta; enviar el valor 1 si el valor indicado por las
propiedades SourceColumn y SourceVersion es igual a DBNull.Value y cero en caso contrario.

Ejecutar el SqlCommand
Despus de que el SqlDataAdapter asigne valores a los SqlParameter en base a las propiedades
SourceColumn, SourceVersion y SourceColumnNullMapping ejecuta el SqlCommand para enviar los
cambios pendientes almacenados en un DataRow.

Comprobar el nmero de registros afectados e indicar escenarios de fallo,


posiblemente lanzando una excepcin.
El SqlDataAdapter determina el nmero de registros afectados al ejecutar la consulta y lanza una
DBConcurrencyException si no se modifica ninguna. Si la base de datos no informa de cuntos registros
han sido afectados por la consulta se asume xito.

Llamar al mtodo AcceptChanges de DataRow si el envo de los cambios que


contiene tiene xito.
Si el SqlDataAdapter determina que el intento de actualizacin de la fila en la base de datos ha tenido
xito llama al mtodo AcceptChanges del DataRow de modo que ya no contenga cambios pendientes.

Funcionalidad aadida
Pero adems de todo esto, SqlDataAdapter ofrece otras funcionalidades.
Eventos antes y despus de actualizar. El SqlDataAdapter lanza los eventos RowUpdating y
RowUpdated que puede usar para aadir lgica antes y despus de los intentos de actualizacin.
Asignar valores recuperados a DataRow. Despus de enviar un cambio a la base de datos puede
ser necesario recuperar valores generados por la base de datos, como predeterminados, marcas de
tiempo y auto incremento. Puede configurar su lgica de actualizacin para devolver esta informacin
usando parmetros de salida o devolviendo una fila de datos, y el SqlDataAdapter asignar estos
valores a las columnas correspondientes del DataRow. Este aspecto se tratar ms a fondo en el
siguiente captulo.
Actualizaciones por lotes. De forma predeterminada, el SqlDataAdapter realiza una llamada de ida
y vuelta a la base de datos para cada cambio pendiente. En ADO.NET 2.0 puede enviar las
actualizaciones por lotes, de modo que una sola llamada a la base de datos puede enviar los cambios
de varias filas. Puede activar esta forma de actualizacin asignando a la propiedad UpdateBatchSize
un valor diferente a 1, el predeterminado. Esta caracterstica se tratar en el siguiente captulo.
Cuando se trata de generar la lgica de actualizacin que usarn los objetos SqlDataAdapter para enviar
los cambios a la base de datos tiene tres opciones:
Configurar manualmente los objetos SqlDataAdapter mediante cdigo
Usar un SqlCommandBuilder en tiempo de ejecucin.
Usar los asistentes de Visual Studio en tiempo de diseo.
Cada uno de estos mtodos tiene sus ventajas e inconvenientes, que se describen en las siguientes
secciones.

9.3 Configuracin objetos DataAdapter manualmente


La clase SqlDataAdapter expone cuatro propiedades que contienen objetos Command. Ya hemos visto
que la propiedad SelectCommand contiene el Command que se utiliza para llenar el DataTable. Las otras
tres propiedades UpdateCommand, InsertCommand y DeleteCommand contienen los objetos Command
que se usan para enviar los cambios pendientes.
152

Escuela de Informtica del Ejrcito

El mtodo Update de SqlDataAdapter es muy flexible. Puede recibir un DataSet, un DataSet y un nombre
de tabla, un DataTable o un array de objetos DataRow. Independientemente de la forma que use para
llamar a este mtodo el SqlDataAdapter intentar enviar los cambios pendientes por medio del
SqlCommand correspondiente.
Podemos usar los comandos creados anteriormente en este captulo para enviar los cambios pendientes
asignndolos a las propiedades correspondientes. Sin embargo, no queremos asignar manualmente valores
de columnas del DataRow a los parmetros antes de enviar los cambios.

9.3.1

Parmetros vinculados

SqlDataAdapter usa las propiedades SourceColumn, SourceVersion y SourceColumnNullMapping de los


SqlParameter junto con el DataRow para determinar qu valores enviar a la base de datos. Una vez
configuradas estas propiedades tenemos toda la informacin necesaria para enviar los cambios pendientes.
SqlParameterCollection pc;
SqlParameter p;
//Comando de insercin
String consulta = "INSERT INTO [Order Details] " +
"(OrderID, ProductID, Quantity, UnitPrice) VALUES " +
"(@OrderID, @ProductID, @Quantity, @UnitPrice)";
adaptador.InsertCommand = new SqlCommand(consulta, conexin);
pc = adaptador.InsertCommand.Parameters;
pc.Add("@OrderID", SqlDbType.Int, 0, "OrderID");
pc.Add("@ProductID", SqlDbType.Int, 0, "ProductID");
pc.Add("@Quantity", SqlDbType.SmallInt, 0, "Q");
pc.Add("@UnitPrice", SqlDbType.Money, 0, "UnitPrice");
//Comando de actualizacin
consulta = UPDATE [Order Details] SET OrderID=OrderID_N, ProductID=ProductID_N, +
Quantity=@Quantity_N, UnitPrice=@UnitPrice_N WHERE OrderID=@OrderID_V AND +
ProductID=@ProductID_V AND Quantity=@Quantity_V AND UnitPrice=@UnitPrice_V;
adaptador.UpdateCommand =new SqlCommand(consulta, conexin);
pc = adaptador.UpdateCommand.Parameters;
pc.Add(@OrderID_N, SqlDbType.Int, 0, OrderID);
pc.Add(@ProductID_N, SqlDbType.Int, 0, ProductID);
pc.Add(@Quantity_N, SqlDbType.SmallInt, 0, Quantity);
pc.Add(@UnitPrice_N, SqlDbType.Money, 0, UnitPrice);
pc.Add(@OrderID_V, SqlDbType.Int, 0, OrderID);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@ProductID_V, SqlDbType.Int, 0, ProductID);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@Quantity_V, SqlDbType.SmallInt, 0, Quantity);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@UnitPrice_V, SqlDbType.Money, 0, UnitPrice);
p.SourceVersion = DataRowVersion.Original;
//Comando de borrado
consulta = DELETE [Order Details] WHERE OrderID = @OrderID AND
ProductID = @ProductID AND Quantity = @Quantity AND UnitPrice = @UnitPrice;
adaptador.DeleteCommand = new SqlCommand(consulta, conexin);
pc = adaptador.DeleteCommand.Parameters;
pc.Add(@OrderID, SqlDbType.Int, 0, OrderID);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@ProductID, SqlDbType.Int, 0, ProductID);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@Quantity, SqlDbType.SmallInt, 0, Quantity);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@UnitPrice, SqlDbType.Money, 0, UnitPrice);
p.SourceVersion = DataRowVersion.Original;

Una vez configurada esta lgica de actualizacin solo es necesario llamar al mtodo Update del
SqlDataAdapter y pasarle el DataTable como parmetro.

9.3.2

Uso de procedimientos almacenados

El fragmento de cdigo anterior muestra cmo puede crear sus propios objetos SqlCommand que el
SqlDataAdapter utilizar para enviar los cambios pendientes. Podemos usar un cdigo similar para utilizar
procedimientos almacenados.
Primero debemos definir en la base de datos procedimientos que modifiquen, inserten y borren filas en la
tabla Order Details. Estos procedimientos contendrn las mismas sentencias que en el ejemplo anterior se
insertaron a mano en el cdigo.
153

ADO.NET 2.0

Una vez que tenemos los procedimientos podemos escribir objetos SqlCommand que llamen a estos
procedimientos cuando llamemos al mtodo Update del SqlDataAdapter. Supongamos que los
procedimientos almacenados se llaman InsertaDetalle, BorraDetalle y CambiaDetalle.
SqlParameterCollection pc;
SqlParameter p;
//Comando de insercin
adaptador.InsertCommand = new SqlCommand(InsertaDetalle, conexin);
adaptador.InsertCommand.CommandType = CommandType.StoredProcedure;
pc = adaptador.InsertCommand.Parameters;
pc.Add("@OrderID", SqlDbType.Int, 0, "OrderID");
pc.Add("@ProductID", SqlDbType.Int, 0, "ProductID");
pc.Add("@Quantity", SqlDbType.SmallInt, 0, "Q");
pc.Add("@UnitPrice", SqlDbType.Money, 0, "UnitPrice");
//Comando de actualizacin
adaptador.UpdateCommand =new SqlCommand(CambiaDetalle, conexin);
adaptador.UpdateCommand.CommandType = CommandType.StoredProcedure;
pc = adaptador.UpdateCommand.Parameters;
pc.Add(@OrderID_N, SqlDbType.Int, 0, OrderID);
pc.Add(@ProductID_N, SqlDbType.Int, 0, ProductID);
pc.Add(@Quantity_N, SqlDbType.SmallInt, 0, Quantity);
pc.Add(@UnitPrice_N, SqlDbType.Money, 0, UnitPrice);
pc.Add(@OrderID_V, SqlDbType.Int, 0, OrderID);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@ProductID_V, SqlDbType.Int, 0, ProductID);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@Quantity_V, SqlDbType.SmallInt, 0, Quantity);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@UnitPrice_V, SqlDbType.Money, 0, UnitPrice);
p.SourceVersion = DataRowVersion.Original;
//Comando de borrado
adaptador.DeleteCommand = new SqlCommand(BorraDetalle, conexin);
adaptador.DeleteCommand.CommandType = CommandType.StoredProcedure;
pc = adaptador.DeleteCommand.Parameters;
pc.Add(@OrderID, SqlDbType.Int, 0, OrderID);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@ProductID, SqlDbType.Int, 0, ProductID);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@Quantity, SqlDbType.SmallInt, 0, Quantity);
p.SourceVersion = DataRowVersion.Original;
pc.Add(@UnitPrice, SqlDbType.Money, 0, UnitPrice);
p.SourceVersion = DataRowVersion.Original;

9.3.3

Proporcionar su propia lgica

Ahora vamos a ver las ventas e inconvenientes de proporcionar su propia lgica en el cdigo.

Ventajas
Las dos grandes ventajas son control y rendimiento. El SqlDataAdapter de ADO.NET ofrece el mximo
control sobre la lgica de actualizacin de las tablas de la base de datos. No est restringido a enviar los
cambios directamente a la tabla, puede aprovechar procedimientos almacenados existentes en la base de
datos.
Es ms, como no est confiando en la tecnologa de acceso a datos para determinar el origen de los
datos puede tratar cualquier conjunto de resultados como actualizable. Con ADO.NET puede cargar el
DataSet con los resultados de un procedimiento almacenado, una consulta contra una tabla temporal o la
unin de varias consultas y an podr enviar los cambios a la base de datos.
Proporcionar la lgica de actualizacin en el cdigo mejora el rendimiento de la aplicacin, puesto que no
es necesario ninguna consulta previa para obtener los metadatos.

Inconvenientes
El inconveniente principal de este mtodo es que necesita ms cdigo que la lgica generada
automticamente. La escritura de este cdigo es costosa en tiempo y bastante aburrida.
Otra desventaja ese que muchos desarrolladores no se sienten cmodos escribiendo su propia lgica de
actualizacin. Surgen preguntas como si se debe delimitar el nombre de la tabla en la consulta, qu tipos de
marcadores de parmetro se deben usar, qu columnas deben aparecer en la clusula WHERE, etc.

154

Escuela de Informtica del Ejrcito

9.4 Generar lgica de actualizacin con SqlCommandBuilder


El modelo de objetos de ADO.NET no solo le permite definir su propia lgica de actualizacin, sino que
tambin proporciona generacin dinmica de esta lgica usando la clase SqlCommandBuilder. Si instancia
un objeto SqlCommandBuilder y lo asocia con un objeto SqlDataAdapter el SqlCommandBuilder intentar
generar lgica de actualizacin en base a la consulta que contiene el objeto SelectCommand del adaptador.
Para demostrar cmo funciona SqlCommandBuilder lo vamos a usar para generar la lgica de
actualizacin para nuestro ejemplo que consulta la tabla Order Details. El siguiente fragmento de cdigo
instancia un SqlCommandBuilder proporcionando un SqlDataAdapter en el constructor. A continuacin
escribe el texto del Command que el SqlCommandBuilder ha generado para enviar las nuevas filas.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT OrderID, ProductID, Quantity, UnitPrice " +
"FROM [Order Details] WHERE OrderID = @OrderID ORDER BY ProductID";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.SelectCommand.Parameters.AddWithValue("@OrderID", 10503);
SqlCommandBuilder cb = new SqlCommandBuilder(adaptador);
Console.WriteLine(cb.GetInsertCommand().CommandText);

Observar que el texto de esta consulta es muy similar al que se ha utilizado al crear la lgica
manualmente.

9.4.1

Cmo genera CommandBuilder la lgica de actualizacin

La lgica que utiliza CommandBuilder para generar las consultas UPDATE, INSERT y DELETE no es
muy compleja. El SqlCommandBuilder consulta la base de datos para determinar los nombres de la tabla y
las columnas as como informacin de clave para los resultados de la consulta. El SqlCommandBuilder
puede generar lgica de actualizacin si se cumplen tres condiciones:
La consulta devuelve datos de una sola tabla.
La tabla tiene una clave principal.
La clave principal est incluida en los resultados de la consulta.
Como vimos anteriormente la clave principal asegura que las actualizaciones basadas en consulta que
genere el SqlCommandBuilder pueden actualizar una fila como mximo. Por qu restringe el
SqlCommandBuilder el nmero de tablas al que hace referencia el resultado de la consulta?. Lo veremos
ms adelante.
El objeto SqlCommandBuilder usa la propiedad SelectCommand del objeto SqlDataAdapter para obtener
los metadatos necesarios para la lgica de actualizacin, utilizando el mtodo ExecuteReader al que pasa
como parmetro la combinacin de los valores SchemaOnly y KeyInfo de la enumeracin
CommandBehavior.

9.4.2

Opciones de concurrencia

El SqlCommandBuilder de forma predeterminada usar todas las columnas comparables en la clusula


WHERE de los comandos de actualizacin y borrado. Esto asegura que una llamada para enviar un cambio
pendiente lanzar un DBConcurrencyException si otro usuario ha modificado cualquiera de estas columnas
entre el momento de recuperar los valores originales y el envo de los nuevos.
En ADO.NET 2.0 la clase SqlCommandBuilder expone una propiedad ConflictOptions que puede usar
para controlar las comprobaciones de concurrencia que genera. El valor predeterminado de esta propiedad
es CompareAllSearchableValues. Si la cambia a OverwriteChanges la lgica generada seguir el mtodo el
ltimo gana, incluyendo solo la o las columnas clave en la clusula WHERE. Otro posible valor es
CompareRowVersion, que har que la lgica de actualizacin utilice la marca temporal y la clave principal

9.4.3

Ventajas e inconvenientes

Puede ver las dos ventajas principales de usar la clase SqlCommandBuilder si comprara fragmentos de
cdigo que lo usen con los equivalentes que crean la lgica manualmente. El uso de la clase
SqlCommandBuilder precisa menos cdigo. Tambin permite generar lgica de actualizacin sin necesidad
de conocer en profundidad la sintaxis SQL.
SqlCommandBuilder tambin puede ser til si encuentra problemas cuando genere su propia lgica. Si
SqlCommandBuilder puede generar esta lgica con xito puede comprobar el valor de la propiedad
CommandText de los objetos SqlCommand o las propiedades de los objetos SqlParameter que genera.
SqlCommandBuilder tambin es muy til para cualquier aplicacin en la que sea necesaria actualizacin
pero no se conozca la estructura de las consultas en tiempo de diseo.

155

ADO.NET 2.0

Como SqlCommandBuilder genera la lgica de actualizacin en tiempo de ejecucin consultando


metadatos adicionales su rendimiento no es el mejor de los posibles. Esta clase tampoco permite el nivel de
control sobre la lgica de actualizacin que la escritura manual. Por ejemplo, no permite el uso de
procedimientos almacenados.

9.5 Generar la lgica de actualizacin en tiempo de diseo


El asistente que ya hemos utilizado para crear las clases TableAdapter para los DataTable de un
DataSet con tipo tambin genera la lgica de actualizacin y la almacena en su cdigo. Vamos a examinar
la lgica de actualizacin de un TableAdapter creando un nuevo DataSet con tipo.
Abra un proyecto en Visual Studio y aada un nuevo DataSet. Los proyectos Web lanzan
automticamente el asistente de configuracin de TableAdapter. Si est trabajando en un proyecto Windows
aada un nuevo TableAdapter al DataSet para lanzar el asistente. Especifique una conexin con la base de
datos Northwind y escriba la siguiente consulta:
SELECT OrderID, ProductID, UnitPrice, Quantity FROM [Order Details]
WHERE OrderID = @OrderID ORDER BY ProductID

Pulse Siguiente. La ltima ventana del asistente le confirmar que se han generado las sentencias de
actualizacin.

9.5.1

Examinar la lgica de actualizacin

Como indica en su ltima pgina, el asistente genera consultas UPDATE, INSERT y DELETE para el
nuevo TableAdapter. Pulse el botn Finalizar. Seleccione el nuevo TableAdapter en el diseador de
DataSet, vaya a la ventana de propiedades y expanda el DeleteCommand del adaptador. Seleccione la
propiedad CommandText y pulse el botn de elipsis a la derecha del valor. Se abrir el generador de
consultas con el texto de la consulta actual.
Como puede ver, el texto que genera el asistente es idntico al que se utiliz al crear la lgica de forma
manual. Puede examinar tambin las propiedades InsertCommand y UpdateCommand para ver el resto de
la lgica de actualizacin generada por el asistente.

9.5.2

Opciones para construir la lgica de actualizacin

La pantalla de sentencia SQL del asistente tiene un botn Opciones avanzadas que puede pulsar para
mostrar un dilogo que ofrece una serie de opciones. Estas opciones proporcionan un ligero control sobre la
lgica de actualizacin que genera el asistente.
Si va a usar el TableAdapter solamente para leer datos de la base de datos puede quitar la marca de la
opcin Generar instrucciones Insert, Update y Delete. Esto impedir que el asistente genere la lgica y la
almacene como cdigo en el archivo de cdigo del DataSet.
El asistente tambin proporciona opciones relativas a la generacin de la lgica de actualizacin. La
opcin predeterminada es la opcin de concurrencia ms defensiva de las disponibles. Si deja marcada la
opcin Usar concurrencia optimista el asistente intentar generar comprobaciones de concurrencia basadas
en columnas de clave principal y de marca temporal. Si esto falla, intentar generar comprobaciones
basadas en la clave principal y todas las columnas que no sean BLOB, ya que SQL Server no puede
comparar tipos de datos como imgenes, text y ntext. Si quita la marca el asistente utilizar solo la clave
principal en las clusulas WHERE de las consultas.
Algunas bases de datos, como SQL Server, permiten consultas por lotes que puede devolver filas de
datos. Si est usando el asistente para crear un TableAdapter que se comunique con una de estas bases de
datos la opcin Actualizar la base de datos estar activada y marcada. Cuando esta opcin est marcada el
asistente genera consultas que vuelven a leer los contenidos de la fila modificada inmediatamente despus
de enviar el cambio. Esto significa que los valores generados por el servidor, como marcas de tiempo y
autoincremento estarn disponibles en el DataRow inmediatamente despus de la llamada a Update.
Esta funcionalidad es muy til, y no est contemplada por la clase SqlCommandBuilder. Trataremos este
escenario en mayor profundidad en el prximo captulo.

9.5.3

Uso de procedimientos almacenados

La opcin Usar procedimientos almacenados existentes de la ventana Elija un tipo de comando del
asistente le permite utilizar procedimientos almacenados de la base de datos.
La siguiente ventana le permite seleccionar los procedimientos almacenados para cada uno de los
objetos Command del TableAdapter. El primer paso es seleccionar el procedimiento almacenado para
SelectCommand. La lista desplegable contendr todos los procedimientos almacenado. Cuando seleccione
un procedimiento la lista de la derecha mostrar las columnas que devuelve.

156

Escuela de Informtica del Ejrcito

A continuacin puede especificar los procedimientos para los objetos Command de actualizacin. Para
configurar la propiedad SourceColumn de los parmetros de estos procedimientos use las listas
desplegables del lado derecho de la ventana del asistente.
El asistente no proporciona un medio para configurar la propiedad SourceVersion de los parmetros.
Como el valor predeterminado de esta propiedad es Current debe cambiar el valor de esta propiedad
usando la ventana de propiedades si debe utilizar el valor original de una columna modificada.
Si dispone de la edicin Professional de Visual Studio 2005 tambin puede proporcionar una consulta
SQL y el asistente crear los nuevos procedimientos almacenados para las propiedades SelectCommand,
UpdateCommand, InsertCommand y DeleteCommand del TableAdapter. Seleccione Crear nuevos
procedimientos almacenados y el asistente le pedir la consulta SQL utilizada para el procedimiento de
seleccin.
La siguiente pantalla le pide nombres para los procedimientos almacenados que se van a crear. Esta
ventana incluye un botn de vista previa que muestra la secuencia de comandos SQL generada por el
asistente. Si est trabajando con una base de datos de prueba puede guardar la secuencia de comandos
SQL en un archivo que podr ejecutar posteriormente en la base de datos de explotacin.
Una vez completado el asistente tendr nuevos procedimientos almacenados en la base de datos y el
nuevo TableAdapter estar configurado para utilizarlos.

9.5.4

Ventajas e inconvenientes

El objetivo del asistente de configuracin de TableAdapter es generar lgica de actualizacin que permite
crear cdigo eficiente de una forma fcil y rpida. El asistente ofrece opciones de las que no dispone la
clase SqlCommandBuilder. Tambin evita el tedioso cdigo que muchos desarrolladores prefieren no
escribir.
El asistente pide a la base de datos la misma informacin de esquema que la clase SqlCommandBuilder.
De hecho, el asistente utiliza la clase CommandBuilder correspondiente al proveedor de bases de datos que
se est usando. Sin embargo, esta informacin solo se pide en tiempo de diseo y almacena la lgica
generada en el cdigo de modo que no es necesario consultar la base de datos en tiempo de ejecucin. De
esta forma se evita la penalizacin en rendimiento que impone la clase SqlCommandBuilder.
El asistente de configuracin de TableAdapter hace un gran trabajo al generar la lgica, pero no es
perfecto. La lgica que usa el asistente para recuperar valores no funciona con actualizaciones por lotes.
Hay una forma ms eficiente de actualizar el DataRow despus de realizar un INSERT o UPDATE usando
SQL Server y otras bases de datos. El asistente adems crea un TableAdapter; si quiere usar un
DataAdapter no es su opcin.

9.6 El regreso de los DataAdapter


Considere el siguiente escenario bsico. Quiere crear una capa de acceso a datos sencilla un
componente que le permita conectar con la base de datos, devolver informacin sobre un pedido concreto y
enviar cambios a la base de datos. Quiere una forma sencilla para crear este componente una que le
permita escribir el mnimo de cdigo pero no quiere utilizar TableAdapter por razones que ya hemos
discutido.
Puede crear el DataSet con tipo y eliminar los TableAdapter. Hasta aqu no hay problema.
Ya sabe cmo crear SqlConnection ySqlDataAdapter en su cdigo. Tambin sabe que puede crear
SqlCommandBuilder para generar la lgica de actualizacin, pero este mtodo obliga a consultar, de forma
innecesaria, la informacin de esquema de la base de datos cada vez que hay que generar la lgica en
tiempo de ejecucin.
Afortunadamente el asistente de configuracin de TableAdapter no es la nica forma de generar lgica
de actualizacin en tiempo de diseo usando Visual Studio, solo es el ms obvio. Si quiere usar
DataAdapter puede usar su primo, el asistente de configuracin de adaptador de datos, de Visual Studio
2003.

9.6.1

Llamar al asistente de configuracin de adaptador de datos

La primera vez que se llama al asistente ms antiguo cuesta un poco, pero vale la pena.
Las clases DataAdapter no estn disponibles de forma predeterminada en la caja de herramientas, pero
se pueden aadir pulsado con el botn derecho y seleccionando Elegir elemento, o la opcin Elegir
elementos del cuadro de herramientas del men Herramientas. En el dilogo que se abre seleccione los
elementos que quiere que aparezcan en la caja de herramientas. Si est trabajando con SQL Server un
buen comienzo es seleccionar SqlConnection, SqlCommand y SqlDataAdapter. Una vez aadidos los
elementos deseados aparecern en la lengeta que tuviera seleccionada al realizar esta accin.
157

ADO.NET 2.0

Ahora podr arrastrar estos componentes a la mayora de los diseadores de elementos de proyecto,
incluyendo formularios Windows, componentes y servicios Web. Los formularios Web no soportan estas
operaciones de arrastrar y soltar.

9.6.2

Capa de acceso a datos con SqlDataAdapter

Imagine que quiere instanciar su componente de acceso a datos pasando una cadena de conexin en el
constructor. A continuacin quiere llamar a un mtodo que acepta un identificador de pedido y devuelve
informacin sobre este pedido y sus detalles. Posteriormente querr enviar cambios a la base de datos.
El elemento de proyecto Componente es perfecto para este trabajo. Expone un diseador similar al
formulario Windows, pero no ofrece un interface de usuario en tiempo de ejecucin. Puede arrastrar y soltar
Connection, Command y DataAdapter al diseador del componente.
Cuando arrastra y suelta un DataAdapter se lanza el asistente de configuracin de adaptador de datos.
Este asistente es muy similar al asistente de configuracin de TableAdapter. Igual que la versin ms
moderna, permite especificar informacin de conexin y opciones para la generacin de lgica de
actualizacin. La diferencia principal es que este asistente no crear un TableAdapter que envuelva al
DataAdapter.

Aadir DataAdapter y conexin


Aada a su proyecto un nuevo componente llamado cAccesoDatos. Arrastre un SqlDataAdapter a la
superficie de diseo del componente. En el asistente de configuracin del adaptador de datos especifique
una conexin con la base de datos Northwind. Especifique una consulta para recuperar solamente
informacin de cliente:
SELECT OrderID, CustomerID, OrderDate FROM Orders
WHERE OrderID = @OrderID

Cuando complete el asistente ver un SqlConnection y un SqlDataAdapter en la superficie de diseo del


componente. Seleccione el nuevo objeto SqlConnection y llmelo cnNorthwind. Cambie el nombre del
adaptador a adpPedidos.
Arrastre otro SqlDataAdapter a la superficie de diseo. En el asistente use la misma informacin de
conexin y utilice la siguiente consulta:
SELECT OrderID, ProductID, Quantity, UnitPrice FROM [Order Details]
WHERE OrderID = @OrderID;

Cambie el nombre del nuevo adaptador a adpDetalles.

Crear un DataSet con tipo


Puede crear un DataSet con tipo basado en estos SqlDataAdapter. Seleccione la opcin Generar
conjunto de datos del men Datos; si esta opcin no est disponible seleccione solo los DataAdapter y
vuelva a desplegar el men Datos. En el dilogo Generar conjunto de datos seleccione la opcin Nuevo y
cambie el nombre a dsNorthwind.
Abra el nuevo DataSet en el diseador y ver que contiene los DataTable de pedidos y detalles de
pedido. No ver un DataRelation entre ellos; estos solo se generan cuando se aaden TableAdapter. Pulse
con el botn derecho sobre la superficie de diseo, seale Agregar y pulse Relacin. Seleccione Orders
como tabla primaria y Order Details como tabla secundaria, y la columna OrderID como clave en ambos
lados. Especifique que quiere crear tanto relacin como Foreign Key y compruebe que las reglas de
actualizacin, eliminacin y aceptacin o rechazo son respectivamente Cascade, Cascade y None.
No olvide cambiar las propiedades AutoIncrementSeed y AutoIncrementStep de la columna OrderID en
el DataTable Orders a -1 para asegurarse de que los valores generados sern negativos.

Aadir cdigo al componente


Pulse con el botn derecho sobre el diseador del componente y seleccione Ver cdigo para ver el
archivo de clase parcial. Puede ocultar los constructores pblicos que proporciona el diseado cambiando
sus modificadores de public a private.
public cAccesoDatos(string cadenaConexion) {
//Inicializa los elementos de la clase componente
InitializeComponent();
//Asigna la cadena de conexin especificada a la conexin
cnNorthwind.ConnectionString = cadenaConexion;
}
public dsNorthwind LeePedido(int idPedido) {
dsNorthwind datos = new dsNorthwind();

158

Escuela de Informtica del Ejrcito

//Asigna el ID de pedido como parmetro de los dos adaptadores


adpPedidos.SelectCommand.Parameters[0].Value = idPedido;
adpDetalles.SelectCommand.Parameters[0].Value = idPedido;
//Conecta, recupera los resultados y desconecta
cnNorthwind.Open();
adpPedidos.Fill(datos.Orders);
adpDetalles.Fill(datos.Order_Details);
cnNorthwind.Close();
//Devuelve el nuevo DataSet
return datos;
}
public int EnviaCambios(dsNorthwind datos) {
int filasModificadas;
//Conecta, enva los cambios y desconecta
cnNorthwind.Open();
filasModificadas = adpPedidos.Update(datos.Orders);
filasModificadas += adpDetalles.Update(datos.Order_Details);
cnNorthwind.Close();
//Devuelve el nmero total de cambios
return filasModificadas;
}

Esta es una forma muy simplista de enviar cambios a la base de datos, y no funcionar en caso de
borrado. En el prximo captulo se tratarn las actualizaciones jerrquicas.
Ahora podemos usar nuestro componente de acceso a datos para recuperar y mostrar los contenidos de
un pedido.
static void Main(string[] args) {
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
//Recupera y muestra el pedido
cAccesoDatos capaDatos = new cAccesoDatos(cadcon);
dsNorthwind datos = capaDatos.LeePedido(10503);
dsNorthwind.Order_DetailsDataTable tablaDetalles = datos.Order_Details;
dsNorthwind.OrdersRow filaPedido = datos.Orders[0];
Console.WriteLine("Detalles originales para {0} con fecha {1:d}",
filaPedido.OrderID, filaPedido.OrderDate);
foreach(dsNorthwind.Order_DetailsRow fila in filaPedido.GetOrder_DetailsRows())
Console.WriteLine("\tIDProducto: {0} Cantidad:{1} Precio unitario: {2:c}",
fila.ProductID, fila.Quantity, fila.UnitPrice);
Console.WriteLine();
//Modifica el contenido del pedido
dsNorthwind.Order_DetailsRow filaDetalle;
//Borra la fila de Tofu
filaDetalle = tablaDetalles.FindByOrderIDProductID(10503, 14);
filaDetalle.Delete();
//Dobla la cantidad de salsa picante
filaDetalle = tablaDetalles.FindByOrderIDProductID(10503, 65);
filaDetalle.Quantity *= 2;
//Aade una fila nueva para te
tablaDetalles.AddOrder_DetailsRow(filaPedido, 1, 24, 18);
//Enva los cambios pendientes
capaDatos.EnviaCambios(datos);
//Vuelve a recuperar y mostrar el pedido
datos = capaDatos.LeePedido(10503);
filaPedido = datos.Orders[0];
Console.WriteLine("Detalles Nuevos para {0} con fecha {1:d}",
filaPedido.OrderID, filaPedido.OrderDate);
foreach(dsNorthwind.Order_DetailsRow fila in filaPedido.GetOrder_DetailsRows())
Console.WriteLine("\tIDProducto: {0} Cantidad:{1} Precio unitario: {2:c}",
fila.ProductID, fila.Quantity, fila.UnitPrice);
Console.WriteLine();

159

ADO.NET 2.0

//Restablece el pedido
RestablecePedido(cadcon);
}
static void RestablecePedido(string cadenaConexion) {
SqlConnection conexin = new SqlConnection(cadenaConexion);
conexin.Open();
//Borra el contenido actual del pedido
string consulta = "DELETE [Order Details] WHERE OrderID = @OrderID";
SqlCommand comando = new SqlCommand(consulta, conexin);
comando.Parameters.AddWithValue("@OrderID", 10503);
comando.ExecuteNonQuery();
//Vuelve a aadir tofu
consulta = "INSERT INTO [Order Details] " +
"(OrderID, ProductID, Quantity, UnitPrice) VALUES " +
"(@OrderID, @ProductID, @Quantity, @UnitPrice)";
comando.CommandText = consulta;
comando.Parameters.AddWithValue("@ProductID", 14);
comando.Parameters.AddWithValue("@Quantity", 70);
comando.Parameters.AddWithValue("@UnitPrice", 23.25);
comando.ExecuteNonQuery();
//Vuelve a aadir salsa picante
comando.Parameters["@ProductID"].Value = 65;
comando.Parameters["@Quantity"].Value = 20;
comando.Parameters["@UnitPrice"].Value = 21.05;
comando.ExecuteNonQuery();
conexin.Close();
}

Como puede ver si los TableAdapter no cumplen sus expectativas puede seguir usando el asistente de
configuracin de adaptador de datos para crear DataAdapter y la lgica para enviar cambios pendiente
rpida y fcilmente.

9.7 Enviar cambios sobre SqlTransaction


Qu pasa si quiere enviar todos sus cambios como una sola unidad de trabajo de modo que todas las
actualizaciones tengan xito o no lo tenga ninguna?. Puede crear un SqlTransaction para su SqlConnection
y envolver las actualizaciones en esta transaccin. Sin embargo el SqlDataAdapter no expone una
propiedad Transaction. Qu hacer?.
Recuerde que el SqlDataAdapter en realidad no enva los cambios. Simplemente distribuye el trabajo a
los objetos SqlCommand de sus propiedades UpdateCommand, InsertCommand y DeleteCommand. El
objeto SqlCommand expone una propiedad Transaction, de modo que si enva cambios por medio de
SqlDataAdapter debe configurar la propiedad Transaction de los objetos SqlCommand que utilizar.
El siguiente fragmento muestra cmo cumplir esta tarea.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;"+
"Integrated Security=true";
string consulta = "SELECT OrderID, ProductID, Quantity, UnitPrice " +
"FROM [Order Details] WHERE OrderID = @OrderID ORDER BY ProductID";
DataTable tabla = new DataTable();
SqlConnection conexin = new SqlConnection(cadcon);
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, conexin);
adaptador.SelectCommand.Parameters.AddWithValue("@OrderID", 10503);
//Define la lgica de actualizacin del adaptador
...
//Abre la conexin y obtiene los resultados de la consulta
conexin.Open();
adaptador.Fill(tabla);
//Modifica los contenidos de la tabla
tabla.Rows[0].Delete();
tabla.Rows[1]["Quantity"] = (short)tabla.Rows[1]["Quantity"] * 2;
tabla.Rows.Add(new object[] {10503, 1, 24, 18 });

160

Escuela de Informtica del Ejrcito

//Crea una nueva transaccin


using(SqlTransaction transaccin = conexin.BeginTransaction()) {
//Configura la propiedad Transaction
adaptador.UpdateCommand.Transaction = transaccin;
adaptador.InsertCommand.Transaction = transaccin;
adaptador.DeleteCommand.Transaction = transaccin;
//Enva los cambios
adaptador.Update(tabla);
//Consigna los cambios y cierra la conexin
transaccin.Commit();
}
conexin.Close();

El DataSet no tiene conocimiento de la transaccin; no hace un seguimiento de los cambios pendientes


enviados sobre la transaccin. La cancelacin de la transaccin no devuelve el DataSet a su estado previo.
Veremos formas de manejar este escenario en el prximo captulo al tratar transacciones distribuidas.
Si est utilizando un SqlCommandBuilder para generar la lgica de actualizacin el uso de transacciones
se complica. El SqlCommandBuilder en realidad no genera la lgica cuando se instancia. Si instancia un
SqlCommandBuilder y llama a SqlDataAdapter.Update, la lgica no se genera en realidad hasta que no se
llama al mtodo Update. Para generar la lgica de actualizacin el SqlCommandBuilder necesita consultar
la base de datos para obtener informacin sobre el conjunto de resultados generado por SelectCommand.
Este comportamiento causa un pequeo problema si quiere usar el SqlCommandBuilder para enviar
cambios sobre una transaccin. Si comienza la transaccin antes de que el SqlCommandBuilder recupere la
informacin de esquema debe asociar el SelectCommand con la transaccin. El fragmento siguiente
sustituye al bloque using del anterior:
using(SqlTransaction transaccin = conexin.BeginTransaction()) {
SqlCommandBuilder cb = new SqlCommandBuilder(adaptador);
//Asocia SelectCommand del adaptador con la transaccin
Adaptador.SelectCommand.Transaction(transaccin);
adaptador.Update(tabla);
transaccin.Commit();
}

Otra opcin es forzar a SqlCommandBuilder a generar la lgica de actualizacin antes de comenzar la


transaccin llamando a uno de los mtodos Get Command y despus asociar el SqlTransaction con los
SqlCommand construidos por el SqlCommandBuilder. El siguiente fragmento sustituye al bloque using del
primer ejemplo de esta seccin. Observe que aade dos lneas antes de este bloque.
//Crea el SqlCommandBuilder y fuerza la recuperacin del esquema
SqlCommandBuilder cb = new SqlCommandBuilder(adaptador);
Cb.GetInserCommand();
using(SqlTransaction transaccin = conexin.BeginTransaction()) {
//Asocia los SqlCommand generados con la transaccin
cb.GetInsertCommand().Transaction = transaccin;
cb.GetUpdateCommand().Transaction = transaccin;
cb.GetDeleteCommand().Transaction = transaccin.;
adaptador.Update(tabla);
transaccin.Commit();
}

9.8 Uso de la coleccin TableMappings


La coleccin TableMappings de la clase SqlDataAdapter afecta al modo en que SqlDataAdapter carga un
DataSet usando el mtodo Fill. En el siguiente cdigo al llamar al mtodo Fill de SqlDataAdapter se crea un
nuevo DataTable cuyo TableName es Table.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT OrderID, ProductID, Quantity, UnitPrice " +
"FROM [Order Details] WHERE OrderID = @OrderID ORDER BY ProductID";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.SelectCommand.Parameters.AddWithValue("@OrderID", 10503);
DataSet datos = new DataSet();
adaptador.Fill(datos);
Console.WriteLine(datos.Tables[0].TableName);

161

ADO.NET 2.0

Si queremos que nuestro nuevo DataTable tenga como nombre Detalles tenemos que cambiar nuestro
cdigo en una de dos formas. La primera opcin es usar el mtodo sobrecargado Fill proporcionando el
nombre deseado:
adaptador.Fill(datos, Detalles);

La otra opcin es aadir una entrada en la coleccin TableMappings del objeto SqlDataAdapter, de modo
que el adaptador sepa que est asociado con el DataTable Detalles, antes de la llamada al mtodo Fill:
adaptador.TableMappings.Add("Table", "Detalles");

La coleccin TableMappings tiene un efecto similar a la hora de enviar cambios a la base de datos. Si
proporciona solo un objeto DataSet en el mtodo Update del adaptador, ste confiar en su coleccin
TableMappings para determinar qu DataTable del DataSet debe utilizar.
Si no ha configurado la coleccin TableMappings del objeto SqlDataAdapter debe usar el mtodo Update
que acepta un DataSet y un nombre de tabla o la versin que acepta un objeto DataTable. Como regla
general debe usar la misma lgica para controlar el DataTable que se va a utilizar en los mtodos Fill y
Update, es decir, especificar el nombre de tabla en el propio mtodo o utilizar la coleccin TableMappings.

9.9 La mejor forma de actualizar


ADO.NET le da muchas opciones a la hora de enviar cambios. Puede generar lgica de actualizacin en
tiempo de ejecucin usando objetos SqlCommandBuilder. Puede proporcionar su propia lgica de
actualizacin en el cdigo, enviar cambios usando consultas INSERT, UPDATE o DELETE o mediante
procedimientos almacenados. Tambin puede usar el asistente de configuracin de TableAdapter para
generar este cdigo en tiempo de diseo. Cul de estas opciones es la mejor?. La respuesta depende de
los parmetros de la aplicacin.
Debe utilizar TableAdapter o DataAdapter?. Los TableAdapter aaden sus mtodos Fill y Get con tipo
que pueden simplificar el cdigo. A la vez, que TableAdapter oculten los DataAdapter que usan
internamente puede ser frustrante si quiere usar funcionalidad que no es parte de un TableAdapter.
Independientemente de si usa TableAdapter o DataAdapter obtendr el mejor rendimiento si enva los
cambios por medio de llamadas a procedimientos almacenados. Sin embargo, si la posibilidad de trabajar
con mltiples bases de datos es una prioridad, utilice actualizaciones basadas en consulta. Sea cual sea la
opcin que elija, proporcione su propia lgica de actualizacin o genrela mediante el asistente en tiempo
de diseo; evite la generacin en tiempo de ejecucin siempre que sea posible. No utilice objetos
SqlCommandBuilder salvo que sea indispensable.
Hay escenarios de actualizacin ms avanzados que discutir. Cmo obtener valores generados por la
base de datos?Cmo enviar cambios desde un DataSet que contiene filas nuevas y borradas en tablas
relacionadas?Cmo detectar y manejar intentos de actualizacin fallidas?Cmo usar ADO.NET con
transacciones distribuidas?. Estas cuestiones y otras se tratarn en el siguiente captulo.

9.10

Referencia del objeto SqlCommandBuilder

En ADO.NET 1.x el nico antecesor comn de las clases CommandBuilder es System.Component.


ADO.NET introduce la clase DBCommandBuilder como base de todas las clases CommandBuilder. Este
cambio permite usar CommandBuilder de diferentes proveedores de datos .NET mediante un conjunto
comn de propiedades y mtodos.

9.10.1 Propiedades
La tabla 10.1 resume las propiedades de la clase SqlCommandBuilder.
Tabla 10. 1: Propiedades de la clase SqlCommandBuilder

Propiedad
ConflictOption

Tipo
ConflictOption

DataAdapter
SetAllValues

SqlDataAdapter
Boolean

Descripcin
Controla el tipo de comprobaciones de concurrencia que aade el
SqlCommandBuilder a la clusula WHERE en los UpdateCommand y
DeleteCommand
que
genera.
El
valor
predeterminado
es
CompareAllSearchableValues.
Devuelve el SqlDataAdapter para el cul se est generando la lgica.
Controla si el SqlCommandBuilder enva valores para todas las columnas o solo
para las modificadas. El valor predeterminado es False.

ConflictOption
La propiedad ConflictOption controla el tipo de comprobaciones de concurrencia que utiliza el
SqlCommandBuilder en la clusula WHERE de UpdateCommand y DeleteCommand. De forma
predeterminada su valor es CompareAllSearchableValues. Los tres valores posibles son:
CompareAllSearchableValues. El SqlCommandBuilder incluye todas las columnas que admiten
bsqueda, no BLOB, en la clusula WHERE. Hay que tener en cuenta que mientras ms columnas
162

Escuela de Informtica del Ejrcito

aparezcan en la consulta, ms tiempo le lleva a la base de datos comparar los valores antes de
determinar si actualiza o no la fila.
CompareRowVersion. El SqlCommandBuilder incluye solo la clave principal y la versin de fila en la
clusula WHERE. Es ms eficiente para la base de datos que comparar todas las columnas no
BLOB.
Recuerde que SQL Server modifica la columna de versin de una fila, si existe, cuando cambia
cualquier valor de la fila. Es importante recordarlo por dos motivos:
Si otro usuario modifica una columna que no aparece en nuestro conjunto de resultados el control
de concurrencia fallar.
Si modifica la fila correctamente deber obtener el nuevo valor de versin generado por el servidor
para poder enviar cambios posteriormente.
Si utiliza esta opcin pero su consulta no devuelve una columna de versin de fila el
SqlCommandBuilder lanzar una InvalidOperationException.
OverwriteChanges. El SqlCommandBuilder utiliza solamente la columna o columnas de clave
principal en la clusula WHERE. A menudo este mtodo se denomina el ltimo gana. Todos los
intentos de actualizacin tendrn xito mientras la fila siga existiendo en la base de datos. Usando
esta opcin un usuario puede sobrescribir los cambios hechos por otro sin ni siquiera verlos.

DataAdapter
Permite examinar o cambiar el SqlDataAdapter con el que est asociado el objeto SqlCommandBuilder.
Puede configurar esta propiedad en el constructor de SqlCommandBuilder.

SetAllValues
De forma predeterminada, cuando el SqlCommandBuilder enve un DataRow con el RowState Modified
enviar solamente las columnas modificadas. Si cambia el valor de esta propiedad a True enviar todas las
columnas.

9.10.2 Mtodos
La tabla 10.2 resume los mtodos de la clase SqlCommandBuilder.
Tabla 10. 2: Mtodos de la clase SqlCommandBuilder

Mtodo
DeriveParameters
GetDeleteCommand
GetInsertCommand
GetUpdateCommand
QuoteIdentifier
RefreshSchema
UnquoteIdentifier

Descripcin
Recupera informacin de parmetros para un SqlCommand que llama a un procedimiento
almacenado.
Devuelve el SqlCommand que contiene la lgica para borrado de filas
Devuelve el SqlCommand que contiene la lgica para insercin de filas
Devuelve el SqlCommand que contiene la lgica para modificacin de filas
Devuelve la cadena que se le pasa entre corchetes
Indica al SqlCommandBuilder que debe regenerar la lgica de actualizacin
Devuelve la cadena que recibe sin los corchetes inicial y final.

DeriveParameters
Puede usar este mtodo para recuperar informacin sobre los parmetros de un procedimiento
almacenado. Devuelve una coleccin de SqlParameter.
No hay forma de diferenciar entre parmetros de entrada/salida y de solo de salida en un procedimiento
almacenado de SQL Server. Todos los parmetros que incluyen la palabra clave OUTPUT se devuelven
como de entrada/salida.
Para poder usar el mtodo DeriveParameters la SqlConnection correspondiente al objeto SqlCommand
proporcionado debe estar disponible y abierta.

GetDeleteCommand, GetInsertCommand, GetUpdateCommand


Estos mtodos permiten examinar la lgica generada por el SqlCommandBuilder.
Estos mtodos tambin pueden ser tiles tambin en tiempo de diseo. Puede crear un
SqlCommandBuilder en cdigo en una pequea aplicacin y usar estos mtodos para mostrar el
CommandText y la informacin de parmetros generada por el SqlCommandBuilder. A continuacin puede
usar la misma lgica e informacin de parmetros en su cdigo.
De forma predeterminada el SqlCommandBuilder genera parmetros llamados @p1, @p2, @p3, etc.
Habr observado que los ejemplos de cdigo de este libro y el asistente de configuracin de TableAdapter
usan nombres de parmetros basados en los nombres de columna. Si quiere generar nombres de
parmetros basados en los nombres de columna correspondientes pase al mtodo el parmetro True.
163

ADO.NET 2.0

Estos parmetros son ms significativos y fciles de seguir en cdigo, pero no se recomiendan cuando
se crean consultas parametrizadas en tiempo de ejecucin. Si crea sus consultas parametrizadas en base a
nombres de columna un usuario malicioso podra, en teora, inyectar su propia lgica en las consultas
generadas.

QuoteIdentifier y UnquoteIdentifier
El acotado de identificadores por lo general es opcional, salvo que el nombre contenga un espacio o un
carcter especial o sea una palabra reservada de SQL. Acotar identificadores ayuda a la base de datos a
reconocer que el valor acotado es en realidad un identificador.
Si tiene in identificador y quiere acotarlo para una consulta use el mtodo QuoteIdentifier. Cuando se
pasa una cadena a este mtodo, el mtodo asume que representa un identificador y lo acota colocando
corchetes alrededor. De forma similar, UnquoteIdentifier asume que el valor que se le pasa representa un
identificador acotado y lo devuelve sin corchetes.
La mayora de las bases de datos soportan el acotado de identificadores, aunque no todas usan los
mismos caracteres. SQL Server de forma predeterminada utiliza corchetes o comillas dobles. Access utiliza
corchetes y el carcter de comilla sencilla. Oracle solo permite el carcter de comillas dobles.
Las clases CommandBuilder que estn diseadas para una base de datos concreta, como
SqlCommandBuilder u OracleCommandBuilder, pueden acotar y desacotar identificadores usando las
normas correspondientes a la base de datos especfica. Las clases CommandBuilder que soportan varias
bases de datos, como OleDbCommandBuilder y OdbcCommandBuilder necesitan un poco de ayuda. Estas
clases exponen las propiedades QuotePrefix y QuoteSuffix que debe configurar para que el
CommandBuilder sepa cmo acotar y desacotar identificadores antes de llamar a los mtodos
QuoteIdentifier o UnquoteIdentifier.
Desde luego, no tiene que preocuparse del acotado si se asegura de que sus nombres de tabla y de
columna no lo precisan.

RefreshSchema
Si est cambiando la estructura de la consulta de su objeto SqlDataAdapter en su aplicacin
probablemente necesitar usar el mtodo RefreshSchema.
El objeto SqlDataAdapter no lanza ningn evento cuando la propiedad CommandText de su propiedad
SelectCommand cambia. Una vez que el objeto SqlCommandBuilder ha generado su lgica de
actualizacin, su trabajo est completo. Si ha cambiado la estructura de la consulta de seleccin de modo
que es necesario volver a generar la lgica de actualizacin puede llamar al mtodo RefreshSchema del
objeto SqlCommandBuilder.
La llamada a RefreshSchema no fuerza a que se actualice la lgica inmediatamente. Simplemente
establece un indicador para sealar que la lgica actual ya no es vlida. La lgica se volver a generar
cuando se llame al mtodo Update del SqlDataAdapter o a uno de los mtodos
Get<Update/Insert/Delete>Command.

164

Escuela de Informtica del Ejrcito

10 Escenarios de actualizacin avanzados


Hasta ahora hemos manejado escenarios de actualizacin simples. En sus aplicaciones probablemente
se enfrente a escenarios ms complejos. Por ejemplo, si est empleando tablas con auto incremento querr
recuperar los valores que la base de datos genera para las nuevas filas. En otras situaciones querr
actualizar el contenido de la fila despus de enviar una actualizacin, como cuando est utilizando
columnas con marcas de tiempo para imponer concurrencia optimista. Puede decidir usar la funcionalidad
de System.Transactions, una novedad de la versin 2.0 del marco de trabajo .NET, para manejar
transacciones distribuidas sin necesidad de emplear System.EnterpriseServices.
Mientras ms compleja es una aplicacin, ms complicados sern los escenarios de actualizacin con
los que se enfrentar. Enviar cambios en datos jerrquicos puede ser un reto. Las aplicaciones multicapa
tienen sus propias complicaciones como enviar objetos DataSet que contienen solo los datos necesarios
para enviar cambios a la base de datos y reintegrar los valores recuperados, como marcas de tiempo y auto
incrementos, en un DataSet existente.
Los intentos de actualizacin optimista no siempre tienen xito. Por ejemplo, su intento de actualizacin
puede fallar si otro usuario ha modificado las filas que quiere actualizar. Probablemente ser mejor saber
cmo manejar estos fallos de una forma elegante en lugar de complicarse intentando impedir estos fallos.
ADO.NET 2.0 introduce soporte para otros escenarios de actualizacin. De forma predeterminada
ADO.NET enva los cambios a la base de datos fila a fila. Usando ADO.NET 2.0 puede enviar los cambios
pendientes por lotes usando un SqlDataAdapter o la funcionalidad de copia masiva de SQL Server.
ADO.NET 2.0 tambin incluye soporte para System.Transactions, una novedad del marco de trabajo .NET
2.0.

10.1 Actualizar una fila tras enviar un cambio


Las actualizaciones que hemos realizado hasta ahora son bsicamente carreteras de una sola va. La
base de datos modifica el contenido de la fila segn la informacin que se proporciona en la consultas.
Aunque la base de datos informa del nmero de filas afectadas no devuelve los nuevos contenidos de las
filas modificadas.
En ocasiones el envo de una actualizacin a la base de datos necesita una carretera de dos direcciones.
Vamos a ver algunas formas de actualizar el contenido de una fila tras enviar un cambio pendiente
utilizando la clase SqlDataAdapter.

10.1.1 Actualizar un DataRow tras enviar cambios


En los siguientes ejemplos utilizaremos una hipottica tabla llamada DetallePedidoConMarcaTiempo
que, como su nombre implica, es similar a la tabla Order Details de Northwind, pero con una columna de
marca de tiempo.
Supongamos que la consulta inicial para recuperar datos es similar a esta:
SELECT OrderID, ProductID, Quantity, UnitPrice, ColumnaMarcaTiempo
FROM DetallePedidoConMarcaTiempo WHERE OrderID = @OrderID

Ya hemos visto que podemos enviar modificaciones a esta tabla con la siguiente consulta parametrizada:
UPDATE DetallePedidoConMarcaTiempo
SET OrderID = @OrderID_N, ProductID = @ProductID_N, Quantity = @Quantity_N,
UnitPrice = @UnitPrice_N
WHERE OrderID = @OrderID_V AND ProductID = @ProductID_V AND
ColumnaMarcaTiempo = @ColumnaMarcaTiempo_V

Podemos usar la siguiente consulta para actualizar el contenido de la fila tras enviar el cambio:
SELECT Quantity, UnitPrice, ColumnaMarcaTiempo
FROM DetallePedidoConMarcaTiempo WHERE OrderID = @OrderID
WHERE OrderID = @OrderID_N AND ProductID = @ProductID_N

Claro que puede ejecutar manualmente esta consulta tras enviar un cambio, pero y si tiene una serie de
cambios para enviar?. Necesitara registrar todos los objetos DataRow cuyos cambios pendientes tienen
xito, recuperar los valores de marca de tiempo de cada uno de ellos y asegurarse de llamar a
AcceptChanges para cada DataRow de modo que el cambio en la marca de tiempo no se vea como un
cambio pendiente de enviar a la base de datos.
Vamos a ver cmo simplificar este proceso usando caractersticas de ADO.NET.
Una precaucin. Aunque el nico dato que necesitamos recuperar para enviar cambios posteriores es la
marca de tiempo habr observado que la consulta recupera todos los datos de columnas no clave. El
objetivo es actualizar toda la fila, no solo la marca de tiempo. Si la tabla contiene un disparador que
modifique los datos enviados, los valores en la base de datos sern diferentes a los del DataRow. Si
165

ADO.NET 2.0

recupera solo la marca de tiempo, tendr toda la informacin que necesita para hacer cambios posteriores
al DataRow y enviar estos cambios con xito. Sin embargo, esto significa que puede sobrescribir valores sin
llegar a verlos.
Como regla general debe actualizar el contenido completo del DataRow y no solo la columna de marca
de tiempo. Las columnas clave no se recuperan en la clusula SELECT porque se han utilizado en la
clusula WHERE.

10.1.2 Consultas por lotes para actualizar tras enviar cambios


Anteriormente vimos cmo usar un SqlDataAdapter para obtener los resultados de conjuntos de
consultas como este:
SELECT CustomerID, CompanyName FROM Customers;
SELECT OrderID, CustomerID, OrderDate FROM Orders;

Tambin puede usar consultas por lotes para recuperar datos despus de enviar una modificacin.
Puede combinar la consulta de actualizacin y la consulta para recuperar el nuevo valor de marca de tiempo
usando una consulta por lotes. Utilice la siguiente consulta como valor de la propiedad CommandText del
objeto UpdateCommand de su SqlDataAdapter:
UPDATE DetallePedidoConMarcaTiempo
SET OrderID = @OrderID_N, ProductID = @ProductID_N, Quantity = @Quantity_N,
UnitPrice = @UnitPrice_N
WHERE OrderID = @OrderID_V AND ProductID = @ProductID_V AND
ColumnaMarcaTiempo = @ColumnaMarcaTiempo_V;
SELECT Quantity, UnitPrice, ColumnaMarcaTiempo
FROM DetallePedidoConMarcaTiempo
WHERE OrderID = @OrderID_N AND ProductID = @ProductID_N;

Tenga en cuenta que no todas las bases de datos permiten este tipo de consultas. SQL Server si lo
soporta, pero Access no. Oracle permite esta funcionalidad por medio de cursores ref.
Podra ejecutar esta consulta por si mismo llamando al mtodo ExecuteReader de SqlCommand y
despus podra usar la propiedad RecordsAffected del SqlDataReader resultante para determinar el nmero
de registros afectados por la actualizacin. Si la consulta ha afectado a una sola fila, podra llamar al
mtodo Read del SqlDataReader, comprobar el contenido, aplicar los valores recuperados al DataRow y
llamar al mtodo AcceptChanges.
O puede utilizar caractersticas de SqlDataAdapter y SqlCommand para manejar esta lgica y aplicar los
valores automticamente.

Propiedad UpdatedRowSource de la clase SqlCommand


SqlDataAdapter utiliza los objetos SqlCommand que almacena en sus propiedades InsertCommand,
UpdateCommand y DeleteCommand para enviar los cambios a la base de datos. A continuacin comprueba
el nmero de filas afectadas por el SqlCommand para determinar si el intento de actualizacin ha tenido
xito. Si determina que el cambio a un DataRow ha tenido xito comprueba la propiedad
UpdatedRowSource del SqlCommand para determinar cmo, o si, debe aplicar los valores que devuelve el
SqlCommand al DataRow.
La propiedad UpdatedRowSource acepta un valor de la enumeracin del mismo nombre, descritos en la
tabla 11.1. De forma predeterminada, el SqlCommand recupera los nuevos datos para la fila modificada
comprobando parmetros de salida y la primera fila devuelta por la consulta.
Tabla 11. 1: Miembros de la enumeracin UpdatedRowSource

Constante
Both

Valor
3

FirstReturnedRecord
None
OutputParameters

2
0
1

Descripcin
Obtiene nuevos datos para la fila usando tanto el primer registro devuelto como los
parmetros de salida. Es el predeterminado.
Obtiene nuevos datos para la fila usando el primer registro devuelto
No recupera datos para la nueva fila tras la ejecucin
Obtiene nuevos datos para la dila usando parmetros de salida

Si la propiedad UpdatedRowSource tiene los valores FirstReturnedRecord o Both y el SqlCommand


informa de que el intento de actualizacin ha tenido xito el SqlDataAdapter buscar el primer registro
devuelto por la consulta y aplicar los valores de este registro al DataRow, haciendo corresponder los
valores de la consulta con las columnas del DataRow usando los nombre de columna de los resultados de
la consulta y la coleccin Columns del DataTable.
De forma similar si la propiedad UpdatedRowSource tiene los valores OutputParameters o Both y el
SqlCommand informa de que el intento de actualizacin ha tenido xito el SqlDataAdapter aplicar los
valores de los parmetros de salida al DataRow, haciendo corresponder estos parmetros con columnas del
DataRow en base a la propiedad SourceColumn de los SqlParameter.
166

Escuela de Informtica del Ejrcito

Puede mejorar el rendimiento de su actualizacin configurando adecuadamente la propiedad


UpdatedRowSource. El SqlCommand no analiza la consulta que va a ejecutar, y no sabe si la consulta
devolver datos por medio de una clusula SELECT o mediante parmetros de salida. Si deja la propiedad
UpdatedRowSource con el valor Both, el predeterminado, el SqlDataAdapter comprobar tanto los
parmetros de salida como un registro devuelto. Esta comprobacin tiene una pequea penalizacin en
rendimiento. Configurando adecuadamente UpdatedRowSource puede evitar comprobaciones innecesarias.

Mejorar la consulta por lotes


Podemos mejorar la lgica de la consulta por lotes utilizada para enviar el cambio pendiente y devolver el
contenido de la fila modificada. Vuelva a repasar la consulta al principio de esta seccin.
Qu sucede si la consulta UPDATE no afecta a ninguna fila?. Si ejecuta esta consulta en una
herramienta como SQL Server Management Studio ver que la consulta SELECT devuelve una fila de datos
incluso aunque no se modifique ninguna fila.
El SqlDataAdapter sabe comprobar este posible resultado. El SqlDataAdapter llama al mtodo
ExecuteReader del SqlCommand almacenado en su propiedad UpdateCommand y comprueba la propiedad
RecordsAffected del SqlDataReader resultante para determinar si debe aplicar valores del SqlDataReader al
DataRow. En otras palabras no tiene que preocuparse de que este escenario afecte al DataRow.
Pero cmo afecta este escenario a SQL Server?. Est pidiendo a SQL Server que ejecute la consulta
SELECT incluso si la consulta UPDATE no afecta a ninguna fila. Puede hacer que esta consulta sea ms
eficiente usando la funcin de SQL Server @@ROWCOUNT, que devuelve el nmero de filas afectado por
la consulta anterior. Si la consulta UPDATE no afecta a ninguna fila, no hay necesidad de ejecutar la
consulta SELECT.
UPDATE DetallePedidoConMarcaTiempo
SET OrderID = @OrderID_N, ProductID = @ProductID_N, Quantity = @Quantity_N,
UnitPrice = @UnitPrice_N
WHERE OrderID = @OrderID_V AND ProductID = @ProductID_V AND
ColumnaMarcaTiempo = @ColumnaMarcaTiempo_V;
IF @@ROWCOUNT <> 0
SELECT Quantity, UnitPrice, ColumnaMarcaTiempo
FROM DetallePedidoConMarcaTiempo
WHERE OrderID = @OrderID_N AND ProductID = @ProductID_N;

10.1.3 Recuperar datos con parmetros de salida


La propiedad UpdatedRowSource tambin permite especificar que se recuperarn nuevos datos usando
parmetros de salida. Recuperar nueva informacin por medio de parmetros de salida es ms eficiente
que devolver datos en una consulta SELECT. Los parmetros tienen una sobrecarga menor que un
conjunto de resultados, por lo que este mtodo puede mejorar el rendimiento del cdigo a la hora de enviar
cambios. Ms importante, devolver valores generados por el servidor por medio de parmetros puede
funcionar con las nuevas caractersticas de actualizacin por lotes de ADO.NET, que veremos ms
adelante.
Vamos a transformar la consulta anterior para que devuelva valores por medio de parmetros de salida.
UPDATE DetallePedidoConMarcaTiempo
SET OrderID = @OrderID_N, ProductID = @ProductID_N, Quantity = @Quantity_N,
UnitPrice = @UnitPrice_N
WHERE OrderID = @OrderID_V AND ProductID = @ProductID_V AND
ColumnaMarcaTiempo = @ColumnaMarcaTiempo_V;
IF @@ROWCOUNT <> 0
SELECT @Quantity_N = Quantity, @UnitPrice_N = UnitPrice,
@ColumnaMarcaTiempo_N = ColumnaMarcaTiempo
FROM DetallePedidoConMarcaTiempo WHERE OrderID = @OrderID_N
WHERE OrderID = @OrderID_N AND ProductID = @ProductID_N;

Para usar esta lgica de actualizacin y renovar los contenidos del DataRow necesitamos hacer dos
cosas. Primero, tenemos que vincular SqlParameter con los DataColumn apropiados, con su propiedad
Direction establecida con el valor adecuado. Los parmetros @Quantity_N y @UnitPrice_N se usan para
enviar valores a la base de datos y para devolver el contenido de las columnas despus del cambio. Por
tanto, la propiedad Direction de los SqlParameter debe ser InputOutput. El parmetro
@ColumnaMarcaTiempo_N solamente devuelve datos, por lo que su propiedad Direction debe ser Output.
Segundo, necesitamos asegurarnos de que la propiedad UpdatedRowSource del UpdateCommand es
OutputParameters.
Podemos crear un procedimiento almacenado que use una consulta similar a la anterior teniendo la
precaucin de declarar los parmetros necesarios como OUTPUT.
167

ADO.NET 2.0

Todo lo que queda es asignar el nombre del procedimiento a la propiedad CommandText de


UpdateCommand, crear la coleccin de objetos Parameter del SqlCommand y cambiar la propiedad
UpdatedRowSource a OutputParameters. Es as de fcil.

10.1.4 Evento RowUpdated de la clase SqlDataAdapter


Algunas bases de datos no soportan consultas por lotes y parmetros de salida en procedimientos
almacenados. Si est usando este tipo de base de datos los dos mtodos anteriores para recuperar datos
tras realizar una actualizacin no son vlidos. Sin embargo, tiene otra opcin, adems de cambiar a una
base de datos que soporte mayor funcionalidad.
Cada clase DataAdapter expone dos eventos que lanza cuando enva los cambios almacenados en un
DataRow: RowUpdating y RowUpdated. Como sus nombres indican, el primero se produce justo antes de
enviar el cambio y el segundo inmediatamente despus.
Si enva cambios en varias filas, estos eventos se lanzan para cada una de ellas, los dos eventos de
cada fila antes de pasar a la siguiente.
El comportamiento es ligeramente diferente si se configura la propiedad UpdatedBatchSize del
DataAdapter para enviar cambios por lotes. En este caso se lanza el evento RowUpdating para cada fila del
lote antes de enviar los cambios y el evento RowUpdated una vez despus.
Podemos usar el evento RowUpdated para obtener el nuevo valor que genera la base de datos para la
fila actualizada. El siguiente fragmento de cdigo muestra cmo manejar este evento. Por brevedad, hace
referencia a funciones imaginarias que crea el DataTable, el OleDbDataAdapter y el OleDbCommand que
se usan para actualizar los contenidos de las columnas de marca de tiempo, cantidad y precio unitario del
DataRow despus de una actualizacin con xito.
Observe que en el controlador de RowUpdated el cdigo comprueba que la actualizacin ha tenido xito
y que el cambio almacenado en la fila es una insercin o actualizacin. Obviamente, si hemos borrado una
fila no hay necesidad de consultar valores generados por el servidor.
//using System.Data.OleDb;
OleDbDataAdapter adaptador = creaAdaptador();
OleDbCommand cmdActualiza = creaComando();
adaptador.RowUpdated += new OleDbRowUpdatedEventHandler(FilaActualizada);
DataTable tabla = creaTabla();
adaptador.Fill(tabla);
.
.
.
adaptador.Update(tabla);
private void FilaActualizada(object sender OleDbRowUpdatedEventArgs e){
if((e.Status == UpdatedStatus.Continue) &&
((e.StatementType == StatementType.Insert) ||
(e.StatementType == StatementType.Update))){
cmdActualiza.Parameters[@OrderID].Value = e.Row[OrderID];
cmdActualiza.Parameters[@ProductID].Value = e.Row[ProductID];
using(OleDbDataReader lector = cmdActualiza.ExecuteReader()){
lector.Read();
e.Row[Quantity] = lector[Quantity];
e.Row[UnitPrice] = lector[UnitPrice];
e.Row[TimestampColumn] = lector[TimestampColumn];
lector.Close();
}
}
}

El cdigo recupera normalmente los nuevos valores y los asigna a las columnas correspondientes del
DataRow.
Este mtodo es muy flexible porque funciona con cualquier base de datos, pero tiene una penalizacin
en rendimiento.

10.1.5 Cdigo de ejemplo


El cdigo de ejemplo para este captulo demuestra cmo refrescar una fila tras enviar una actualizacin
usando UpdateRowSource.FirstReturnedRecord, UpdateRowSource.OutputParameters y el evento
RowUpdated.

168

Escuela de Informtica del Ejrcito

10.2 Recuperar valores de auto incremento


10.2.1 Con SQL Server
Suponga que no est utilizando un SqlDataAdapter, est creando sus propias consultas para enviar
cambios. Si est trabajando con la base de datos Northwind puede usar esta consulta para recuperar datos
de la base de datos:
SELECT OrderID, CustomerID, OrderDate FROM Orders

Para insertar una fila nueva puede usar una consulta como esta:
INSERT INTO Orders (CustomerID, OrderDate)
VALUES (@CustomerID, @OrderDate)

Si est trabajando con alguna version de SQL Server 2005, incluyendo SQL Express, 2000, incluyendo
MSDE, probablemente utilice la siguiente consulta para recuperar el valor de auto incremento:
SELECT SCOPE_IDENTITY()

En versiones anteriores de SQL Server utilice:


SELECT @@IDENTITY

Esta consulta es clave para recuperar el nuevo valor de autoincremento. Podemos usarla en el modelo
de objetos de ADO.NET de la misma forma que usamos la consulta para recuperar la columna de marca de
tiempo en los ejemplos anteriores.
Podemos modificar el CommandText del InsertCommand del objeto SqlDataAdapter para actualizar los
contenidos de la fila usando SCOPE_IDENTITY() tras cada insercin:
INSERT INTO Orders(CustomerID, OrderDate)
VALUES(@CustomerID, @OrderDate);
SELECT @OrderID = SCOPE_IDENTITY()

Tambin podemos usar esta consulta en un procedimiento almacenado, declarando el parmetro


@OrderID de salida.
Finalmente, podemos usar el evento RowUpdated del objeto SqlDataAdapter para ejecutar una consulta
para recuperar el valor de autoincremento.

SCOPE_IDENTITY() y @@IDENTITY
La consulta SELEC @@IDENTITY devuelve el ltimo valor de identidad generado sobre su conexin.
Esto significa que el trabajo hecho por otros usuario sobre otras conexiones no afectar al resultado de la
consulta. Pero esto no significa que vaya a recibir el resultado que espera.
Los administradores de bases de datos a menudo usan sus propias tablas de auditora para hacer un
seguimiento de los cambios hechos en la base de datos. Para seguir estos cambios generalmente confan
en disparadores o procedimientos almacenados. Suponga que la actualizacin que hacemos en una tabla
dispara una insercin en una tabla de auditora que incluye una columna de autoincremento. Si tras la
actualizacin lanzamos una consulta SELECT @@IDENTITY recibir el valor de autoincremento de la
nueva fila en la tabla de auditora y no el generado para la nueva fila en la tabla Orders. Recuerde que
SELECT @@IDENTITY devuelve el ltimo valor de autoincremento generado en la conexin.
Para enfrentarse a esta situacin SQL Server introdujo una nueva forma de recuperar valores de autoincremento en su versin 2000: SCOPE_IDENTITY(). Si utiliza SELECT SCOPE_IDENTITY() en la situacin
anterior obtendr el valor de autoincremento generado para la nueva fila de la tabla Orders.
Si est trabajando con SQL Server 2000 o posterior debe usar SCOPE_IDENTITY() en lugar de
@@IDENTITY. Hay una excepcin menor a esta regla. Si inserta la nueva fila usando un procedimiento
almacenado pero quiere recuperar el valor despus de llamar al procedimiento SCOPE_IDENTITY()
devolver null. Segn los libros en lnea de SQL Server SCOPE_IDENTITY devuelve el ltimo valor de auto
incremento generado en el alcance actual. Como he dicho, es una excepcin menor. Si va a insertar nuevas
filas usando procedimientos almacenados y quiera recuperar el nuevo valor de auto incremento debe
devolver esta informacin usando un parmetro de salida.

10.2.2 Con Access


Si est trabajando con una base de datos Access tambin puede usar la consulta SELECT
@@IDENTITY para recuperar valores de auto incremento recin generados. Esta caracterstica se aadi
en la version 4.0 del motor Jet OLE DB y solo funciona con bases de datos con formato para Access 2000 o
posterior. Como su contrapartida de SQL Server la consulta SELECT @@IDENTITY devuelve el ltimo
valor de auto incremento generado sobre la conexin.
169

ADO.NET 2.0

Las bases de datos Access no soportan parmetros de salida en las consultas. El proveedor Jet OLE DB
no soporta consultas por lotes. Por tanto, la nica forma de obtener los valores de auto incremento es usar
el evento RowUpdated del objeto OleDbDataAdapeter.

10.2.3 Con secuencias Oracle


Las bases de datos Oracle no aceptan columnas de auto incremento, pero s una construccin similar
la secuencia. Por lo general se crea una secuencia para generar nuevos valores para una columna en una
tabla, pero no hay un enlace directo entre la secuencia y la tabla o columna. No hay forma de que una
tecnologa como ADO.NET sepa cmo asociar una tabla con una secuencia.
Puede usar la secuencia de dos formas. Puede hacer referencia a la secuencia en la consulta INSERT.
Suponiendo que el nombre de la secuencia es miSecuencia:
INSERT INTO Tabla(ID, OtraColumna)
VALUES(miSecuencia.NEXTVAL, Fila nueva);

La secuencia devuelve un nuevo valor cada vez que se llama a NEXTVAL. Una vez insertada la nueva
fila puede consultar la secuencia para obtener el ltimo valor utilizado:
SELECT miSecuencia.CURRVAL FROM DUAL

Como al usar SCOPE_IDENTITY y @@IDENTITY de SQL Server y Access, el resultado de esta


consulta no se ve afectado por otros usuarios que utilicen la secuencia para insertar nuevas filas.
Cmo recuperar los nuevos valores de secuencia para los objetos DataRow?. La opcin ms simple es
usar una consulta por lotes que devuelva el nuevo valor clave por medio de un parmetro de salida.
Consulte el nuevo valor a la secuencia, asigne el valor como parmetro de salida y haga referencia al
parmetro en la consulta INSERT INTO:
//using System.Data.OracleClient
OracleDataAdapter datos = CreaAdaptador();
OracleConnection conexin = adaptador.SelectCommand.Connection;
string consulta = BEGIN +
SELECT MiSecuencia.NEXTVAL INTO :ID FROM DUAL; +
INSERT INTO Tabla (ID, UnaColumna) VALUES (:ID, :UnaColumna); +
END;
OracleCommand comando = new OracleCommand(consulta, conexin);
OracleParameter pr = comando.Parameters.Add(:ID, OracleType.Int32, 0 , ID);
pr.Direction = ParameterDirection.Output;
comando.UpdatedRowSource = UpdatedRowSource.OutputParameters;
adaptador.InsertCommand = comando;

Si est trabajando con Oracle 10g ni siquiera necesita usar una consulta por lotes. Simplemente puede
usar la clusula RETURNING en la consulta INSERT INTO:
INSERT INTO Tabla(ID, UnaColumna) VALUES
(miSecuencia.NEXTVAL, :UnaColumna)
RETURNING ID INTO :ID

Como con SQL Server, puede llamar a un procedimiento almacenado y devolver el nuevo valor clave por
medio de un parmetro de salida. Puede usar este tipo de procedimiento almacenado en el InsertCommand
de su objeto DataAdapter para insertar nuevas filas. Si enlaza el parmetro de salida con la columna
correspondiente de su objeto DataRow el DataRow contendr el nuevo valor inmediatamente despus de
enviar la nueva fila a su base de datos.
Tenga en cuenta que no existe un enlace directo entre la secuencia y la columna en la tabla, por lo que
es posible insertar nuevos valores sin utilizar la secuencia. Para impedir que la secuencia y la tabla queden
descoordinadas asegrese de que la nica forma de modificar la tabla sea por medio de procedimientos
almacenados.

Generar valores temporales para sus objetos DataColumn de secuencia


Las secuencias no son columnas con autoincremento, pero puede hacer que ADO.NET genere valores
temporales para las nuevas filas configurando la propiedad AutoIncrement de los objetos DataColumn
correspondientes a true. Sin embargo, debe hacerlo manualmente. No hay una vinculacin directa entre la
secuencia y la tabla. Si usa el mtodo FillSchema del objeto OracleDataAdapter o el asistente de
configuracin de TableAdapter o de DataAdapter ADO.NET no sabr que la columna de la tabla est
asociada con una secuencia.
Se encontrar con problemas similares independientemente de cmo genere la lgica de actualizacin
para sus objetos OracleDataAdapter. El objeto OracleCommandBuilder y el asistente de configuracin del
adaptador de datos no saben omitir la columna de la lgica en InsertCommand. Si va a confiar en estas
170

Escuela de Informtica del Ejrcito

herramientas para generar su lgica de actualizacin debe hacer algunos cambios en el resultado de la
generacin.

10.2.4 Aplicaciones de ejemplo


El cdigo de ejemplo de muestra cmo recuperar nuevos valores de autoincremento desde bases SQL
Server, Access y Oracle usando UpdateRowSource.OutputParameters para SQL Server y Oracle y el
evento RowUpdated para Access,

10.3 Enviar cambios jerrquicos


Cuando modifica datos en mltiples niveles de un DataSet jerrquico tendr que enfrentarse a dos retos
a la hora de enviar los cambios a la base de datos. Vamos a examinar estos escenarios.

10.3.1 Enviar borrados e inserciones pendientes


Suponga que est tratando con una jerarqua que contiene pedidos y detalles de pedido. La aplicacin
que est creando es un sistema de introduccin de pedidos. El usuario ha hecho una serie de cambios en
los datos y ahora quiere enviar estos cambios a la base de datos. Los datos modificados en el DataSet
contienen nuevos pedidos y nuevos detalles de pedido. El DataSet tambin contiene pedidos y detalles de
pedido marcados para borrar.
El reto est en enviar estos cambios en el orden apropiado para cumplir con las restricciones de
integridad referencial en la base de datos. La base de datos Northwind contiene restricciones que precisan
que todos los pedidos hagan referencia a clientes existentes en la base de datos.
Si el DataSet contiene nuevos pedidos y nuevos detalles par estos pedidos, debemos enviar los nuevos
pedidos antes de enviar sus detalles. Como regla general, debe enviar las filas nuevas desde arriba hacia
abajo.
Para las filas borradas sucede al revs. No puede borrar pedidos que tengan detalles; debe borrar los
detalles primero.
Suponiendo que tiene tanto pedidos aadidos como borrados e intentamos actualizar la tabla de pedidos
primero y la de detalles despus no funcionar, porque intenta borrar pedidos que an tienen detalles
pendientes. Si invertimos el orden de las actualizaciones ser la primera actualizacin la que falle porque
intentar enviar detalles para pedidos que an no existen en la base de datos.
Qu puede hacer el programador?. Necesitamos una forma de controlar el orden de las actualizaciones
de un DataSet jerrquico para enviar los cambios en el orden adecuado:
1.
2.
3.
4.
5.
6.

Nuevos pedidos
Nuevos detalles
Modificaciones a pedidos
Modificaciones a detalles
Detalles borrados
Pedidos borrados

Usar el mtodo Select del objeto DataTable para enviar cambios jerrquicos
El mtodo Select nos permite obtener una array de DataRow que cumplen una condicin determinada. Y
una de las varias sobrecargas del mtodo Update acepta un array de objetos DataRow. Que coincidencia.
El siguiente fragmento usa el mtodo Select para aislar solamente los datos deseados y enviarlos a la
base de datos en el orden adecuado.
DataSet ds = CreaDataSet();
DataTable tblPedidos = ds.Tables[Pedidos];
DataTable tblDetalles = ds.Tables[Detalles];
SqlDataAdapter adpPedidos = CreaAdpPedidos();
cSqlDataAdapter adpDetalles = CreaAdpDetalles();
LlenaYModificaDataSet(ds);
DataViewRowState estadoFila;
//Enva los pedidos nuevos y despus los detalles
estadoFila = DataViewRowState.Added;
adpPedidos.Update(tablaPedidos.Select(, , estadoFila));
adpDetalles.Update(tablaDetalles.Select(, , estadoFila));
//Enva primero los pedidos modificados y despus los detalles modificados
estadoFila = DataViewRowState.ModifiedCurrent;
adpPedidos.Update(tablaPedidos.Select(, , estadoFila));
adpDetalles.Update(tablaDetalles.Select(, , estadoFila));

171

ADO.NET 2.0

//Enva los detalles borrados y despus los pedidos borrados


estadoFila = DataViewRowState.Deleted;
adpPedidos.Update(tablaPedidos.Select(, , estadoFila));
adpDetalles.Update(tablaDetalles.Select(, , estadoFila));

Envo de cambios jerrquicos con GetChanges


Tambin puede usar el mtodo GetChanges de DataSet o DataTable para controlar el orden de las
actualizaciones. El siguiente fragmento crea un DataTable que contiene solo las filas con cambios
pendientes en el DataTable inicial.
tablaPedidosNuevos = tablaPedidos.GetChanges(DataRowState.Added);
adpPedidos.Update(tablaPedidosNuevos);
tablaDetallesNuevos = tablaDetalles.GetChanges(DataRowState.Added);
adpClientes.Update(tablaDetallesNuevos);

Esta forma es ms fcil de leer que la anterior, pero menos recomendable.


Cuando se usa el mtodo GetChanges se crea un nuevo objeto separado. El cdigo anterior enva las
nuevas filas a las tablas de pedidos y de detalles de la base de datos. En la base de datos Northwind la
tabla Orders tiene una columna de auto incremento: OrderID. Si el SqlDataAdapter que enva los cambios a
la tabla Orders incluye lgica para obtener los valores generados para la columna OrderID los valores se
insertarn en el DataTable usando en el mtodo Update tblPedidosNuevos. Pero este DataTable es
diferente al DataTable tblPedidos, de modo que el DataTable principal no contendr los valores de OrderID.
Este escenario tendr ms sentido cuando tratemos el aislamiento y reintegracin de cambios ms
adelante en este captulo.
En cambio, si usa el mtodo Select para enviar las filas modificadas los cambios que devuelve el
SqlDataAdapter se aplicarn al DataTable original, porque el mtodo Select devuelve un array de objetos
DataRow. Los objetos DataRow que contiene el array son en realidad punteros a objetos DataRow en el
DataTable, por lo que los cambios que se hagan a los contenidos del array se aplicarn al DataTable
principal.

10.3.2 Valores auto incremento y datos relacionales


Para desarrollar este punto vamos a utilizar un ejemplo en el cual el usuario introducir dos nuevos
pedidos y los detalles de cada uno de ellos.
Anteriormente recomendamos configurar las propiedades AutoIncrementSeed y AutoIncrementStep del
objeto DataColumn con los valores -1 cuando se trabaje con auto incremento. Si sigue esta recomendacin
y aade nuevos pedidos y detalles, su DataSet puede tener un aspecto similar al siguiente:
OrderID
10268
10785
-1
-2

Orders
CustomerID
EmployeeID
GROSR
8
GROSR
1
GROSR
7
GROSR
7

OderDate
7/30/1996
12/18/1997
1/14/2002
1/14/2002

OrderID
10268
10268
10785
10785
-1
-1
-2
-2

Order Details
ProductID
Quantity
29
10
72
4
10
10
75
10
1
12
67
24
4
6
65
8

UnitPrice
99.00
27.80
31.00
7.75
18.00
14.00
22.00
21.05

Fig.11. 1: Pedidos y detalles de pedido pendientes de enviar a la base de datos

Para enviar con xito los nuevos pedidos y sus detalles necesitamos enviar los pedidos, recuperar los
valores de autoincremento correspondientes, aplicar estos valores a los detalles correspondientes y enviar
los nuevos detalles a la base de datos. Este proceso suena complicado, pero es asombrosamente sencillo.
Ya sabe cmo enviar los nuevos pedidos; solo necesita enviar los pedidos nuevos puede usar el mtodo
Select del DataTable que contiene los pedidos, como hemos visto en este mismo captulo. Tambin puede
obtener los nuevos valores de auto incremento usando cualquiera de los mtodos ya mostrados.
Pero cmo aplicar los nuevos valores de autoincremento a los nuevos detalles de pedido?. ADO.NET
hace el trabajo por medio del DataRelation, que de forma predeterminada propaga los cambios en cascada.
Si ha configurado un DataRelation entre la tabla de pedidos y la de detalles, en cuanto se envan los
pedidos y se recuperan sus valores de auto incremento el DataRelation propaga los cambios a la tabla de
detalles.
Una vez que las nuevas filas de la tabla de detalles contiene los valores apropiados para la columna
OrderID puede enviar las nuevas filas de detalle a la base de datos. Gracias a la funcionalidad de la clase
DataRelation la propagacin de los nuevos valores de auto incremento es simplemente parte del proceso.
Si los valores de auto incremento que acaba de recuperar no se propagan a las filas hijas relacionadas
puede deberse a cmo las caractersticas de Visual Studio en tiempo de diseo han configurado el DataSet
172

Escuela de Informtica del Ejrcito

con tipo. Como ya se ha indicado, los DataRelation configurados automticamente por Visual Studio 2005
no van acompaados de un ForeignKeyConstraint, lo que impide la propagacin de cambios. Si se
encuentra con este problema pulse dos veces sobre el DataRelation en el DataSet, y en el dilogo
resultante indique que quiere tanto el DataRelation como el ForeingKeyConstraint. Compruebe que las
reglas de actualizacin y borrado tienen el valor Cascade y la de aceptacin o rechazo el valor None.

10.4 Aislar y reintegrar cambios


Suponga que est creando una aplicacin multicapa con un interface de usuarios Windows que accede a
sus bases de datos por medio de un servicio Web. El servicio Web devuelve objetos DataSet que contienen
la informacin solicitada. La aplicacin cliente permite al usuario modificar el contenido del DataSet.
Despus de modificar los datos el usuario puede pulsar un botn y el cliente enviar los cambios a la base
de datos por medio del servicio Web. La forma ms simple de enviar estos cambios es enviar el DataSet de
vuelta al servicio Web y que el servicio Web utilice objetos DataAdapter para enviar los cambios.
Para obtener el mejor rendimiento posible de su aplicacin querr aprovechar al mximo el ancho de
banda. Mientras menos datos se muevan entre la aplicacin cliente y el servicio Web, ms velocidad.
Limitar la cantidad de datos que devuelve el servicio Web es muy fcil e intuitivo. No disee el servicio
Web para devolver el contenido completo de las tablas si estas pueden contener miles, o incluso millones,
de filas; si lo hace as el rendimiento de la aplicacin sufrir. Asegrese tambin de que el servicio devuelve
solo los datos que necesita la aplicacin cliente.
Qu hay de limitar la cantidad de datos que la aplicacin cliente enva al servicio Web?. Puede pasar
un DataSet de vuelta al servicio Web para que el servicio Web enve los cambios a la base de datos. Pero si
este es el objetivo de llamar al servicio Web, probablemente no le pasar el DataSet completo. Si el DataSet
contiene unos pocos cientos de filas y el usuario ha modificado solo un puado de ellas, pasar todo el
DataSet al servicio Web ser extremadamente ineficiente. Cmo podemos mejorar este proceso?.

10.4.1 Ahorrar ancho de banda con GetChanges


Como mencion anteriormente, los objetos DataSet y DataTable exponen un mtodo GetChanges.
Cuando se llama a este mtodo sobre el DataSet se obtiene un objeto DataSet que tiene la misma
estructura que el original, pero que solo contiene las filas modificadas del original; lgicamente, tambin
contendr las filas necesarias para mantener la integridad referencial.
Si est llamando a su servicio Web para enviar cambios a la base de datos puede obtener una mejora de
rendimiento significativa llamando primero al mtodo GetChanges de su DataSet y enviando al servicio Web
el resultado, porque no hay necesidad de enviar las filas no modificadas.
Anteriormente tratamos la forma de utilizar columnas de marcas de tiempo en comprobaciones de
concurrencia, y la forma de recuperar valores generados por la base de datos despus de enviar cambios.
Este proceso se complica en una aplicacin multi capa. Supongamos que estamos utilizando marcas de
tiempo en la lgica de actualizacin del servicio Web, y que recuperamos estos valores. Pero qu sucede
con estos nuevos valores?.
Los valores se recuperan y se almacenan en el DataSet en el servicio Web. Pero este DataSet est
separado del de la aplicacin cliente. Cmo obtener los nuevos valores en el DataSet de la aplicacin?.
Podra limitarse a que el servicio Web devuelva un DataSet que contenga los mismos datos que tiene la
aplicacin cliente. Pero aunque este mtodo asegura que la aplicacin est ms actualizada, no hace el
mejor uso posible del ancho de banda.
Una forma ms econmica de trabajar es hacer que el servicio Web devuelva el DataSet que ha recibido,
con los nuevos valores incluidos.
Pero esto solo resuelve parte del problema. La aplicacin cliente tiene ahora los valores de marca de
tiempo cmo podemos integrar el DataSet devuelto por el servicio Web con el que de la aplicacin cliente?

Mtodo Merge de la clase DataSet


La respuesta ms simple es usar el mtodo Merge de la
clase DataSet. Este mtodo permite fusionar los contenidos de
un DataSet, un DataTable o un array de objetos DataRow en
un DataSet existente.
En el ejemplo de la figura cada DataSet contiene una tabla
con un mismo nombre. Despus de llamar a Merge sobre el
DataSet principal y pasar el segundo el DataSet principal
contendr todas sus columnas ms las del segundo DataSet.
El DataSet principal contendr tambin las filas del segundo.

DataTable
principal
ColA
ColB
A1
B1
A2
B2

DataTable
secundario
ColC
ColD
C1
D1
C2
D2

DataTable principal despus de


Merge
ColA
ColB
ColC
ColD
A1
B1
null
null
A2
B2
null
null
null
null
C1
D1
null
null
C2
D2
Fig.11. 2: Ejemplo bsico de utilizacin de Merge

173

ADO.NET 2.0

Este ejemplo no es especialmente til. Pocos


desarrolladores combinan dos objetos DataSet con
objetos DataTable con el mismo nombre pero con
estructuras diferentes. La figura 11.3 muestra un
ejemplo ms tpico. Los dos objetos DataSet contienen
objetos DataTable con estructuras similares. En cada
una de las tabla la columna ID es la clave principal.
Despus de la llamada al mtodo Merge el DataSet
principal contendr una columna adicional procedente
del segundo DataSet. El contenido tambin habr
cambiado como resultado de la fusin.

ID
1
2
3

DataTable
principal
ColA
ColB
P
P
P
P
P
P

ID
1
2
4

DataTable
secundario
ColA ColC
S
S
S
S
S
S

DataTable principal despus de


Merge
ID
ColA
ColB
ColC
1
S
P
S
2
S
P
S
3
P
P
null
4
S
null
S
Fig.11. 3: Ejemplo tpico de utilizacin de Merge

La diferencia entre los dos ejemplos es la clave principal. Si ADO.NET encuentra filas que tienen el
mismo valor de clave principal combina sus contenidos en una sola fila. En este ejemplo, ambos objetos
DataSet contienen los valores 1 y 2. Cada uno tiene adems una fila adicional que no tiene contrapartida en
el otro.
Observe que en el resultado de la fusin los datos del DataSet que se pasa como parmetro tienen
precedencia. Los valores de la columna ColA del segundo DataSet reemplazan a los correspondientes del
DataSet principal cuando se combinan filas; en nuestro caso las filas que se combinan son las que tienen
los ID 1 y 2.
Ahora que conocemos el mecanismo de fusin, retomemos nuestro escenario.
Nuestra aplicacin cliente recupera informacin de clientes desde un servicio Web. El usuario modifica el
contenido del DataSet. La aplicacin cliente usa el mtodo GetChanges para crear un nuevo DataSet que
contiene solamente los cambios y enva este DataSet ms pequeo de vuelta al servicio Web.
El servicio Web enva los cambios a la base de datos usando los valores de marca de tiempo en la lgica
de actualizacin para imponer concurrencia optimista. El servicio Web tambin recupera los nuevos valores
de la marca de tiempo y almacena esta informacin en su DataSet. Despus de completar esta operacin el
servicio Web devuelve el DataSet con estos nuevos valores incluidos. La aplicacin cliente recibe este
DataSet y lo fusiona con el principal como se muestra en el siguiente fragmento de cdigo. Puesto que los
valores del segundo DataSet reemplazan a los del primero, los nuevos valores de marca de tiempo
sobrescriben a los existentes.
ClaseServicioWeb objServicio = new ClaseServicioWeb();
DataSet dsPrincipal = objServicio.LeeDataSet();
ModificaContenidoDataSet(dsPrincipal);
DataSet dsCambios = dsPrincipal.GetChanges();
dsCambios = objServicio.EnviaCambios(dsCambios);
dsPrincipal.Merge(dsCambios);

Mtodo Merge y propiedad RowState


Ya casi hemos terminado, pero no del todo. Si comprobamos el contenido de las filas que modificamos
originalmente en el DataSet principal veremos que tienen los nuevos valores de marca temporal, pero estas
filas an tienen el valor Modified en RowState.
Si el usuario pulsa un botn en la aplicacin para enviar los cambios a la base de datos el DataSet
devuelto por el mtodo GetChanges contendr an las filas que el usuario modific previamente. Cuando el
servicio Web recibe este DataSet e intenta enviar los cambios el intento de actualizacin falla porque la
base de datos ya contiene estos cambios.
Sabemos que hemos enviado estos cambios a la base de datos, pero ADO.NET no lo sabe. Cuando el
servicio Web enva los cambios ADO.NET cambia el RowState de las filas modificadas de Modified a
Unmodified. Pero este cambio se produce en el DataSet del servicio Web. ADO.NET no cambia el RowState
de las filas modificadas en el DataSet de la aplicacin cliente porque no hay enlace entre los dos objetos
DataSet.
La fusin del DataSet devuelto por el servicio Web no cambia la propiedad RowState de las filas
modificadas en el DataSet principal. Esto es lo que queremos que pase, pero ADO.NET no lo hace
automticamente. Sin embargo, como sabemos que hemos enviado con xito los cambios que contiene
actualmente el DataSet principal podemos cambiar el RowState de las filas modificadas a Unmodified
llamando al mtodo AcceptChanges inmediatamente despus de la llamada a Merge.

Mtodo Merge y valores de auto incremento


Vamos a cambiar el ejemplo ligeramente. En lugar de trabajar con informacin de clientes vamos a
trabajar con informacin de pedidos. En este ejemplo, la tabla de base de datos que contiene la informacin
174

Escuela de Informtica del Ejrcito

de pedido usar una columna de autoincremento como clave principal, como hace la tabla de pedidos en la
base de datos Northwind.
Como en el ejemplo anterior la aplicacin cliente se comunicar con la base de datos por medio de un
servicio Web. Supongamos que el usuario recupera dos pedidos para un cliente existente, aade dos
nuevos pedidos para este cliente y enva estos nuevos pedidos a la base de datos. Ya sabe cmo usar el
mtodo GetChanges para pasar al servicio Web solamente las filas modificadas.
Tambin sabe cmo recuperar valores de auto incremento nuevos en el DataSet que usa el servicio Web
para enviar nuevos pedidos a la base de datos. Estos valores estn incluidos en el DataSet que devuelve el
servicio Web despus de enviar los pedidos modificados a la base de datos. Sin embargo, si fusionamos
este DataSet con nuestro DataSet principal no obtendremos el comportamiento deseado, incluso aunque
llamemos a AcceptChanges inmediatamente a continuacin.
Lo que conseguiremos es que el DataSet principal de la aplicacin cliente contenga los pedidos
pendientes originales con valor generado por ADO.NET para la columna clave de auto incremento cuando
se insertaron, adems de los pedidos devueltos por el servicio Web con los valores generados para esta
columna por la base de datos.
El mtodo Merge se basa en la clave principal del objeto DataTable para hacer corresponder las filas
entre diferentes objetos DataSet. Las filas que queremos combinar no tienen los mismos valores de clave
principal. El mtodo Merge no se da cuenta de que los pedidos del DataSet devuelto por el servicio Web se
corresponden con los pedidos aadidos al DataSet principal. Como resultado el mtodo Merge simplemente
aade las filas del DataSet que recibe del servicio Web al DataSet principal. Este no es el comportamiento
que deseamos, pero afortunadamente existen dos soluciones.
Purgar antes de Merge. Nuestro objetivo es combinar los contenidos de dos DataSet y recuperar los
nuevos valores de OrderID al DataSet resultante. Hasta ahora lo que hemos conseguido es tener pedidos
duplicados, que solo se diferencian en que uno tiene el OrderID generado por ADO.NET al insertar el
pedido en el DataSet y otro el valor de OrderID generado por la base de datos, recuperado por el servicio
Web y enviado a la aplicacin cliente.
Podemos lograr los resultados deseados eliminando los pedidos nuevos del DataSet justo antes de
fusionar el DataSet devuelto por el servicio Web. El siguiente fragmento de cdigo usa el mtodo Select del
objeto DataTable para recorrer las filas cuyo RowState es Added y elimina estas filas antes de fusionar el
DataSet que devuelve el servicio Web.
ClaseServicioWeb objServicio = new ClaseServicioWeb();
DataSet dsPrincipal = objServicio.LeeDataSet();
ModificaContenidoDataSet(dsPrincipal);
DataSet dsCambios = dsPrincipal.GetChanges();
dsCambios = objServicio.EnviaCambios(dsCambios);
//Elimina los nuevos pedidos del DataSet principal antes de la fusin
DataTable tabla = dsPrincipal.Tables[Orders];
foreach(DataRow fila in tabla.Select(, , DataRowState.Added))
tabla.Rows.Remove(fila);
dsPrincipal.Merge(dsCambios);
dsPrincipal.AcceptChanges();

Recorrer el DataSet principal eliminando las inserciones pendientes no es muy elegante, pero resuelve el
problema.
Cambiar la clave principal en sus objetos DataSet. Como ya hemos visto, el problema surge del hecho
de que las filas que queremos combinar no tienen los mismos valores de clave principal. Qu pasa si
cambiamos la clave principal?.
Justo antes de fusionar los dos objetos DataSet podemos cambiar la clave principal de cada uno de los o
objetos DataTable a una columna diferente. Si las filas correspondientes en el DataSet tiene los mismos
valores en esta columna, obtendremos los valores deseados cuando fusionemos los objetos DataSet.
Despus de fusionarlos podemos restablecer la clave principal a su estado original.
Repase sus opciones. Personalmente no me gusta ninguna de las dos soluciones. La solucin que implica
cambiar la clave principal puede resultar compleja, especialmente si la tabla con la que estamos trabajando
tiene objetos DataTable hijos en el DataSet. Si tuviera que elegir una, elegira la que elimina las inserciones
pendientes del DataSet original antes de llamar a Merge.
Una opcin final es evitar el problema estructurando los datos de modo que conozca los valores de la
clave principal antes de enviarlos a la base de datos. Cada vez ms desarrolladores utilizan GUID en las
columnas de clave principal de sus tablas. Si no quiere usar este tipo de datos en su tabla, puede usarlo en
el DataSet para ayudar a que Merge funcione correctamente.
175

ADO.NET 2.0

10.4.2 Cdigo de ejemplo


El cdigo de ejemplo para este captulo incluye ejemplos que muestran cmo actualizar una fila despus
de enviar una actualizacin usando UpdateRowSource.FirstReturnedRecord, UpdateRowSource.
OutputParameters y el evento RowUpdated. Dos ejemplos se relacionan con este tema:
SubmitHierarchicalChanges e IsolateAndReintegrateChanges. Ambos ejemplos siguen la misma premisa
bsica. Un cliente llama para quejarse del primer pedido que recibe. El cdigo borra el primer pedido,
modifica todos los pedidos restantes con un descuento del 10% e introduce dos nuevos pedidos con un
descuento del 25%. Hay cambios en pedidos y el detalles de pedidos. En cada caso las filas aadidas y
modificadas se deben enviar de arriba hacia abajo (pedidos antes que detalles), mientras que los borrados
se deben enviar desde abajo hacia arriba (detalles antes que pedidos).
El ejemplo SubmitHierarchicalChanges enva estos cambios pendientes usando el mtodo Update de
SqlDataAdapter junto con el mtodo Select del DataTable.
El ejemplo IsolateAndReintegrateChanges contiene los mismos cambios, pero tambin contiene pedidos
de otros clientes y sus detalles. Este ejemplo usa el mtodo GetChanges del DataSet para crear un DataSet
que contiene solo los cambios pendientes entes de enviar los cambios, simulando un escenario en el se
pasaran estos cambios a un servicio Web. El ejemplo purga los nuevos pedidos, fusiona el DataSet que
contiene los nuevos valores de OrderID y llama a AcceptChanges.

10.5 Manejar intentos de actualizacin fallidos


ADO.NET est diseado para trabajar con datos desconectados. Cuando un usuario modifica el
contenido de un DataSet no modifica directamente el contenido de la base de datos. ADO.NET almacena
los cambios en el objeto u objetos DataRow modificados. Puede enviar estos cambios a la base de datos
posteriormente usando un objeto SqlDataAdapter.
Pero nada garantiza que los datos en la base de datos no cambiarn despus de que el usuario ejecute
la consulta inicial. La lgica de actualizacin que usan los objetos SqlDataAdapter para enviar los cambios
utiliza concurrencia optimista. Igual que a los optimistas en la vida real, no todo nos va siempre como
esperamos.
Considere el siguiente escenario. El usuario A recupera informacin de cliente de la base de datos en un
DataSet. Este usuario modifica la informacin de un cliente concreto. Entre el momento en que el usuario A
consulta la base de datos y su intento de enviar los cambios, el usuario B modifica el mismo registro en la
base de datos. Como resultado, el intento de actualizacin por el usuario A fracasa.
Muchos desarrolladores creen que esto es un gran inconveniente, pero considere la alternativa. Qu
pasa si el intento de modificacin por el usuario A tiene xito?. El usuario A sobrescribira los cambios
hechos por el usuario B. Ninguno de los dos usuarios se dara cuenta de esta circunstancia.

10.5.1 Planifique los conflictos por adelantado


Si est creando una aplicacin multi usuario que trabaje con datos desconectados y confe en
concurrencia optimista al enviar los cambios, hay posibilidades de que los intentos de actualizacin fallen.
Debe planificar por adelantado y determinar cmo responder su aplicacin a estas situaciones.
Suponga que modifica los contenidos de 10 filas y enva todos los cambios. El SqlDataAdapter enva
correctamente los cambios a las tres primeras filas, pero el intento de enviar los cambios en la fila nmero 4
falla. Cmo debe responder la aplicacin?. Debe el SqlDataAdapter intentar enviar los cambios
pendientes restantes?. Debe informar al usuario?. Dejar que el usuario intente solucionar el problema?.

Propiedad ContinueUpdateOnError de la clase SqlDataAdapter


Puede controlar cmo responde SqlDataAdapter a una actualizacin fallida usando la propiedad
ContinueUpdateOnError. De forma predeterminada esta propiedad tiene el valor false, lo que significa que el
SqlDataAdapter lanzar una excepcin cuando encuentra un intento de actualizacin fallido. Si quiere que el
SqlDataAdapter intente enviar los cambios restantes cambie su valor a true.
Si el valor de esta propiedad es true y uno o ms de los intentos de actualizacin falla, el SqlDataAdapter
no lanzar una excepcin. Cuando encuentra una actualizacin fallida, asigna el valor true a la propiedad
HasErrors del DataRow correspondiente y el mensaje de excepcin a la propiedad RowError del mismo.
Puede comprobar la propiedad HasErrors del DataSet o DataTable despus de llamar al mtodo Update del
SqlDataAdapter para determinar si ha fallado cualquiera de los intentos de actualizacin. Desde luego, esta
comprobacin no ser vlida si el DataSet o DataTable contiene errores antes de la llamada a Update.
Algunos desarrolladores querrn enviar los cambios por medio de una transaccin y consignarlos
solamente si todas las actualizaciones tienen xito. En estos escenarios probablemente querr dejar la
propiedad ContinueUpdateOnError con su valor predeterminado false y cancelar la transaccin si el mtodo
Update lanza una excepcin.
176

Escuela de Informtica del Ejrcito

10.5.2 Informar al usuario de fallos


Es importante informar al usuario si falla un intento de actualizacin. Algunos componentes le ayudarn
a mostrar al usuario qu filas no se han actualizado correctamente. Por ejemplo, si modificar una serie de
filas en un control DataGridView y el intento de enviar estas filas modificadas falla, las filas que no se
actualizan correctamente se marcarn con un icono de advertencia. Si pasa el icono del ratn sobre el icono
la rejilla mostrar una etiqueta de ayuda que muestra el contenido del error.
Si no est usando un DataGridView Windows vinculado puede usar el siguiente cdigo para proporcionar
informacin sobre las filas cuya actualizacin ha fallado.
try{
adaptador.ContinueUpdateOnError = true;
adaptador.update(tabla);
if(tabla.HasErrors){
StringBuilder sb = new StringBuilder();
sb.AppendLine(Las siguientes filas no se actualizaron: );
foreach(DataRow fila in tabla.Rows)
if(fila.HasErrors){
sb.AppendFormat({0}: {1}, fila[ID], fila.RowError);
sb.AppendLine();
}
MessageBox.Show(sb.ToString());
}else{
MessageBox.Show(Todas las actualizaciones han tenido xito);
}
}catch(Exception ex){
MessageBox.Show(Se ha producido un error inesperado: + ex.Message);
}

Pero los usuarios pueden ser exigentes. Si un intento de actualizacin fracasa no les basta con saber
que ha habido un fallo. Por lo general quieren saber por qu ha fallado y como hacer que funcione. Primero
vamos a centrarnos en determinar por qu falla un intento de actualizacin. Para determinar esto
necesitamos saber qu datos ha intentado enviar el usuario, cules eran los datos originales de la fila y cul
es el contenido actual en la base de datos.
Ya sabemos cmo usar el objeto DataRow para acceder a los contenidos originales y actuales de una
fila, pero cmo podemos obtener el contenido de esta fila en la base de datos?.

10.5.3 Obtener el contenido actual de filas en conflicto


Para obtener el contenido actual de las filas deseadas podemos usar el evento RowUpdated de la clase
SqlDataAdapter. El siguiente fragmento de cdigo determina si el SqlDataAdapter ha encontrado algn error
en el intento de actualizacin. Si el error es una excepcin por concurrencia el cdigo usa una consulta
parametrizada para obtener el contenido actual de la fila correspondiente en la base de datos.
Para que el fragmento sea conciso y legible he omitido la definicin de los objetos SqlDataAdapter y
DataSet. La variable AdaptadorConflicto es un SqlDataAdapter que contiene una consulta parametrizada
que recupera el contenido de una fila en la base de datos. El parmetro para esta consulta es la clave
principal de la base de datos, la columna ID. El cdigo usa el valor de esta columna en la fila cuya
actualizacin ha fallado como valor para el parmetro, ejecuta la consulta usando el parmetro y almacena
el resultado en un DataSet separado.
Existe la posibilidad de que la fila ya no exista en la base de datos. El cdigo determina si la consulta ha
recuperado alguna fila y configura adecuadamente la propiedad RowError del DataRow.
private void HandleRowUpdated(object sender OleDbRowUpdatedEventArgs){
if((e.Status == UpdateStatus.ErrorsOccurred) &&
(e.Errors.GetType == typeof(DBConcurrencyException))){
AdaptadorConflicto.SelectCommand.Parameters[0].Value = e.Row[ID];
int filasDevueltas = AdaptadorConflicto.Fill(DataSetConflicto);
if(filasDevueltas == 1)
e.Row.RowError = La fila ha sido modificada por otro usuario;
else
e.Row.RowError = La fila ya no se encuentra en la base de datos;
e.Status = UpdateStatus.Continue;
}
}

El SqlDataAdapter aadir texto automticamente a la propiedad RowError cuando el intento de


actualizacin falle si no se cambia la propiedad Status del parmetro e del evento a Continue o
SkipCurrentRow.
177

ADO.NET 2.0

Este fragmento de cdigo obtiene el contenido actual de las filas correspondientes en la base de datos
en un DataSet separado de modo que se puedan examinar los datos despus de un intento de
actualizacin. De esta forma, ya tenemos todos los datos necesarios para informar al usuario de una forma
detallada.

10.5.4 Si no se consigue a la primera


Decir al usuario que un intento de actualizacin ha fallado es til, pero los usuarios probablemente no
querrn pedir a la base de datos que vuelva a aplicar los mismos cambios a los mismos datos para volver a
intentar su envo. Cmo podemos usar el modelo de objetos de ADO.NET para simplificar el proceso?.
Vamos a volver a pensar por qu fall el intento de actualizacin la primera vez. Los datos que us el
SqlDataAdapter en su comprobacin de concurrencia ya con se corresponden con el contenido actual de la
fila en la base de datos. El SqlDataAdapter usa el contenido original del objeto DataRow en la comprobacin
de concurrencia al actualizar. Hasta que no se refresquen los valores originales en el DataRow no seremos
capaces de enviar los cambios almacenados en el DataRow a la base de datos, no importa cuantas veces
llamamos al mtodo Update del SqlDataAdapter.
Si podemos cambiar el contenido original de un objeto DataRow sin perder el actual podremos enviar
con xito los cambios almacenados en la fila, asumiendo que los contenidos de la fila en la base de datos
no vuelven a cambiar.

Importar los nuevos valores originales en un DataRow


Ya ha aprendido cmo usar el mtodo Merge de la clase DataSet para combinar el contenido de dos
objetos DataSet. Si un mtodo Merge detecta que dos filas tienen el mismo valor de clave principal combina
el contenido de las dos filas en una. El mtodo Merge permite especificar si quiere preservar los cambios
almacenados en el DataSet.
En el ejemplo anterior capturamos el evento RowUpdated del objeto SqlDataAdapter. Si la fila actual no
se actualiza correctamente el cdigo recupera el contenido actual de la fila en la base de datos en un nuevo
DataSet llamado DataSetConflicto. Suponiendo un DataSet principal llamado DataSetPrincipal podemos
usar la siguiente lnea de cdigo para fusionar los contenidos de DataSetConflicto en DataSetPrincipal.
DataSetPrincipal.Merge(DataSetConflicto, true);

Al pasar true como segundo parmetro se indica al DataSet principal que conserve sus valores actuales
cuando se combinen las filas. El mtodo sustituye solo los valores originales de los objetos DataRow por los
datos del DataSet de conflicto.
Con estos datos nuevo original en el DataSet principal podemos intentar enviar los cambios restantes a
la base de datos. Si el contenido de las filas correspondientes no ha cambiado de nuevo desde que los
hemos recuperado en el evento RowUpdated las actualizaciones tendrn xito.
Este procedimiento es uno que se puede utilizar para volver a enviar los cambios pendientes. Si se utiliza
de forma automtica el efecto que se consigue es el mismo que utilizar solamente la clave principal en la
lgica de actualizacin se sobrescriben los cambios hechos por el primer usuario. El objetivo del ejemplo
anterior es demostrar cmo se puede acceder a los valores actuales y originales del DataRow as como a
los valores actuales en la base de datos. Dependiendo de los objetivos de su aplicacin, es posible que le
interese preguntar al usuario para determinar cmo proceder.
Tenga en mente que no puede obtener informacin ms actual sobre una fila que ya no existe. Si un
intento de actualizacin falla porque la fila ya no existe en la base de datos no puede usar este mtodo para
actualizar los valores originales en la fila. Si quiere volver a aadir los contenidos actuales del objeto
DataRow a la base de datos puede llamar al mtodo SetAdded del DataRow para cambiar su RowState a
Added. Cuando utilice el SqlDataAdapter para enviar el cambio, intentar insertar la fila en la base de datos.

10.5.5 Aplicacin de ejemplo


El cdigo para este captulo incluye un ejemplo llamado HandleFailedUpdateAttempts. Este ejemplo
intenta enviar una serie de cambios a la base de datos. Simula cambios por otro usuario para causar que
varios de los intentos de actualizacin generen fallos de concurrencia.
El cdigo usa el evento RowUpdated de la clase SqlDataAdapter para detectar fallos por concurrencia.
Cuando el cdigo detecta un fallo por concurrencia aade el DataRow a una lista de filas cuya actualizacin
ha fallado y consulta a la base de datos el contenido actual de la fila correspondiente en la base de datos.
Una vez que el cdigo ha intentado enviar todos los cambios pendientes recorre todos los DataRow con
actualizacin fallida, mostrando su contenido y su RowState, as como el contenido de la fila
correspondiente en la base de datos. Si existe la fila correspondiente en la base de datos el cdigo usa el
mtodo LoadDataRow, similar a Merge, para sobrescribir los valores originales del DataRow por los
recuperados de la base de datos, y vuelve a enviar el cambio pendiente.
178

Escuela de Informtica del Ejrcito

10.6 Transacciones distribuidas


Ya hemos visto cmo usar la clase SqlTransaction de ADO.NET. Puede usar un objeto SqlTransaction
para agrupar los resultados de varias consultas hechas sobre una conexin como una nica unidad de
trabajo. El problema aparece cuando las consultas que queremos agrupar actan sobre diferentes bases de
datos.
Puede comenzar una transaccin sobre cada conexin. Si determina que una de las operaciones falla
puede volver atrs ambas transacciones; en caso contrario puede consignarlas las dos. Esto parece
sencillo.
Pero vamos a suponer que consigna los cambios en la primera transaccin, y justo antes de consignar
los cambios de la segunda transaccin la conexin se rompe. La base de datos detectar la prdida de
conexin y volver atrs la transaccin inmediatamente. Como resultado, se habrn consignado los
cambios de la primera transaccin, pero no los de la segunda.
Usar una transaccin sobre cada conexin no es una solucin completamente fiable para el problema.
Para que el sistema sea ms fiable la aplicacin necesita trabajar ms de cerca con ambas bases de datos
para coordinar las transacciones y resolver problemas como el que acabamos de describir. Necesita una
transaccin que abarque varias conexiones con bases de datos. Una transaccin de este tipo normalmente
se conoce como transaccin distribuida.
Hay libros enteros dedicados al procesamiento de transacciones o a COM+. Obviamente, no puedo
tratar todos los temas en profundidad en las siguientes secciones. Simplemente proporcionar una
introduccin a los aspectos bsicos del procesamiento de transacciones y el trabajo con COM+.

10.6.1 Coordinadores de transacciones y administradores de recursos


En una transaccin distribuida estn implicados dos componentes principales: el administrador de
recursos y el coordinador de transacciones. Un administrador de recursos realiza el trabajo deseado, ya sea
modificar el contenido de una base de datos o leer un mensaje de una cola, e informa si ha sido capaz de
completarlo.
El coordinador de transacciones se comunica con los administradores de recursos que participan en la
transaccin y controla el estado actual de la transaccin. Si un administrador de recursos indica que se ha
producido un error el coordinador de transacciones recibe el mensaje e informa a los otros administradores
de recursos para que cancelen el trabajo realizado en la transaccin. Si todos los administradores de
recursos informan de que han completado sus tareas con xito el coordinador de transacciones dice a todos
ellos que consignen el resultado de sus tareas.

Consignacin de dos fases


Cada uno de los administradores de recursos implementa lo que se conoce como consignacin en dos
fases. El coordinador de transacciones dice a cada administrador de recursos que prepare los cambios
realizados durante el tiempo de vida de la transaccin. Esta es solo la primera parte del proceso. El
administrador de recursos an no ha consignado los cambios realmente. Solo se ha preparado para
hacerlo.
Una vez que todos los administradores de recursos indican que estn listos para consignar los cambios
el coordinador de transacciones ordena a todos que lo hagan. Si uno o ms de los administradores de
recursos indican que no han podido preparar los cambios el coordinador de transacciones indicar a cada
administrador de recursos que cancele el trabajo realizado por la transaccin.
Volvamos al problema anterior. Cuando el coordinador de transacciones pide a los administradores de
recursos que se preparen para consignar los cambios, ambos indican que estn preparados. El coordinador
enva un mensaje al primer administrador para que consigne los cambios, pero se produce un error fatal,
como un corte de energa, antes de que se pueda comunicar con el segundo administrador de recursos.
Qu pasa ahora?.
Corresponde al coordinador de transacciones y el segundo administrador de recursos resolver la
transaccin. Cada componente es responsable de mantener informacin sobre el estado de la transaccin.
El coordinador de transacciones debe ser capaz de recuperarse del fallo, determinar que la transaccin an
est pendiente y contactar con el administrador de recursos correspondiente para resolverla.
El administrador de recursos debe ser capaz de consignar todos los cambios que prepar en la primera
fase del proceso de consignacin. Supongamos que el corte de energa que puso en duda la resolucin de
la transaccin se produjo en la mquina donde est la base de datos utilizada en la segunda parte de la
transaccin. El sistema de base de datos debe recuperarse del fallo, determinar que la transaccin an est
pendiente, proporcionar la capacidad de consignar estos cambios y resolver la transaccin con el
coordinador.
179

ADO.NET 2.0

Como puede suponer, es muy complicado desarrollar un coordinador de transacciones o un


administrador de recursos.

10.6.2 Transacciones distribuidas en .NET


Microsoft inicialmente introdujo su coordinador de transacciones como tecnologa de soporte del sistema
operativo Windows como aadido a NT4. Esta funcionalidad ahora est integrada en el sistema operativo
Windows como parte de los servicios de componentes.
La belleza de esta arquitectura es que solo es necesaria una pequea cantidad de cdigo para
aprovechar las caractersticas transaccionales de los servicios de componentes. Se escribe el cdigo como
normalmente. Se dice a los servicios de componentes si se debe consignar o abortar la transaccin, y l se
ocupa de todo el trabajo necesario para gestionar una transaccin distribuida.

10.6.3 Soporte de base de datos para transacciones distribuidas


Para usar transacciones distribuidas con su base de datos, el sistema de base de datos debe tener un
administrador de recursos que se pueda comunicar con el coordinador de transacciones incluido en los
servicios de componentes.
Algunos sistemas de bases de datos, como SQL Server y Oracle, tienen administradores de recursos
que soportan esta funcionalidad, pero muchos otros no. Antes de empezar a planear una aplicacin que
confe en transacciones distribuidas asegrese de que est utilizando una base de datos que tenga un
administrador de recursos que implemente la consignacin en dos fases y pueda comunicarse con los
servicios de componentes.
En realidad, las transacciones distribuidas no se limitan a bases de datos. Los servicios de colas de
mensajes de Microsoft, por ejemplo, permiten enviar y recibir mensajes como parte de una transaccin
distribuida.

10.6.4 Crear sus componentes


En la versin 1.x de ADO.NET para utilizar transacciones distribuidas haba que emplear la clase
ServicedComponent del espacio de nombres System.EnterpriseServices. El tedioso proceso de registrar un
componente con los servicios de componentes obligaba a utilizar varias herramientas de lnea de comando
y aadir atributos tanto al ensamblado como a la clase.
Afortunadamente, en la versin 2.0 del marco de trabajo .NET el trabajo con transacciones distribuidas
es mucho ms sencillo gracias al espacio de nombres System.Transactions. Este espacio de nombres
incluye mltiples clases transaccin que puede usar para crear transacciones sin necesidad de llamar a
herramientas de lnea de comando o aadir atributos a sus ensamblados y clases. Vamos a ver un par de
las clases ms usadas de este espacio de nombres.

TransactionScope
La clase TransactionScope es la ms sencilla de utilizar. Si el proveedor de datos .NET que est
utilizando soporta System.Transactions, al abrir una conexin dentro del alcance de un TransactionScope
automticamente se enrolar la conexin en la transaccin. Al llamar al mtodo Commit de
TransactionScope se consigna el trabajo realizado en la transaccin. Si se llama al mtodo Dispose de
TransactionScope, ya sea explcitamente o utilizando un bloque using, el trabajo sin consignar en la
transaccin se anula automticamente.
El siguiente ejemplo muestra cmo puede trabajar sobre varias conexiones dentro de un
TransactionScope y consignar el trabajo:
//using System.Transactions;
using(TransactionScope txn = new TransactionScope()){
using(SqlConnection cn1 = new SqlConnection(cadcon1){
cn1.Open();
//Trabaja con la conexin
cn1.Close();
}
using(SqlConnection cn2 = new SqlConnection(cadcon2)){
cn2.Open();
//Trabaja con la conexin
cn2.Open();
}
//Consigna el trabajo realizado sobre la transaccin
txn.Commit();
}

180

Escuela de Informtica del Ejrcito

CommitableTransaction
La clase CommitableTransaction es similar a TransactionScope, salvo por una diferencia principal debe
enrolar explcitamente las conexiones que quiere que participen en la transaccin. Para enrolar una
conexin en una transaccin CommitableTransaction pase el objeto transaccin al mtodo
EnlistTransaction.
Para consignar o cancelar el trabajo realizado en una CommitableTransaction llame a los mtodos
Commit y Rollback respectivamente. Si se llama al mtodo Dispose de CommitableTransaction, implcita o
explcitamente, cualquier trabajo sin consignar se cancela automticamente.
El siguiente ejemplo muestra cmo puede realizar trabajo sobre varias conexiones dentro de un
CommitableTransaction y consignar el trabajo.
//using System.Transactions;
using(SqlConnection con1 = new SqlConnection(cadcon1),
con2 = new SqlConnection con2(cadcon2)){
con1.Open();
con2.Open();
using(CommitableTransaction txn = new CommitableTransaction()){
con1.EnlistTransaction(txn);
con2.EnlistTransaction(txn);
//Trabaja sobre las conexiones
//Consigna el trabajo
txn.Commit();
}
//Desasocia las conexiones de la transaccin
con1.EnlistTransaction(null);
con2.EnlistTransaction(null);
con1.Close();
con2.Close();
}

SQL Server 2005 y transacciones promocionables


Las transacciones distribuidas son potentes, pero necesitan una sobrecarga mayor que las transacciones
simples. Si en una System.Transaction solo participa una transaccin, en teora la transaccin no tiene que
ser distribuida. Puede hacer que la base de datos trabaje con un transaccin local a la base de datos, como
una SqlTransaction. Sin embargo, la base de datos puede necesitar promocionar la transaccin a distribuida
si otro administrador de recursos participa en la misma.
SQL Server 2005 y el proveedor de datos .NET para SQL que incluye la versin 2.0 del marco de trabajo
.NET soportan transacciones promocionables. Si enrola una SqlConnection que se comunica con SQL
Server 2005 en una transaccin de System.SqlTransactions y este SqlConnection es el primer
administrador de recursos implicado en la transaccin, SQL Server realizar el trabajo en una transaccin
local, lo que resulta en un mejor rendimiento. Si otro administrador de recursos se enrola en la transaccin
SQL promover la transaccin local a distribuida.

10.6.5 Ejemplos
El
cdigo
para
este
captulo
incluye
dos
ejemplos
de
System.Transactions:
SystemTransactions_TransactionScope y SystemTransactions_CommitableTransaction. Como implican sus
nombres, el primer ejemplo usa la clase TransactionScope y el segundo la clase CommitableTransaction.

10.7 Consultas por lotes


En la versin 1.0 del marco de trabajo .NET las diferentes clases DataAdapter solo podan enviar un
cambio pendiente a la vez a la base de datos. La versin 2.0 de ADO.NET se enfrenta a los cambios
mltiples por medio de la propiedad UpdateBatchSize de las clases DataAdapter. El valor predeterminado
de esta propiedad es 1, lo que hace que se enven los cambios pendientes a la base de datos fila por fila. Si
quiere enviar los cambios pendientes a la base de datos por lotes, cambie el valor de la propiedad al
tamao deseado. Si el valor es 0 se enviarn todos los cambios en un solo lote.
No todos los DataAdapter soportan esta caracterstica. En la versin 2.0 del marco de trabajo .NET
SqlDataAdapter y OracleDataAdapter soportan actualizaciones por lotes, pero OdbcDataAdapter y
OleDbDataAdapter no.
Como el DataAdapter no conoce de forma inherente lo que va a hacer la lgica en InsertCommand,
UpdateCommand y DeleteCommand la tarea de reunir todas las consultas en un lote a la vez que se
181

ADO.NET 2.0

mantiene suficiente lgica interna para determinar qu intentos de actualizacin fallan y cules tienen xito
es ms complicada.
El SqlDataAdapter aade marcadores de llamada a procedimiento remota (RPC) para separar las
consultas del lote. Este mtodo ayuda al SqlDataAdapter a identificar los resultados de las consultas desde
el servidor, e impide que el SqlDataAdapter tenga que cambiar los nombres de los parmetros dentro del
lote.
El OracleDataAdapter tiene que hacer ms trabajo, porque Oracle no soporta el retorno del nmero de
filas afectadas por consultas individuales dentro de un lote. OracleDataAdapter aade al lote parmetros de
salida que devuelven el nmero de filas afectadas por las consultas individuales. Tambin modifica el
contenido de la consulta por lotes para asegurar que los nombres de parmetros de entradas diferentes
dentro del lote no se repiten.
En otras palabras, pedir que un DataAdapter enve actualizaciones por lotes en lugar de fila a fila no es
algo trivial.

10.7.1 Transacciones y actualizaciones por lotes


Simplemente dar un valor mayor que 1 a la propiedad UpdateBatchSize de un DataAdapter que soporte
actualizaciones por lotes puede mejorar el rendimiento, pero no es la forma correcta de intentar mejorar el
rendimiento de su aplicacin.
Cuando se enva un lote de consultas a una base de datos SQL Server el servidor, de forma
predeterminada, maneja cada consulta por separado y escribe los cambios en el disco antes de ejecutar la
siguiente. Es decir, se sigue pidiendo al servidor que ejecute tantas operaciones de escritura en disco como
consultas contenga el lote.
Un medio ms eficiente es enviar los cambios en una transaccin. De esta forma, el servidor SQL Server
puede responder ms rpidamente a los cambios y consignarlos en el disco al final de la operacin.
Tambin tiene sentido envolver las actualizaciones por lotes en una transaccin porque la gestin de
fallos puede ser ms difcil en el modo de actualizacin por lotes. Si enva cambios pendientes en modo fila
a fila e intenta enviar un cambio que viola una restriccin de la base de datos, es obvio a qu DataRow le
corresponde la excepcin. En modo de actualizacin por lotes determinar el o los DataRow que ha causado
el fallo es ms complicado. La excepcin est disponible en la propiedad Errors de RowUpdatedEventArgs,
pero el DataRow correspondiente no est disponible. DBConcurrencyException es una excepcin a la regla
porque expone una propiedad Row y un mtodo CopyToRows que puede usar para localizar el o los
DataRow que la han causado.

10.7.2 Valor adecuado para UpdateBatchSize


Por un momento vamos a imaginar un escenario en el que tiene 100.000 cambios pendientes dentro de
un DataTable y est trabajando con SQL Server. Cuando ve el tiempo que le toma enviar todos los cambios
pendientes se pregunta cmo puede hacer que el cdigo se ejecute con ms rapidez.
Sabe que SqlDataAdapter soporta la actualizacin por lotes y como ha ledo la seccin anterior, envuelve
los cambios en un SqlTransaction, asigna 10 a la propiedad UpdateBatchSize, ejecuta pruebas de
rendimiento y observa que su escenario se ejecuta ms deprisa. Cambia UpdateBatchSize a 100, vuelve a
ejecutar las pruebas y ve que es incluso ms rpido.
Como observa que al aumentar el tamao del lote mejora el rendimiento cambia UpdateBatchSize a 0,
un valor especial que provoca que el SqlDataAdapter enve todos los cambios en un solo lote. Al volver a
ejecutar las pruebas el rendimiento es malsimo, o se provoca una excepcin qu ha pasado?.
En realidad, nada ha ido mal. Aumentar el tamao del lote aumenta el rendimiento, pero enviar 100.000
filas a la vez puede no funcionar tan bien como enviar una a una. En algn punto, construir el lote y
gestionar los resultados se hace demasiado complejo. Por este motivo, no conviene usar el valor 0 para
UpdateBatchSize.
Las pruebas muestran que el tamao ptimo de lote est entre 100 y 1000 filas. Muchos factores pueden
influir sobre el escenario, como, pero no solo, el tamao de fila, los controles de concurrencia utilizados en
la lgica de actualizacin y restricciones dentro de la base de datos. Lo mejor es que haga sus propias
pruebas.

10.7.3 Eventos
El evento RowUpdating de SqlDataAdapter y OracleDataAdapter se comporta igual en el modo de
actualizacin de una fila que en el modo de actualizacin por lotes, con un par de excepciones menores. En
ambos casos el evento se lanza antes de enviar los cambios pendientes en cada DataRow. El DataRow que
contiene el cambio, el Command empleado para enviarlo y la informacin de TableMapping estn
182

Escuela de Informtica del Ejrcito

disponibles. Sin embargo, si enva los cambios en el modo por lotes, el evento RowUpdating se lanza una
vez por fila, pero antes de enviar el lote.
El evento RowUpdated se comporta de forma diferente en el modo de actualizacin por lotes. El evento
no se lanza fila a fila, sino una sola vez despus de enviar el lote. Puede acceder a los objetos DataRow del
lote por medio del mtodo CopyToRows de RowUpdatedEventArgs; sin embargo el Command no est
disponible. El nmero total de filas modificado por el lote est disponible, pero el nmero de filas
modificadas por cada entrada individual del lote no.

10.7.4 Refrescar filas


El refresco de filas despus de una actualizacin es ms complejo en modo por lotes. Mientras que las
clases SqlDataAdapter y OracleDataAdapter soportan el refresco del DataRow por medio de parmetros de
salida, ninguna de las clases permite este refresco por medio de una consulta SELECT bsica. De hecho,
estas clases lanzarn una InvalidOperationException si en el modo de actualizacin por lotes encuentran un
Command cuyo UpdatedRowSource tiene el valor FirstReturnedRecord o Both.

10.7.5 Ejemplos
El cdigo para este captulo incluye dos ejemplos de actualizacin por lotes: BatchedUpdates y
BatchedUpdates_RefreshAfterUpdate. Estos ejemplos muestran cmo enviar actualizaciones por lotes y
cmo refrescar objetos DataRow despus de actualizaciones a la vez que se envan cambios,
respectivamente.

10.8 Copia masiva SQL


Las actualizaciones por lotes estn bien, pero SQL Server ofrece un medio mucho ms eficiente para
enviar nuevas filas a la base de datos. Las caractersticas del protocolo de copia masiva de SQL Server,
conocido como BCP han estado disponibles desde los primeros das de este servidor.
BCP es potente y extremadamente rpido porque SQL Server permite insertar por este medio filas
saltndose operaciones de servidor, como el lanzamiento de disparadores o imposicin de restricciones. Sin
embargo, muchos desarrolladores han encontrado BCP frustrante, un poco arcaico, o ambos, porque obliga
a poner los datos en archivos con un formato especial.
ADO.NET 2.0 introduce una nueva clase, SqlBulkCopy, diseada especficamente para ayudar a los
desarrolladores a acceder a las caractersticas BCP de SQL Server. En lugar de tener que construir
archivos con un formato especial puede usar las clases en memoria de ADO.NET con las que ya est
familiarizado. El mtodo sobrecargado WriteToServer de SqlBulkCopy acepta un DataTable, un array de
DataRow o incluso un DataReader.
La clase SqlBulkCopy funciona tanto con SQL Server 2005 como con SQL Server 2000, as como con
SQL Server Express y MSDE.

10.8.1

Crear un objeto SqlBulkCopy

El constructor de la clase SqlBulkCopy est sobrecargado. Puede proporcionar o un objeto


SqlConnection o una cadena de conexin que permitan que el objeto se comunique con una base de datos
SQL Server. Tambin puede especificar un valor, o combinacin de valores, de la enumeracin
SqlBulkCopyOptions, que describiremos en breve.
Finalmente, puede proporcionar un objeto SqlTransaction si quiere que el objeto SqlBulkCopy realice su
trabajo sobre una transaccin que posteriormente se pueda consignar o cancelar. Si no quiere usar un
SqlTransaction pero necesita usar el constructor que espera uno use null para este parmetro.
SqlBulkCopy bcp;
String cadcon;
cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind +
Integrated Security=true;
SqlConnection conexin = new SqlConnection(cadcon);
SqlBulkCopyOptions opciones = SqlBulkCopyOptions.Default;
SqlTransaction transaccin = null;
//Pasa una cadena de conexin. El SqlBulkCopy crea y usa una conexin
bcp = new SqlBulkCopy(cadcon);
//Proporciona una cadena de conexin y valor de SqlBulkCopyOptions
bcp = new SqlBulkCopy(cadcon, opciones);
//Proporciona un objeto conexin
bcp = new SqlBulkCopy(conexin);
//Pasa un objeto conexin, un valor de SqlBulkCopy y una transaccin (o null)
bcp = new SqlBulkCopy(conexin, opciones, transaccin);

183

ADO.NET 2.0

10.8.2

Escribir datos en el servidor

La clase SqlBulkCopy escribe datos en el servidor cuando se llama al mtodo WriteToServer. Como el
constructor de la clase, este mtodo est sobrecargado para ayudarle a manejar diferentes escenarios.
Necesitar configurar la propiedad DestinationTableName del objeto SqlBulkCopy antes de llamar al mtodo
WriteToServer. Discutiremos esta propiedad poco ms adelante.
Puede utilizar una aproximacin basada en flujos pasando un DataReader al mtodo WriteToServer. El
objeto SqlBulkCopy escribir en el servidor todas las filas disponibles en el DataReader. Este mtodo no
est limitado a SqlDataReader, puede usar cualquier clase que implemente IDataReader
OdbcDataReader, OracleDataReader, OleDbDataReader, etc.
La clase SqlBulkCopy tambin puede manejar datos almacenados en un DataTable. Si pasa solo el
DataTable el objeto SqlBulkCopy escribir en el servidor SQL Server todas las filas no borradas. Tambin
puede proporcionar un valor, o combinacin de valores, de la enumeracin DataRowState si quiere escribir
solo los objetos DataRow que tienen un estado o estados concretos.
Finalmente, la clase SqlBulkCopy ofrece una versin del mtodo WriteToServer que acepta un array de
DataRow. Esta sobrecarga es til si quiere escribir solo los DataRow que cumplen ciertos criterios que se
puedan expresar por medio del mtodo Select de DataTable.

10.8.3

Correspondencia de datos con la tabla de destino

De forma predeterminada la clase SqlBulkCopy implcitamente hace corresponder las columnas del
DataReader o DataTable con la tabla de destino. Sin embargo, no determina implcitamente el nombre de la
tabla de destino. Debe configurar esta propiedad explcitamente antes de llamar al mtodo WriteToServer.
La clase SqlBulkCopy califica el valor que se le proporciona, encerrndolo entre corchetes, de modo que no
tiene que preocuparse si el nombre de la tabla contiene espacios.
Se ha mencionado que la clase SqlBulkCopy hace corresponder las columnas implcitamente. Este
proceso hace corresponder las columnas del DataReader o DataTable con la tabla de destino por ordinal.
En otras palabras, la primera columna del origen con la primera del destino.
Tambin puede hacer corresponder explcitamente columnas por medio de la propiedad
ColumnMappings de la clase SqlBulkCopy. El mtodo Add est sobrecargado para permitir muchas
opciones de correspondencia de columnas. Puede especificar el nombre o el ordinal tanto de la columna de
origen como de la columna de destino. Incluso puede mezclar y hacer corresponder nombres con ordinales.

10.8.4

Enumeracin SqlBulkCopyOptions

Algunos de los constructores de la clase SqlBulkCopy permiten proporcionar un valor, o combinacin de


valores, de la enumeracin SqlBulkCopyOptions. Esta enumeracin le da un control ms fino sobre el
comportamiento que se produce en la llamada a WriteToServer. La tabla 11.2 muestra las diferentes
opciones.
Tabla 11. 2: Miembros de la enumeracin SqlBulkCopyOptions

Constante
Default

Valor
0

KeepIdentity

CheckConstraints

TableLock

KeepNulls

FireTriggers

16

UseInternalTransactions

32

Descripcin
Especifica que no se utilizar ninguna opcin. Como implica su nombre es el
comportamiento predeterminado si usa un constructor que no incluya un parmetro
para indicar otra.
Especifica que los valores proporcionados para columnas identidad se deben
escribir en las nuevas filas; en caso contrario se generan nuevos valores en la base
de datos.
Especifica que se comprueban las restricciones de clave externa al importar datos;
en caso contrario los datos entrantes se validan usando restricciones de clave
externa.
Especifica que SQL Server debe mantener un bloqueo de tabla durante la
operacin; en caso contrario se utiliza bloqueo de fila.
Especifica que los valores nulos procedentes del origen se aplican a las nuevas
filas incluso si la columna correspondiente en la tabla de destino tiene un valor
predeterminado; en caso contrario se usa el valor predeterminado.
Especifica que la creacin de nuevas filas lanza los disparadores; en caso contrario
no se lanzan.
Especifica que el trabajo en cada lote se envuelve en una transaccin implcita; en
caso contrario no se crea transaccin.

Como regla general, la opcin Default obtiene el mejor rendimiento. La excepcin es la opcin
KeepNulls. Especificar KeepNulls mejorar el rendimiento de ligeramente porque evita que SQL Server
tenga que comprobar y evaluar valores predeterminados de las columnas.

184

Escuela de Informtica del Ejrcito

10.8.5

Ejemplo

El cdigo para este captulo incluye cdigo funcional que usa la clase SqlBulkCopy en el procedimiento
BulkCopy.

10.9 Objetos DataSet y Transacciones


Muchos de los escenarios discutidos hasta ahora en este captulo han implicado transacciones.
Desafortunadamente, en la versin 2.0 de ADO.NET, la clase DataSet no ofrece soporte para
transacciones.
Si enva cambios pendientes por medio de un SqlDataAdapter ste llamar implcitamente al mtodo
AcceptChanges de los objetos DataRow cuyos cambios se han enviado con xito. Si la llamada a Updated
estaba implicada en una transaccin, ya sea SqlTransaction o una System.Transactions, y la transaccin se
anula la base de datos descartar los cambios hechos durante la transaccin, pero el DataSet no volver al
estado anterior a la misma. Sin embargo, hay un par de formas sencillas de enfrentar este escenario.
El mtodo general es almacenar los contenidos del DataSet en el momento de comenzar la transaccin.
Si cancela la transaccin, descarte el DataSet usado para realizarla y use el que contiene an los cambios
pendientes.
La forma de almacenar el contenido del DataSet queda para el desarrollador. La forma ms simple es
usar el mtodo Copy para copiar el DataSet en otro DataSet en memoria. Si prefiere guardarlo en disco
para ahorrar memoria use el mtodo WriteXml, pero asegrese de especificar XmlWriteMode.Diffgram de
modo que cuando vuelva a cargar los datos por medio de ReadXml se conserven los cambios pendientes.
Otra opcin es serializar y deserializar el DataSet mediante programacin.

10.9.1 Ejemplo
El cdigo para este captulo incluye un ejemplo de trabajo con objetos DataSet y transacciones llamado
DataSetAndTransactions, que crea una copia de un DataTable en memoria antes de enviar actualizaciones
sobre una transaccin. El cdigo cancela el trabajo de la transaccin, recupera el DataTable en memoria y
vuelve a enviar los cambios para demostrar que esta forma de trabajo funciona.

185

ADO.NET 2.0

186

Escuela de Informtica del Ejrcito

11 Trabajar con datos XML


Este captulo tratar cmo usar las caractersticas XML de ADO.NET, principalmente las que permiten
leer y escribir datos en formato XML. A lo largo del camino aprender cmo usar el nuevo tipo de datos XML
de SQL Server 2005 desde ADO.NET, incluyendo cmo acceder al contenido de columnas XML, usar
consultas XPath para filtrar los resultados devueltos y procesar los resultados usando XQuery.
Antes de comenzar, una disculpa. Este captulo discutir cmo puede usar XML y la tecnologas
relacionadas, como esquemas XML, expresiones XPath y XQuery desde ADO.NET. Pero este captulo no
est pensado para ensearle XML.
Aunque este captulo no es una gua en profundidad de XML y tecnologas relacionadas, mostrar parte
de la potencia de estas tecnologas. Este captulo asume que conoce lo bsico del trabajo con XML, XSL,
XPath y XQuery, pero este conocimiento no es imprescindible. Incluso aunque no sepa deletrear XML este
captulo puede ayudarle a desarrollar un aprecio por XML y alimentar su apetito por aprender ms.

11.1 Llenar el hueco entre XML y acceso a datos


XML sigue siendo una de las tecnologas calientes en el mundo del desarrollo. Ms y ms
desarrolladores encuentran que pasar datos como XML entre diferentes capas de servicio o diferentes
aplicaciones proporciona una tremenda flexibilidad y permite la interoperabilidad entre sistemas dispares.
Puede usar un documento XML para almacenar datos como informacin sobre una serie de clientes y el
historial de pedidos de cada cliente. En algunos aspectos un documento XML es similar a un DataSet de
ADO.NET; ambos pueden almacenar mltiples elementos de informacin en una estructura bien definida.
Uno de los objetivos del equipo de desarrollo de ADO.NET fue cubrir el hueco que exista entre las
tecnologas de acceso a datos anteriores y XML, de modo que resulte sencillo integrar ambas tecnologas.
Cargar datos desde un documento XML en un DataSet y viceversa es sencillo. Si est trabajando con SQL
Server 2005 2000 puede recuperar datos desde la base de datos como XML y almacenar los resultados
en un DataSet ADO.NET o en un documento XML. Tambin puede sincronizar un DataSet y un documento
XML de modo que los cambios en uno sean visibles en el otro.

11.2 Leer y escribir datos XML


11.2.1 Mtodos XML de la clase DataSet
La clase DataSet tiene una serie de mtodos que le permiten examinar su contenido como XML, as
como cargar datos XML en el DataSet.

Mtodo GetXml
El ms simple de los mtodos XML es GetXml, que puede usar para extraer el contenido de un DataSet
y ponerlo en una cadena. El mtodo GetXml es simple, casi demasiado. No est sobrecargado y no acepta
parmetros.
El siguiente fragmento de cdigo recupera pedidos y detalles de pedido para un cliente y lo muestra en
pantalla en forma de XML. Para recuperar los datos utiliza una funcin LeeInfoPedidoCliente, que
reutilizaremos ms adelante.
static void Main(string[] args) {
DataSet ds = LeeInfoPedidoCliente("GROSR");
Console.WriteLine(ds.GetXml());
}
static private DataSet LeeInfoPedidoCliente(string idCliente) {
string cadcon = @"Data Source=.\sqlexpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT OrderID, CustomerID, OrderDate FROM Orders " +
"WHERE CustomerID = @CustomerID;" +
"SELECT OrderID, ProductID, Quantity, UnitPrice " +
"FROM [Order Details] WHERE OrderID IN " +
"(SELECT OrderID from Orders WHERE CustomerID = @CustomerID)";
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.SelectCommand.Parameters.AddWithValue("@CustomerID", idCliente);
adaptador.TableMappings.Add("Table", "Pedidos");
adaptador.TableMappings.Add("Table1", "Detalles");
DataSet ds = new DataSet();
adaptador.Fill(ds);
return ds;
}

187

ADO.NET 2.0

Mtodos WriteXml y ReadXml


Como indiqu anteriormente el mtodo GetXml est bastante limitado. El mtodo WriteXml de la clase
DataSet es ms robusto. Est sobrecargado de modo que puede escribir el contenido de su DataSet en un
archivo o en un objeto que implemente los interfaces de Stream, TextWriter o XmlWriter.
El mtodo WriteXml tambin permite especificar valores de la enumeracin XmlWriteMode para
aumentar el control sobre la salida. Puede usar esta enumeracin para elegir si incluir la informacin de
esquema del DataSet y si escribir el contenido del DataSet en formato diffgram, que trataremos en breve.
El siguiente fragmento de cdigo muestra cmo usar el mtodo WriteXml para escribir el contenido de un
DataSet, incluido su esquema, en un archivo y muestra el contenido del archivo. Prefiero mostrar los
contenidos de XML en Internet Explorer en lugar de en la consola porque el formato es ms inteligible. El
fragmento muestra el XML en un formulario Windows que incluye un control WebBrowser, y por tanto
necesita una referencia a System.Windows.Forms. Este cdigo utiliza la funcin LeeInfoPedidoCliente del
ejemplo anterior.
[STAThread]
static void Main(string[] args) {
DataSet ds = LeeInfoPedidoCliente("GROSR");
string ruta = @"C:\Datos\Datos.xml";
ds.WriteXml(ruta, XmlWriteMode.WriteSchema);
MuestraXMLEnFormulario(ruta, "DataSet.WriteXml");
}
static void MuestraXMLEnFormulario(string ruta, string ttulo) {
using(Form frm = new Form()){
frm.Width = 800;
frm.Height = 600;
frm.Text = ttulo;
WebBrowser navegador = new WebBrowser();
navegador.Dock = DockStyle.Fill;
navegador.Visible = true;
navegador.Navigate(ruta);
frm.Controls.Add(navegador);
frm.ShowDialog();
}
}

El mtodo Main del ejemplo anterior se debe marcar como de una sola hebra para poder utilizar el
control WebBrowser, por lo que est marcado con el atributo [STAThread].
La clase DataSet tiene un mtodo ReadXml sobrecargado que puede usar para cargar datos XML. Este
mtodo es bsicamente la inversa de WriteXml. Puede leer datos XML desde un archivo o desde un objeto
que implemente los interfaces de Stream, TextReader o XmlReader. Tambin puede controlar cmo lee el
mtodo los contenidos de los datos XML proporcionando valores de la enumeracin XmlReadMode.

Mtodo WriteXmlSchema, ReadXmlSchema e InferXmlSchema


La clase DataSet tambin expone los mtodos ReadXmlSchema y WriteXmlSchema que permiten leer y
escribir solo la informacin de esquema del DataSet. Cada uno de los mtodos permite el trabajo con
archivos y objetos que implementen los interfaces de Stream, TextReader o XmlReader.
El mtodo ReadXmlSchema puede cargar informacin de esquema desde un documento de esquema
que siga los estndares XSD XDR. Tambin puede leer un esquema en lnea de un documento XML.
Otro mtodo que expone la clase DataSet es InferXmlSchema, que funciona igual que ReadXmlSchema,
salvo que acepta un segundo parmetro. Puede proporcionar en el segundo parmetro un array de cadenas
para indicar a ADO.NET qu espacios de nombres quiere que ignore en un documento XML.

11.2.2 Inferir esquemas


En captulos anteriores indiqu que proporcionar informacin de metadatos o de esquema en cdigo
mejora el rendimiento respecto a obtener esta informacin en tiempo de ejecucin. Lo mismo se cumple al
inferir esquemas XML, y el mtodo ReadXml es un ejemplo.
Suponga que usa el mtodo ReadXml para cargar datos en un DataSet y que ni el documento XML ni el
DataSet contienen informacin de esquema. No puede aadir filas de datos al DataSet si no tiene
informacin de esquema. El mtodo ReadXml primero debe explorar el documento XML entero para
determinar la estructura del DataSet y entonces aadir la informacin de esquema adecuada DataSet antes
de aadir el contenido del documento. Mientras mayor sea el documento XML, mayor ser la penalizacin
en rendimiento en que incurrir al inferir el esquema a partir del documento.
188

Escuela de Informtica del Ejrcito

Este mtodo puede llevar a otro problema: puede que no obtenga el esquema que necesita. ADO.NET
asume que todos los tipos de datos son cadenas y no crear restricciones. Por qu?.
Imagine que su documento XML contiene una lista de contactos y direcciones, omitidas las etiquetas
XML, con el siguiente formato:
<EtiquetaCorreo>
<Nombre>Perico</Nombre>
<Apellidos>de los Palotes</Apellidos>
<Direccin>Calle Mayor 25</Direccin>
<Ciudad>Una Ciudad</Ciudad>
<Regin>Una Regin</Regin>
<CodPostal>00011</CodPostal>
</EtiquetaCorreo>

Supongamos que el contenido del documento representa una pequea muestra de los datos completos
de la base de datos. En otras entradas de la base de datos el contacto puede tener una segunda lnea de
direccin, o una direccin en otro pas con un formato de cdigo postal diferente. Recuerde que si pide a
ADO.NET que infiera el esquema a partir de los contenidos de un documento XML que no contiene
informacin de esquema, har lo mejor que pueda para generar el adecuado. Por tanto, siempre que pueda
debe incluir el esquema. Una opcin es incluir el esquema cuando guarde el contenido del DataSet
especificando XmlWriteMode.WriteSchema en la llamada al mtodo WriteXml. Otra opcin es guardar el
esquema en un archivo separado llamando a WriteXmlSchema y cargar el esquema llamando a
ReadXmlSchema antes de cargar el contenido del DataSet. De cualquiera de las formas ayudar a mejorar
el rendimiento de su aplicacin a la vez que evita dolores de cabeza.

11.2.3 Propiedades que afectan al esquema


Un documento XML puede tener ms de un formato; dos documentos pueden contener la misma
informacin con un esquema diferente. Si intenta cargar datos en un DataSet que ya contiene informacin
de esquema, ADO.NET ignorar la informacin que no corresponda con el esquema del objeto DataSet. Por
tanto, es importantes que el esquema del objeto DataSet corresponda al de los datos que quiere cargar.
Puede usar propiedades de los objetos del DataSet para controlar el formato que usa ADO.NET para
leer y escribir documentos XML.

Nombres de elementos y atributos


ADO.NET usa la propiedad Name de cada objeto como nombre del elemento o atributo XML
correspondiente. La propiedad DataSetName del DataSet controla el nombre del elemento raz. De forma
similar, la propiedad TableName de los objetos DataTable y la propiedad ColumnName de cada objeto
DataColumn controlan los nombres que usa ADO.NET para los elementos y atributos que corresponden a
las tablas y columnas.

Elegir elementos o atributos


Puede usar la propiedad ColumnMapping de los objetos DataColumn para almacenar los datos en forma
de elementos o de atributos. De forma predeterminada el valor de esta propiedad es Element; puede
cambiarla a Attribute si quiere almacenar los datos de la columna en forma de atributo. Tambin puede usar
el valor Hidden si no quiere que los contenidos de la columna aparezcan en el archivo XML.

Anidar datos relacionales


Puede controlar si los datos relacionales se anidan configurando la propiedad Nested del objeto
DataRelation. De forma predeterminada el valor de esta propiedad es false, de modo que aparecen primero
todos los registros de la tabla madre y despus todos los de la tabla hija. Si se cambia a true las filas de la
tabla hija aparecen anidadas debajo la fila correspondiente de la tabla madre.

Nombres de espacios y prefijos


Los objetos DataSet, DataTable y DataColumn exponen todos ellos las propiedades Namespace y
Prefix. Ambas propiedades contienen cadenas y de forma predeterminada estn vacas. Si estas
propiedades tienen contenido se aade el atributo xmlns al elemento raz del archivo, con el valor Prefijo :
Namespace, y se precede cada nombre de atributo por el prefijo, separado por dos puntos.

11.2.4 Almacenar en cache cambios en documentos XML


Uno de los posibles usos de esta capacidad de acceso a datos en formato XML es crear una capa de
acceso a datos tomando datos de la base de datos y almacenndolos en XML. Se podran modificar los
contenidos del documento XML y recuperar los datos de nuevo en objetos que deberan enviar los cambios
a la base de datos. Pero esto no funciona.
Los DataRow de ADO.NET almacenan los contenidos originales y actuales de la fila de modo que se
puedan enviar los cambios de vuelta a la base de datos. Si cambia el contenido de un elemento o atributo
189

ADO.NET 2.0

en un documento XML el documento no retiene el valor original. Si lee el documento modificado en un


DataSet de ADO.NET, no ser posible decir qu filas se han modificado, y menos cmo se han modificado.
De hecho, si modifica el contenido de un DataSet y usa el mtodo WriteXml para guardar los datos en un
documento XML usando el cdigo que se ha mostrado en este captulo, perder los cambios. De forma
predeterminada el mtodo WriteXml escribe solo el contenido actual de las filas.

Diffgram de ADO.NET
Como ya se ha dicho, puede proporcionar valores de la enumeracin XmlWriteMode cuando llama al
mtodo WriteXml. Uno de los valores de esta enumeracin es DiffGram. Si utiliza este valor ADO.NET
escribe tanto los valores originales del DataSet como los actuales en un diffgram en el documento. Este
documento se puede leer posteriormente en un DataSet para enviar los cambios pendientes a la base de
datos usando objetos SqlDataAdapter.
El siguiente fragmento de cdigo genera un documento XML con diffgram. El cdigo modifica el DataSet
cambia una fila, borra otra y aade una tercera y muestra el contenido del DataSet como diffgram XML.
Examinando el documento puede ver cmo corresponden los cambios en el DataSet con entradas en el
diffgram. El cdigo usa el mtodo MuestraXMLEnFormulario del ejemplo anterior, y por tanto necesita una
referencia al espacio de nombres System.Windows.Forms.
[STAThread]
static void Main(string[] args) {
string cadcon = @"Data Source=.\sqlexpress;Initial Catalog=Northwind;" +
"Integrated Security=true";
string consulta = "SELECT TOP 3 CustomerID, CompanyName FROM Customers";
DataSet ds = new DataSet();
SqlDataAdapter adaptador = new SqlDataAdapter(consulta, cadcon);
adaptador.Fill(ds, "Clientes");
DataTable tbl = ds.Tables["Clientes"];
//Deja el primer cliente sin cambiar
//Modifica el segundo cliente
tbl.Rows[1]["CompanyName"] = "Nombre de compaa modificado";
//Borra el tercer cliente
tbl.Rows[2].Delete();
//Aade un cliente nuevo
tbl.Rows.Add("CIANUE", "Nombre de Compaa Nueva");
//Escribe el contenido en un documento diffgram XML
string ruta = @"C:\Datos\DatosDiffgram.xml";
ds.WriteXml(ruta, XmlWriteMode.DiffGram);
MuestraXMLEnFormulario(ruta, "DataSet.WriteXml - Diffgram");
}

Novedad en ADO.NET 2.0: Caractersticas XML a nivel de DataTable


En la versin 1.x del marco de trabajo .NET los mtodos XML solo estaban disponibles a nivel de
DataSet. En la versin 2.0 se han aadido estos mtodos a la clase DataTable. Al usar estos mtodos a
nivel de tabla puede leer o escribir el contenido de un nico DataTable en lugar del DataSet completo.
Adems, puede usar estos mtodos sobre un DataTable aunque no resida en un DataSet.

11.3 DataSet + XmlDocument = XmlDataDocument


En los fragmentos de cdigo anteriores se han utilizado algunas de las caractersticas XML de la clase
DataSet, pero no ha resultado muy excitante. Si est usando estas caractersticas simplemente para
almacenar el contenido de un DataSet en un archivo y leerlo posteriormente de nuevo al DataSet, el hecho
de que el formato sea XML es irrelevante.
Si est realmente interesado en trabajar con el contenido de un DataSet en formato XML puede
considerar cargar los datos en un objeto XmlDocument. Esta clase tiene un mtodo Load que puede usar
para cargar el contenido de un archivo XML, de modo que puede usar el mtodo WriteXml de un objeto
DataSet para crear su archivo XML y despus llamar al mtodo Load del objeto XmlDocument para cargar
el archivo. Esto puede sonar bien al principio, pero tendr dos objetos que almacenan los mismos datos, y
no tiene un mtodo sencillo para sincronizarlos. Si quiere mantener los objetos sincronizados y cambia el
contenido de un objeto, necesitar localizar y modificar los datos correspondientes en el otro objeto.

11.3.1 Clase XmlDataDocument


La solucin ms simple a este problema es usar un objeto XmlDataDocument. Puede verlo como un
XmlDocument que sabe cmo comunicarse con un objeto DataSet. La clase XmlDataDocument deriva de
XmlDocument, por lo que expone sus mismas caractersticas.
190

Escuela de Informtica del Ejrcito

La clase XmlDataDocument aade dos caractersticas clave. Primero, permite cargar con facilidad el
contenido de un DataSet en un XmlDocument y viceversa. Segundo, se sincroniza con el DataSet. Si el
DataSet contiene datos, los mismos datos estarn disponibles en el XmlDataDocument, y cuando cambie el
contenido de uno de los dos objetos, el cambio afectar al otro.

11.3.2 Acceder a su DataSet como documento XML


Si es un programador XML acostumbrado a trabajar con documentos de este tipo para acceder a datos
encontrar fcil usar el objeto XmlDataDocument para acceder a los contenidos de un DataSet por medio
de interfaces XML.
Por ejemplo, puede crear un XmlDataDocument sincronizado con su DataSet para examinar el contenido
del DataSet usando consultas XPath. El siguiente fragmento de cdigo crea un XmlDataDocument
sincronizado con un DataSet que contiene informacin de pedido y detalle para un cliente. El cdigo usa
consultas XPath para extraer el contenido de un pedido del objeto XmlDataDocument. Utiliza la funcin
LeeInfoPedidoCliente de ejemplos anteriores, y debe hacer referencia al espacio de nombres System.Xml.
DataSet ds = LeeInfoPedidoCliente("GROSR");
XmlDataDocument datosXML = new XmlDataDocument(ds);
string consultaXPath = "/NewDataSet/Pedidos[OrderID=10268]";
XmlNode nodoPedido = datosXML.SelectSingleNode(consultaXPath);
Console.WriteLine("ID Pedido: {0}; ID Cliente: {1}; Fecha de pedido: {2:d}",
nodoPedido.ChildNodes[0].InnerText, nodoPedido.ChildNodes[1].InnerText,
DateTime.Parse(nodoPedido.ChildNodes[2].InnerText));
Console.WriteLine("Lneas de pedido: ");
consultaXPath = "/NewDataSet/Detalles[OrderID=10268]";
foreach(XmlNode nodoDetalle in datosXML.SelectNodes(consultaXPath))
Console.WriteLine("\tID Producto: {0,2}; Cantidad: {1,2}; Precio Unidad: {2:c}",
nodoDetalle.ChildNodes[1].InnerText, nodoDetalle.ChildNodes[2].InnerText,
Decimal.Parse(nodoDetalle.ChildNodes[3].InnerText));

11.3.3 Almacenar actualizaciones al documento XML


Al principio del captulo se afirm que los archivos XML no mantienen estado de una forma que permita
enviar los cambios posteriormente a la base de datos. La clase XmlDataDocument proporciona esta
funcionalidad sincronizando el documentos XML con el DataSet. Al modificar el contenido del documento
XML el objeto XmlDataDocument modifica los datos correspondientes en el objeto DataSet correspondiente.
El objeto DataSet tiene toda la informacin que necesita para enviar los cambios a la base de datos.
El siguiente fragmento de cdigo muestra esta funcionalidad. El cdigo usa una consulta XPath para
localizar un pedido y modifica el contenido del nodo hijo que corresponde al ID de cliente del pedido. A
continuacin examina los valores original y actual disponibles en el DataRow. Puede usar un
SqlDataAdapter con la lgica de actualizacin necesaria para enviar este cambio a la base de datos.
Justo antes de modificar el contenido del documento XML el cdigo asigna false a la propiedad
EnforceConstraints del objeto DataSet. Si esta lnea el fragmento lanzara una excepcin. Si modifica el
contenido de un DataSet que tenga impuestas restricciones por medio de un XmlDataDocument se produce
un error.
DataSet ds = LeeInfoPedidoCliente("GROSR");
DataTable tblPedidos = ds.Tables["Pedidos"];
tblPedidos.PrimaryKey = new DataColumn[] { tblPedidos.Columns["OrderID"] };
XmlDataDocument datosXML = new XmlDataDocument(ds);
string consultaXPath = "/NewDataSet/Pedidos[OrderID=10268]";
XmlNode nodoPedido = datosXML.SelectSingleNode(consultaXPath);
ds.EnforceConstraints = false;
nodoPedido.ChildNodes[1].InnerText = "ALFKI";
ds.EnforceConstraints = true;
DataRow fila = tblPedidos.Rows.Find(10268);
Console.WriteLine("ID Pedido: {0}", fila["OrderID"]);
Console.WriteLine("\tID Cliente actual: {0}", fila["CustomerID"]);
Console.WriteLine("\tID Cliente original: {0}", fila["CustomerID",
DataRowVersion.Original]);

Como puede ver, los cambios se almacenan en el DataSet. Ahora podramos usar un SqlDataAdapter
para enviar estos cambios a la base de datos.

11.4 Caractersticas XML de SQL Server 2005


Todas las bases de datos soportan XML, de alguna manera. Siempre puede almacenar datos XML en un
campo de texto, pero esto como mucho es un soporte nominal.

191

ADO.NET 2.0

SQL Server 2005 proporciona importantes caractersticas XML en el servidor, que veremos en el
apartado Tipo XML de SQL Server 2005. Pero primero vamos a ver brevemente las caractersticas XML
disponibles en SQL Server 2000.
SQL Server 2000 aadi la capacidad de recuperar datos almacenados en columnas relacionadas
tpicas y devolverlos en forma de flujo XML por medio de consultas SELECT FOR XML. Sin embargo, no
proporcionaba verdaderas caractersticas XML de servidor. Por ejemplo, no hay forma de decir que una
columna debe contener XML, no digamos XML conforme a un cierto esquema. SQL Server 2000 no
manejaba directamente una consulta XPath, ni ndices para mejorar el rendimiento de este tipo de consulta.
Finalmente, las consultas SELECT FOR XML proporciona opciones muy limitadas para el formato de los
resultados de la consulta. SQL Server 2000 trataba esta limitacin por medio de caractersticas XML en el
cliente, como consultas plantilla y el proveedor de datos .NET para XML.
SQL Server 2005 cubre todas estas limitaciones. XML ahora un tipo de datos plenamente soportado por
SQL Server; puede crear columnas de tipo XML. Tambin puede registrar esquemas XML y exigir que una
columna se adapte a un esquema particular. Puede usar XPath, un lenguaje diseado para consultar
documentos XML, cuando consulte columnas XML. SQL Server 2005 tambin soporta la indexacin de
columnas XML para mejorar el rendimiento de las consultas XPath. Finalmente, SQL Server 2005 soporta
XQuery, un lenguaje de consultas XML que permite construir consultas que devuelven datos como XML.
XQuery permite incluir columnas XML y columnas estndar en los resultados. Con XQuery tiene un control
detallado sobre el formato de los resultados de la consulta sin tener que transformas los datos desde el
cdigo cliente.

11.4.1 Tipo XML de SQL Server 2005


Puede crear una columna XML en una tabla SQL Server 2005 con la misma facilidad que cualquier otra
columna, por medio de la consulta CREATE TABLE:
CREATE TABLE MiTabla (ColumnaEntera int PRIMERY KEY,
ColumnaXml xml)

La columna ColumnaXml de la tabla puede contener ahora datos XML, pero SQL Server no valida estos
datos. La forma ms simple de validar los datos es registrar un esquema XML y asociar la columna con el
esquema:
CREATE XML SCHEMA COLLECTION esquemaXML AS ...
CREATE TABLE MiTabla (ColumnaEntera int PRIMARY KEY,
ColumnaXml xml (esquemaXML))

Por brevedad he omitido el esquema real en la sentencia CREATE XML SCHEMA COLLECTION. Puede
especificar el esquema como un valor esttico en lnea o puede pasar un parmetro.
SQL Server ahora valida los datos entrantes para asegurar que se adaptan al esquema especificado. De
forma predeterminada SQL Server permitir que un valor en la columna contenga varios documentos que se
adapten al esquema especificado. Si quiere asegurar que los valores en la columna solo puedan contener
un nico documento que se adapte al esquema use la palabra clave DOCUMENT:
CREATE TABLE MiTabla (ColumnaEntera int PRIMARY KEY,
ColumnaXml xml (DOCUMENT esquemaXML))

Recuperar datos XML mediante ADO.NET


Los datos XML recuperados por un SqlCommand o SqlDataAdapter se devolvern de forma
predeterminada en forma de cadena. En otras palabras, puede llamar al mtodo SqlDataReader sin tener
que aadir cdigo especializado para moldear los datos a otro tipo.
Si prefiere acceder a sus datos XML por medio de flujos puede usar la nueva clase SqlXml del espacio
de nombres System.Data.SqlTypes.
Para recuperar datos de este tipo desde un SqlDataReader se sigue el mismo patrn que con otros tipos
del espacio de nombres SqlTypes ya descrito. En concreto, para recuperar datos desde un SqlDataReader
usando el tipo SqlXml use el mtodo GetSqlXml.
Una vez recuperados los contenidos de una columna en un objeto SqlXml puede llamar al mtodo
CreateReader para acceder a los contenidos de la columna en forma de XmlReader.
Como otras clases del espacio de nombres SqlTypes la clase SqlXml expone las propiedades IsNull y
Value. El constructor de la clase SqlXml acepta o un Stream o un XmlReader y, una vez instanciado, su
contenido no puede cambiar.

Enviar datos XML mediante ADO.NET


El envo de datos XML por medio de ADO.NET es igual de directo. Suponga que tiene la siguiente
consulta parametrizada que quiere ejecutar para insertar una nueva fila en la tabla definida anteriormente:
192

Escuela de Informtica del Ejrcito

INSERT INTO MiTabla (ColumnaEntera, ColumnaXml)


VALUES (@ColumnaEntera, @ColumnaXml)

Como en los escenarios de recuperacin de datos XML puede proporcionar el valor para el parmetro
XML como una simple cadena .NET o como un objeto SqlXml. Una vez creado el comando, cuya propiedad
CommandText es la del ejemplo inmediato anterior, hay que aadir al comando el parmetro
correspondiente a la columna XML, utilizando una cadena:
string valorXML = <Etiqueta>Valor</Etiqueta>;
comando.Paramters.Add(@ColumnaXml, valorXML);

O utilizando un objeto SqlXml:


SqlXml valorXML = new SqlXml(new XmlTextReader(rutaAlArchivoXML));
Comando.Parameters.Add(@ColumnaXml, valorXML);

Aadir una columna XML a la base de datos Northwind


Hasta ahora hemos trabajado con una tabla inexistente; necesitamos una columna XML real para poder
comprobar lo que estamos enseando.
El grueso de los ejemplos de este libro se han basado en la base de datos Northwind. En lugar de
cambiar radicalmente a una base de datos diferente imagine el siguiente escenario. La informacin de
detalle para cada pedido, que hasta ahora se almacena en la tabla Order Details, ahora se encuentra en
una columna XML en fila correspondiente de la tabla Order. El esquema XML para esta columna es el
siguiente:
<schema id=DetallePedido xmlns=http://www.w3.org/2001/XMLSchema>
<element name=DetallePedido>
<complexType>
<attribute name=IdProducto type=int use=required />
<attribute name=PrecioUnitario type=decimal use=required />
<attribute name=Cantidad type=short use=required />
<attribute name=Descuento type=float use=required />
</complexType>
</element>
</schema>

Dado este esquema una consulta de informacin de pedido para un cliente concreto devolvera la
informacin de la siguiente tabla:
ProductID
10268

ClientID
GROSR

10785

GROSR

DetallesXML
<DetallePedido IdProducto=29 PrecioUnitario=99 Cantidad=10 Descuento=0 />
<DetallePedido IdProducto=72 PrecioUnitario=27.8 Cantidad=4 Descuento=0 />
<DetallePedido IdProducto=10 PrecioUnitario=31 Cantidad=10 Descuento=0 />
<DetallePedido IdProducto=75 PrecioUnitario=7.75 Cantidad=10 Descuento=0 />

El esquema podra ser ms preciso, especificando que los atributos no pueden ser negativos, que los
valores de IdProducto deben ser nicos, etc., pero se trata de un simple ejemplo introductorio. Para ms
informacin sobre las caractersticas disponibles en los esquemas XML vea los libros en pantalla de SQL
Server.
Ahora que tenemos esta columna qu hacemos con ella?. Un ejemplo muy comn en SQL es listar los
pedidos que contienen un producto concreto. La consulta SQL para este caso sera similar a esta:
SELECT O.OrderID, C.CompanyName, OD.Quantity
FROM Orders O
JOIN Customers C ON C.CustomerID = O.CustomerID
JOIN [Order Details] OD ON OD.OrderID = O.OrderID
JOIN Products P ON P.ProductID = OD.ProductID
WHERE P.ProductName = @NombreProducto

Ahora que tenemos los elementos de lnea de pedido como XMl en la columna DetalleXml la consulta
para conseguir la misma informacin cambia. Vamos a ver dos ejemplos de cmo podemos usar las nuevas
caractersticas XML de SQL Server 2005 para recuperar esta informacin. Primero recuperaremos los
mismos datos usando una consulta XPath, despus utilizaremos XQuery para dar formato a los resultados
de la consulta.

11.4.2 Ejecutar una consulta XPath para recuperar datos


La informacin sobre las lneas de pedido ID de pedido, cantidad, precio por unidad y descuento
ahora est almacenada como XML en la columna DetalleXml. Entonces cmo encontramos los pedidos
que contienen un producto concreto?.

193

ADO.NET 2.0

XPath es un lenguaje diseado para consultar documentos XML. De hecho, se puede usar la siguiente
XPath para consultar los detalles de pedido que contienen el producto con el ID 48:
/Detalle[@IdProducto=48]

SQL Server 2005 soporta consultas XPath por medio de diferentes mtodos del tipo de datos XML. En
este caso, queremos usar XPath en la clusula WHERE para localiza solo los pedidos que contienen este
producto. El mtodo exist del tipo de datos XML est diseado para su uso en esta clusula en esta
situacin. Devuelve 1 si se satisface la consulta XPath, 0 si no y NULL si los datos XML son nulos. En este
caso queremos comprobar si las columnas para la que el mtodo exists con esta consulta XPath devuelve
1. La consulta sera como esta:
SELECT O.OrderID, C.CompanyName
FROM Orders O
JOIN Customers C ON O.CustomerID = C.CustomerID
WHERE O.DetalleXml.exist('/Detalle[@IdProducto=48]')=1

En el escenario descrito no conocemos el identificador de producto, slo su nombre. Podemos pedir a la


tabla Products el identificador correspondiente al nombre de producto. Afortunadamente SQL Server
proporciona una forma de hacer referencia a valores de otras columnas en la consulta XPath usando la
funcin sql:column. Por ejemplo, esta consulta XPath usa el valor de la columna ProductID en el conjunto de
resultados y la compara con el atributo IdProducto en la columna XML:
/Detalle[@IdProducto=sql:column(P.ProductID)]

Podemos modificar la consulta anterior para incluir la tabla Products y proporcionar el nombre de
producto por medio de una variable y usar la funcin sql:column para hacer referencia al valor de
identificador de producto en la consulta XPath:
SELECT O.OrderID, C.CompanyName
FROM Orders O
JOIN Customers C ON O.CustomerID = C.CustomerID
CROSS JOIN Products P
WHERE P.ProductName = @NombreProducto AND
O.DetalleXml.exist('/Detalle[@IdProducto=sql:column("P.ProductID")]') = 1

Ahora solo tenemos que aadir la cantidad al producto solicitado en el detalle de pedido.
El tipo de datos XML tambin soporta rasgado, que significa dividir en partes el contenido XML en busca
de valores especficos por medio de XPath. En este caso queremos devolver solo el atributo Cantidad en
busca del elemento Detalle que contiene el producto deseado, de modo que podemos usar la siguiente
consulta XPath para acceder a este valor:
/Detalle[@IdProducto=sql:column("P.ProductID")]/@Cantidad

SQL soporta el rasgado de XML por medio del mtodo value del tipo de datos XML. El primer parmetro
para el mtodo recibe una consulta XPath y el segundo el nombre del tipo de datos SQL deseado. SQL
Server puede requerir que modifique la consulta XPath ligeramente para asegurar que devuelve un solo
valor, encerrando la consulta entre parntesis y aadiendo [1] al final:
(/Detalle[@IdProducto=sql:column("P.ProductID")]/@Cantidad)[1]

Si omite el [1] de la consulta SQL Server devolver un mensaje de error indicando que value() necesita
un singleton o secuencia vaca, lo que significa que SQL Server espera que la consulta XPath devuelva
como mucho un solo valor, que es lo que impone el [1].
Ahora podemos incluir la cantidad de elementos pedidos en los resultados de la consulta usando el
mtodo value en la columna DetalleXml:
SELECT O.OrderID, C.CompanyName,
O.DetalleXml.value('
(/Detalle[@IdProducto=sql:column("P.ProductID")]/@Cantidad)[1]', 'smallint')
AS Cantidad
FROM Orders O
JOIN Customers C ON O.CustomerID = C.CustomerID
CROSS JOIN Products P
WHERE P.ProductName = @NombreProducto AND
O.DetalleXml.exist('/Detalle[@IdProducto=sql:column("P.ProductID")]') = 1

Usar sql:variable para evitar inyeccin de SQL


Los mtodos exist y value del tipo de datos XML aceptan solo literales de cadena. No puede pasar
parmetros a estos mtodos. Como resultado, hay una tendencia natural a concatenar cadenas para
construir los literales para pasar a estos mtodos. No aada la entrada del usuario directamente a estos
valores. Utilice la funcin sql:variable, que es similar a sql:column.
194

Escuela de Informtica del Ejrcito

Imagine que la columna DetalleXml contiene nombres de producto en lugar de su identificador. El


usuario sigue especificando el nombre de producto. No debe confiar en la entrada de usuario para construir
una consulta XPath como esta:
'/Detalle[@NombreProducto="Chocolade"]'

En lugar de esto debe asignar la entrada de usuario al parmetro @NombreProducto, como en ejemplos
anterior y hacer referencia al parmetro usando la funcin sql:variable.
'/Detalle[@NombreProducto=sql:variable("@NombreProducto")]'

Mejorar el rendimiento de la consulta XPath con ndices XML


La consulta anterior, de forma predeterminada, necesita explorar toda la tabla Orders, porque no hay
forma de que SQL Server sepa qu filas de la tabla contienen el producto deseado sin examinar el
contenido de la columna DetalleXml en cada una de ellas.
Podemos mejorar el rendimiento de esta consulta indexando la columna XML:
CREATE PRIMARY XML INDEX idxDetalleXml
ON Orders(DetalleXml)

11.4.3 Recuperar resultados XML usando XQuery


El ejemplo anterior mostraba cmo usar la potencia de la consulta XPath para consultar consultas XML
en una base de datos SQL Server 2005 y devolver los datos en un conjunto de datos estndar. Pero qu
pasa si quiere obtener los datos como XML y no como filas y columnas?.
SQL Server introdujo la clusula FOR XML que permite convertir un resultado estndar en XML. Aunque
es una caracterstica potente, tambin es algo limitada en las opciones que proporciona para el esquema de
los resultados. Para intentar obtener los resultados dentro del esquema deseado por lo general es necesario
procesamiento en el cliente, usando consultas plantilla, una transformacin XSL o ambos.
SQL Server 2005 soporta XQuery, un lenguaje XML que permite a los usuarios consultar datos y darles
formato. Es la segunda parte de la definicin, el formato, lo que lo distingue de XPath. XQuery es un
lenguaje muy potente y flexible. Esta seccin le mostrar como puede usarlo para dar formato como XML a
los resultados de la consulta anterior. En algunos aspectos el ejemplo es equivalente al hola mundo de C#.

Formato para los resultados deseados


Para mostrar solo una parte de la potencia de XQuery vamos a continuar con el mismo escenario
descrito anteriormente en el que queremos recuperar informacin sobre pedidos que contienen un
determinado producto, pero aadiendo la siguiente condicin: queremos que los resultados se devuelvan
como un flujo de datos XML similar a este:
<Pedido IdPedido=10403 NombreCompaia=Ernst Handel Quantity=70 />
<Pedido IdPedido=10453 NombreCompaia=Around the Horn Quantity=15 />
...

Afortunadamente, el formato de resultados con XQuery es sencillo.


XQuery es, en muchos aspectos, similar a tecnologas como ASP.NET o ASP. Con ASP.NET se crea
HTML que incluye cdigo de secuencia de comandos de servidor. XQuery utiliza una aproximacin similar,
permitiendo combinar contenido XML con cdigo XQuery. El cdigo XQuery se delimita entre llaves. El
objetivo es generar el siguiente contenido para cada fila encontrada usando la consulta XPath descrita:
<Pedido IdPedido={ valor de la columna OrderID }
NombreCompaia={ valor de la columna CompanyName }
Cantidad= valor del atributo Cantidad en DetalleXml />

Podemos usar la funcin sql:column del ejemplo anterior en XQuery para asignar valores de las
columnas CompanyName y OrderID a los atributos correspondientes:
<Pedido IdPedido={ sql:column(O.OrderID)
NombreCompaia={ sql:column(C.CompanyName) } />

Incluso podemos construir los nodos XML mediante programacin. Por ejemplo, la siguiente expresin
XQuery es equivalente a la anterior:
element Pedido {
attribute NombreCompaia { sql:column(C.CompanyName) },
attribute IdPedido { sql:column(O.OrderID) }
}

Creo que la segunda forma es ms clara para los ejemplos.

195

ADO.NET 2.0

Meramente dar formato a columnas del conjunto de resultados como atributos no impresiona, porque
puede obtener el mismo resultado con una consulta SELECT FOR XML. Vamos a hacer algo ms
complejo. Vamos a asegurarnos de que el valor que aparece en el atributo NombreCompaia contiene,
como mximo, 20 caracteres de la columna CompanyName. Si la columna es ms larga recortaremos el
valor a 20 caracteres y aadiremos ; si no, simplemente devolvemos el valor completo. Podemos
lograrlo una sentencia if y las funciones string-length, concat y substring:
element Pedido{
attribute NombreCompaia{
if(string-length(sql:column(C.CompanyName)) <= 20)
then sql:column(C.CompanyName)
else concat(substring(sql:column(C.CompanyName), 0, 20), ...)
},
attribute IdPedido{sql:column(O.OrderID)}
}

Todo lo que queda es aadir el valor del atributo Cantidad del elemento Detalle que contiene el producto
deseado. La seccin anterior del captulo demostr cmo encontrar este elemento usando XPath:
/Detalle[@IdProducto=sql:Column(P.ProductID)]

XQuery soporta consultas XPath, de modo que podemos usar esta consulta y especificar que queremos
devolver el valor del atributo Cantidad del elemento Detalle. Ahora la expresin XQuery tiene este aspecto:
element Pedido{
attribute NombreCompaia{
if(string-length(sql:column(C.CompanyName)) <= 20)
then sql:column(C.CompanyName)
else concat(substring(sql:column(C.CompanyName), 0, 20), ...)
},
attribute IdPedido{sql:column(O.OrderID)},
attribute Cantidad{
/Detalle[@IdProducto=sql:column(P.ProductID)]/@Cantidad
}
}

Ahora es el momento de usar esta expresin. El tipo de datos XML de SQL Server 2005 soportar la
ejecucin de consultas XQuery por medio del mtodo query. Como los mtodos exists y value el mtodo
query recibe un literal de cadena. Si combinamos la expresin XQuery con nuestras consultas SQL y XPath
el resultado es el siguiente:
SELECT DetalleXml.query('
element Pedido{
attribute NombreCompaia{
if(string-length(sql:column("C.CompanyName")) <= 20)
then sql:column("C.CompanyName")
else concat(substring(sql:column("C.CompanyName"), 0, 20), "...")
},
attribute IdPedido{sql:column("O.OrderID")},
attribute Cantidad{
/Detalle[@IdProducto=sql:column("P.ProductID")]/@Cantidad
}
}
') AS Detalle
FROM Orders O
JOIN Customers C ON O.CustomerID = C.CustomerID
CROSS JOIN Products P
WHERE P.ProductName = @NombreProducto AND
O.DetalleXml.exist('/Detalle[@IdProducto=sql:column("P.ProductID")]') = 1

11.5 Recuperar datos XML desde SQL Server 2000


Las aplicaciones que utilicen SQL Server 2000 y por tanto no puedan aprovechar las caractersticas XML
de SQL Server 2005 pueden obtener resultados XML utilizando una consultas SELECT FOR XML.
La forma ms sencilla de ejecutar esta consulta y examinar sus resultados es utilizar el Analizador de
consultas. Veamos una consulta simple que recupera los valores de las columnas CustomerID y
CompanyName de los dos primeros registros de la tabla Customers:
SELECT TOP 2 CustomerID, CompanyName FROM Customers

Y vamos a aadir el siguiente cdigo:


FOR XML AUTO, ELEMENTS

196

Escuela de Informtica del Ejrcito

La parte FOR XML indica a SQL Server que devuelva el resultado de la consulta en formato XML. AUTO
dice que llame al elemento para cada fila segn la tabla a la que hace referencia la consulta. ELEMENTS
pide a SQL Server que almacene el valor de cada columna en un elemento. De forma predeterminada SQL
Server devolvera esta informacin en forma de atributos en lugar de elementos.
Si est usando este tipo de consulta en SQL SERVER 2005 puede aadir adems una clusula ROOT.
Esta clusula aade un elemento nico de mximo nivel usando el nombre especificado. Este elemento
puede ser til porque una serie de nodos XML que no tenga un elemento de mximo nivel ser un
fragmento y no un documento.
SELECT TOP 2 CustomerID, CompanyName FROM Customers
FOR XML AUTO, ELEMENTS, ROOT('Clientes');

La clase SqlCommand est diseada para ayudar a manejar los resultados de consultas SELECT
FOR XML por medio de un mtodo ExecuteXmlReader que devuelve un objeto XmlReader que puede usar
para acceder a los resultados de la consulta. Los proveedores de datos .NET para OLE DB, ODBC u Oracle
incluidos con el marco de trabajo .NET no tienen una funcionalidad similar.
Puede tomar el XmlReader devuelto por una llamada a ExecuteXmlReader y usarlo para cargar un
XmlDocument:
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;"
+ "Integrated Security=true";
string consulta = "SELECT TOP 2 CustomerID, CompanyName FROM Customers "
+ "FOR XML AUTO, ELEMENTS";
XmlDocument documento = new XmlDocument();
XmlNode raz = documento.CreateElement("Clientes");
documento.AppendChild(raz);
using(SqlConnection conexin = new SqlConnection(cadcon)) {
conexin.Open();
SqlCommand comando = new SqlCommand(consulta, conexin);
using(XmlReader lector = comando.ExecuteXmlReader()) {
while(!lector.EOF)
raz.AppendChild(documento.ReadNode(lector));
lector.Close();
}
conexin.Close();
}
string cadRutaXml = @"c:\datos\Datos.xml";
documento.Save(cadRutaXml);
MuestraXMLEnFormulario(cadRutaXml, "SELECT ... FOR XML");

Este cdigo sera ms sencillo si la consulta devolviera un documento XML en lugar de un fragmento
XML. Como se indic anteriormente, en SQL Server 2005 se puede lograr este efecto por medio de la
clusula ROOT en la consulta SELECT FOR XML. En este caso se simplifica la carga del objeto
XmlDocument, ya que basta con llamar a su mtodo Load pasando como argumento el objeto XmlReader.

11.6 El proveedor de datos .NET SQL XML


Hay otra forma de procesar los resultados de consultas SELECT FOR XML el proveedor de datos
.NET SQL XML. Este proveedor se cre originalmente para SQL Server 2000, para ayudar a generar
consultas SELECT FOR XML y procesar sus resultados. Gracias a las caractersticas XML incluidas en
SQL Server 2005 en realidad hay poca necesidad de usar este proveedor. Esta seccin solo es de utilidad
en caso de trabajar con bases de datos SQL Server 2000.
El proveedor de datos .NET SQL XML no est incluido en el marco de trabajo .NET, pero se instala como
parte de las caractersticas SQL XML incluidas con SQL Server 2005. Puede usar este proveedor en sus
aplicaciones aadiendo una referencia al espacio de nombres Microsoft.Data.SqlXml.
Una versin anterior del proveedor de datos .NET para SQL XML es parte de la descarga SQLXML para
SQL Server 2000. Esta versin no se ha probado con la versin 2.0 del marco de trabajo .NET.
El proveedor de datos .NET SQL XML es muy diferente a los otros proveedores, porque las
caractersticas XML de SQL Server no son caractersticas de acceso a datos tradicionales. Este proveedor
consiste solamente en tres objetos de los proveedores tradicionales: SqlXmlCommand, SqlXmlAdapter y
SqlXmlParameter. Estas clases no derivan de las mismas clases base que los otros proveedores ni se
pueden usar dentro de un bloque using porque no implementan IDisposable. El proveedor de datos .NET
SQL XML no incluye clases para gestionar conexiones, cadenas de conexin o transacciones.
Conviene observar que el proveedor de datos .NET XQL XML se basa en tecnologa OLE DB nativa para
comunicar con SQL Server. Deber proporcionar una cadena de conexin OLE DB.
197

ADO.NET 2.0

11.6.1 SqlXmlCommand
El objeto SqlXmlCommand ofrece un nico constructor que precisa una cadena de conexin con la base
de datos SQL Server. Deber usar la misma que utilizara para un objeto OleDbConnection.
Como en las restantes clases Command la consulta que se quiere ejecutar se especifica en la propiedad
CommandText. Puede usar el objeto ExecuteXmlReader para ejecutar la consulta y obtener los resultados
en un objeto XmlReader.
Como se ha indicado, este proveedor est diseado especficamente para ayudar a los desarrolladores a
trabajar con el resultado de consultas XML. La clase SqlXmlCommand tiene una propiedad RootTag que
puede usar para aadir un nodo de mximo nivel al resultado de la consulta, lo que significa que los
resultados constituirn un documento bien formado sin la necesidad de confiar en la clusula ROOT de la
consulta SELECT FOR XML. Esto es especialmente til si est conectando con una base de datos SQL
Server 2000. Por tanto, puede usar el mtodo Load de la clase XmlDocument:
string cadcon = @"Provider=SQLOLEDB;Data Source=.\SQLExpress " +
"Inicial Catalog=Northwind;Integrated Security=SSPI";
string consulta = "SELECT TOP 2 CustomerID, CompanyName FROM Customers " +
"FOR XML AUTO, ELEMENTS";
SqlXmlCommand comando = new SqlXmlCommand(cadcon);
comando.CommandText = consulta;
comando.RootTag = "Clientes";
XmlDocument documento = new XmlDocument();
XmlReader lector = comando.ExecuteXmlReader();
documento.Load(lector);
lector.Close();
string cadRutaXml = @"C:\datos\Datos.xml";
documento.Save(cadRutaXml);
MuestraXMLEnFormulario(cadRutaXml, "Proveedor de datos SQL XML");

La clase SqlXmlCommand tambin permite pasar el resultado de una consulta XML a un flujo, ya sea
para crear un nuevo flujo para los resultados por medio del mtodo ExecuteStream o para escribir el
resultado en un flujo existente por medio del mtodo ExecuteToStream.
Si quiere ejecutar una consulta que no devuelve resultados utilice el mtodo ExecuteNonQuery, que
solamente se diferencia del correspondiente en la clase SqlCommand en que no devuelve un valor.
A diferencia de la clase SqlCommand la clase SqlXmlCommand no ofrece un mtodo para devolver
resultados de una consulta SELECT estndar por medio de una clase DataReader

11.6.2 SqlXmlAdapter
Podra usar el mismo proceso para cargar el contenido de un XmlReader en un DataSet, pero el
proveedor de datos .NET SQL XML ofrece un medio ms sencillo la clase SqlXmlAdapter. Puede
almacenar el resultado de una consulta FOR XML en un DataSet usando SqlXmlAdapter con la misma
facilidad que el resultado de una consulta SQL estndar con SqlDataAdapter.
Se usa el mismo cdigo para crear el objeto SqlXmlCommand y despus se crea el SqlXmlAdapter
especificando el SqlXmlCommand en el constructor. A continuacin puede llenar el DataSet con los
resultados de la consulta llamando al mtodo Fill del objeto SqlXmlAdapter:
//Aadido a partir de la lnea donde se configura comando.RootTag
SqlXmlAdapter adaptador = new SqlXmlAdapter(comando);
DataSet datos = new DataSet();
adaptador.Fill(datos);
foreach(DataRow fila in datos.Table[0].Rows)
Console.WriteLine("ID Cliente: {0}, Nombre Ca: {1}",
fila["CustomerID"], fila["CompanyName"]);

11.6.3 Consultas plantilla


El proveedor de datos .NET SQL XML soporta consultas plantilla para dar un mayor control sobre el
formato de los resultados de sus consultas. Bsicamente, una consulta plantilla es un documento XML que
contiene consulta. Cuando se ejecuta esta consulta el proveedor combina el XML en la plantilla con el
resultados de las consultas.
Vamos a ver un ejemplo de consulta plantilla. La consulta plantilla siguiente contiene dos consultas
SELECT FOR XML que recuperan informacin de pedido y de detalle para un cliente concreto:

198

Escuela de Informtica del Ejrcito

<RESULTADO xmlns:sql='urn:schemas-microsoft-com-sql'>
<sql:query>
SELECT OrderID, CustomerID, OrderDate FROM Orders WHERE CustomerID='GROSR'
FOR XML AUTO, ELEMENTS
</sql:query>
<sql:query>
SELECT OrderID, ProductID, Quantity, UnitPrice FROM [Order Details]
WHERE OrderID IN (SELECT OrderID FROM Orders WHERE CustomerID='GROSR')
FOR XML AUTO, ELEMENTS
</sql:query>
</RESULTADO>

La propia consulta es un documento XML. El proveedor de datos examina los elementos que residen en
el espacio de nombres sql y ejecuta el texto en los elementos query. El resto de los elementos se tratan
simplemente como XML y aparecen en el resultado como tales.
Puede ejecutar esta consulta usando el proveedor de datos .NET SQL XML y recibir un documento que
contiene los resultados de ambas consultas, con el elemento raz RESULTADO.

Ejecutar una consulta plantilla con SqlXmlCommand


Para indicar al objeto SqlXmlCommand que est trabajando con una consulta plantilla se utiliza el valor
correspondiente de la enumeracin SqlXmlCommandType en la propiedad CommandType. Si proporciona
la ruta del archivo que contiene la consulta, utilice el valor TemplateFile, si lo que proporciona es el texto de
la consulta, utilice el valor Template.
A continuacin puede ejecutar la consulta para almacenar los resultados en un documento XML o un
DataSet, como en los ejemplos anteriores.

Consultas plantilla parametrizadas


La creacin y ejecucin de consultas parametrizadas es muy simple. Primero se define el parmetro en
la consulta plantilla, especificando su nombre, asegurndose de omitir el carcter @. A continuacin use el
parmetro en las consultas dentro de la plantilla como hara en un SqlCommand, especificando el nombre
del parmetro con el carcter @.
La siguiente consulta plantilla recupera la misma informacin informacin de pedido y de detalle para
un cliente salvo que la consulta contiene un parmetro para el identificador del cliente en lugar de
especificar su valor explcitamente.
<RESULTADO xmlns:sql='urn:schemas-microsoft-com-sql'>
<sql:header>
<sql:param name='IdCliente' />
</sql:header>
<sql:query>
SELECT OrderID, CustomerID, OrderDate FROM Orders WHERE CustomerID=@IdCliente
FOR XML AUTO, ELEMENTS
</sql:query>
<sql:query>
SELECT OrderID, ProductID, Quantity, UnitPrice FROM [Order Details]
WHERE OrderID IN (SELECT OrderID FROM Orders WHERE CustomerID=@IdCliente)
FOR XML AUTO, ELEMENTS
</sql:query>
</RESULTADO>

Para establecer el valor de este parmetro mediante programacin se usa la clase SqlXmlParameter. No
puede crear un objeto de este tipo con la palabra clave new; la nica forma es usar el mtodo
CreateParameter del objeto SqlXmlCommand. No hay necesidad de aadir el parmetro a la coleccin de
parmetros; de hecho, no se expone esta coleccin como propiedad. Una vez creado el objeto
SqlXmlParameter se configuran sus propiedades Name y Value antes de ejecutar la consulta:
//comando creado como en ejemplos anteriores
SqlXmlParameter parmetro = comando.CreateParameter();
parmetro.Name = "@IdCliente";
parmetro.Value = "GROSR";
//Ejecucin de la consulta y uso de resultados como en ejemplo anterior.

11.6.4 Consultas XPath


Si tuviramos un documento XML con todos los pedidos de la base de datos Northwind podramos usar
la siguiente consulta XPath para examinar solamente los pedidos para el cliente con el ID GROSR:
Orders[CustomerID='GROSR']

199

ADO.NET 2.0

Si examina la enumeracin SqlXmlCommandType encontrar una entrada XPath. Puede asignar este
valor a la propiedad CommandType de SqlXmlCommand, proporcionar la consulta XPath en CommandText
y ejecutar la consulta, pero obtendr una excepcin indicando que la consulta no es vlida.
El motor de base de datos SQL Server en realidad no sabe lo que es una consulta XPath. El proveedor
de datos .NET SQL XML soporta consultas XPath, pero en realidad lo que hace es traducir estas consultas
a consultas SELECT FOR XML. Aunque puede interpretar la consulta y realizar esta traduccin, el
proveedor de datos necesita algo de ayuda.

Aadir informacin de esquema


Puede ayudar al proveedor de datos a traducir esta consulta XPath proporcionando un esquema XML
que defina las tablas y columnas de la base de datos que incluir en la consulta, as como la estructura de los
resultados:
<?xml version="1.0" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:sql="urn:schemas-microsoft-com:mapping-schema">
<xsd:annotation>
<xsd:appinfo>
<sql:relationship name="relOrderDetails" parent="Orders" parent-key="OrderID"
child="[Order Details]" child-key="OrderID" />
</xsd:appinfo>
</xsd:annotation>
<xsd:element name="Orders">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="OrderID" type="xsd:int" />
<xsd:element name="CustomerID" type="xsd:string" />
<xsd:element name="OrderDate" type="xsd:dateTime" sql:datatype="datetime" />
<xsd:element name="Order_x0020_Details" sql:relation="[Order Details]"
sql:relationship="relOrderDetails ">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="ProductID" type="xsd:int" />
<xsd:element name="Quantity" type="xsd:int" />
<xsd:element name="UnitPrice" type="xsd:decimal" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>

El esquema tiene entradas que hacen referencia a tablas y columnas, que relacionan datos entre dos
tablas (sql:relationship) y que ayudan a que el proveedor de datos comprenda el tipo de datos de una
columna (sql:datatype). Sin embargo, este esquema muestra solo una parte de las caractersticas que
puede usar en un archivo de esquema XML con el proveedor de datos .NET SQL XML. Para ms
informacin consulte la documentacin de SQL XML.
Una vez creado el archivo de esquema asigne a la propiedad SchemaPath del objeto SqlXmlCommand
el archivo que contiene esta informacin. El siguiente fragmento ejecuta la consulta XPath descrita
anteriormente para recuperar los pedidos y detalles para un cliente concreto usando este esquema, y
almacena el resultado de la consulta en un objeto XmlDocument.
string cadcon = @"Provider=SQLOLEDB;Data Source=.\SQLExpress;"+
"Initial Catalog=Northwind;Integrated Security=SSPI";
SqlXmlCommand comando = new SqlXmlCommand(cadcon);
comando.SchemaPath = @"C:\datos\Esquema.xsd";
comando.CommandText = "Orders[CustomerID='GROSR']";
comando.CommandType = SqlXmlCommandType.XPath;
comando.RootNode = "NodoRaiz";
XmlReader lector = comando.ExecuteXmlReader();
XmlDocument documento = new XmlDocument();
documento.Load(lector);
string cadRutaXml = @"C:\datos\Datos.xml";
documento.Save(cadRutaXml);
MuestraXMLEnFormulario(cadRutaXml, "Consulta XPath con esquema");

200

Escuela de Informtica del Ejrcito

11.6.5 Aplicar una transformacin XSL


Anteriormente se dijo que poda dar formato a un documento XML de ms de una forma, y que es
posible que dos documentos XML contengan los mismos datos y se diferencien solo en el esquema. Puede
usar una tecnologa complementaria llamada XSL para transformar la estructura de documentos XML.
Una transformacin XSL es un documento XML que contiene un conjunto de instrucciones que describe
cmo transformar el contenido de otro documento XML. Las transformaciones XSL son tiles si quiere
cambiar la estructura de su documento. Puede usarlas para convertir XML en HTML.
Si tiene una transformacin XSL y quiere aplicarla al resultado de su consulta SQL XML puede asignar a
la propiedad XslPath del objeto SqlXmlCommand una cadena que contenga la ruta al documento XSL.

11.6.6 Enviar actualizaciones


El proveedor de datos .NET XQL XML permite enviar actualizaciones a la base de datos. La clase
SqlXmlAdapter tiene un mtodo Update que puede usar para enviar los cambios almacenados en el
DataSet a la base de datos. Si ha ledo los captulos anteriores no le sorprender.
Sin embargo, la clase SqlXmlAdapter no maneja la actualizacin de la misma forma en que lo hacen
otras clases DataAdapter. En lugar de recorrer las filas del DataSet enviando el comando de actualizacin,
borrado o insercin correspondiente para cada una de ellas, el SqlXmlAdapter procesa los cambios
generando un diffgram. El proveedor de datos .NET SQL XML procesa el diffgram completo creando una
consulta por lotes compleja para enviar todos los cambios a la base de datos de una vez. Pero para generar
esta consulta necesita un poco de ayuda.
La solucin es la misma que para las consultas XPath. Puede usar el SqlXmlAdapter para enviar
cambios a la base de datos si se asegura de que el objeto SqlXmlCommand tiene un archivo de esquema
que contenga toda la informacin de tabla y columna necesaria.
De hecho, puede enviar la actualizacin usando un SqlXmlCommand cuya propiedad CommandText
tenga el valor DiffGram. Utilice el mtodo WriteXml del objeto DataSet para crear el diffgram y configure un
SqlXmlCommand que use este diffgram y un archivo de esquema.

Lgica de actualizacin de SqlXmlCommand


Si va a utilizar este proveedor de datos para enviar cambios a la base de datos debe comprender los
siguientes puntos:
El proveedor de datos .NET SQL XML envuelve los cambios en una transaccin y cancela la
transaccin si detecta que se ha producido un error en cualquier consulta individual o si cualquier
consulta individual informa de que no ha actualizado exactamente una fila. Esto significa que realizar
todos los cambios o ninguno.
El proveedor de datos .NET SAL XML no recupera datos mientras enva cambios. No ver los valores
de autoincremento o marca de tiempo tras enviar las actualizaciones.
Si enva cambios a la base de datos usando el mtodo Update del objeto SqlXmlAdapter, el
adaptador llamar al mtodo AcceptChanges del DataSet tras enviar los cambios con xito.

11.7 Un ejemplo sencillo


Hemos visto fragmentos de cdigo que muestran caractersticas aisladas. Es el momento de unir
algunas de estas caractersticas en una pequea aplicacin que muestre la potencia de XML y de las
caractersticas XML de ADO.NET.
El ejemplo usar consultas parametrizadas para recuperar un historial de pedidos de un cliente concreto.
Las consultas recuperarn datos de cuatro tablas relacionadas: Customers, Orders, Order Details y
Products. El resultado de las consultas se devolver en XML, que se convertir en HTML mediante una
transformacin XSL.
La lgica de esta aplicacin se puede aprovechar en dos escenarios: crear aplicaciones Web y generar
informes.
En realidad se trata de dos ejemplos. El primero GenerateHtml_FromDataSet conecta con la base de
datos Northwind local para cargar un DataSet a partir del que generar un XmlDocument. El segundo,
GenerateHtml_FromSqlXml genera el mismo resultado utilizando el proveedor .NET SQL XML.

201

ADO.NET 2.0

202

Escuela de Informtica del Ejrcito

12 Aplicaciones Windows
En este captulo vamos a usar el conocimiento adquirido en los captulos previos para discutir la creacin
de aplicaciones efectivas basadas en Windows.
El captulo tambin tratar las nuevas caractersticas que forman parte de la vinculacin de datos de
formularios Windows. Examinaremos cmo usar la vinculacin de datos arrastrar y soltar desde la ventana
Origen de datos. Tambin veremos nuevas clases del espacio de nombres System.Windows.Forms - las
clases BindingNavigator, BindingSource y DataGridView diseadas para ayudar a crear interfaces
interactivos ms rpidamente. Estudiaremos mejoras a caractersticas existentes como las propiedades
disponibles en la clase Binding.
El captulo demostrar cmo puede usar vinculacin de datos para ahorrar tiempo cuando desarrolle el
interface de su aplicacin. Tambin examinaremos diferentes estrategias para conexin y actualizacin.
Finalmente discutiremos diferentes formas de trabajar con objetos binarios grandes (BLOB) en una
aplicacin Windows.

12.1 Crear un interface con vinculacin de datos


El paquete Windows Forms que forma parte del marco de trabajo .NET incluye soporte para vinculacin
de datos. La vinculacin de datos ofrece funcionalidades que permiten mostrar y modificar datos mediante
controles Windows, permitiendo crear el interface de usuario ms rpida y fcilmente.
En realidad la vinculacin de datos se extiende a objetos que no forman parte de ADO.NET, como son
todos los que implementen el interface IList.
Vamos a usar vinculacin de datos para crear una aplicacin de entrada de datos sencilla. Esta
aplicacin permitir a los usuarios ver y modificar los pedidos de un cliente. Crearemos la aplicacin en
fases para mostrar las diferentes caractersticas de vinculacin de datos necesarias para desarrollarla.

12.1.1

Paso 1: Crear el DataSet con tipo

Comience creando una aplicacin Windows. Cambie el nombre del formulario predeterminado a
frmPedidos y su ttulo a Edicin de Pedidos.
La aplicacin mostrar informacin de pedidos de un cliente, incluyendo datos de las tablas Orders,
Order Details, Products y Employees. Querremos acceder a datos de cada una de estas tablas relacionadas
en varias partes de la aplicacin en base a restricciones de clave externa que existen en la base de datos.
Para simplificar el acceso a estos datos crearemos un DataSet con tipo que contendr un DataTable por
cada tabla. Comenzaremos solo con los datos con los que queremos trabajar inicialmente, en este caso la
tabla Orders, y crearemos el esquema del DataSet incrementalmente.
Hay un par de opciones para crear el DataSet con tipo en tiempo de diseo. La ms sencilla es llamar al
Asistente de configuracin de origen de datos pulsando el botn o enlace Agregar nuevo origen de datos en
la ventana Orgenes de datos, o seleccionando Agregar nuevo origen de datos en el men Datos.
Una vez abierto el asistente, especifique que quiere crear el nuevo origen basado en una base de datos
y seleccione la cadena de conexin que le permitir conectar con la base de datos de su servidor SQL
Server. Si no tiene an esta conexin puede crearla usando el asistente.
Seleccione las columnas OrderID, CustomerID, EmployeeID y OrderDate de la tabla Orders. Cambie el
nombre del DataSet a dsNorthwind. Pulse el botn Finalizar; el DataSet est listo para usar.
No necesita crear un SqlDataAdapter separado. El asistente de configuracin crear un TableAdapter
separado, que encapsula el SqlDataAdapter, para cada tabla, o vista, que seleccione en el asistente. El
TableAdapter generado automticamente recupera todas las filas de la tabla. En el paso 3 limitaremos las
filas recuperadas.
La columna OrderID de la tabla Orders es una columna con auto incremento; vamos a configurar los
valores de auto incremento. Pulse dos veces sobre el archivo dsNorthwind.xsd en el Explorador de
servidores y seleccione la columna OrderID. En la ventana de propiedades cambie el valor de
AutoIncrementSeed y AutoIncrementStep a -1. Guarde los cambios y cierre el diseador de DataSet.

12.1.2

Paso 2: Aadir controles con vinculacin simple

Ahora tiene una clase DataSet con tipo para su proyecto. Vamos a aadir al formulario algunos controles
vinculados para mostrar los datos de un pedido concreto. Para que el interface sea ms intuitivo aadiremos
por cada control vinculado un control Label que ayude al usuario a comprender qu datos contiene.
Con Visual Studio 2005 todo esto consiste simplemente en arrastrar la columna deseada desde la
ventana de Origen de datos. Arrastre una a una las columnas OrderID, CustomerID y EmployeeID al
formulario. Visual Studio crea un control Label y un control TextBox y usa el nombre de la columna para la
propiedad Name de cada control, por ejemplo, orderIDTextBox y orderIDLabel. Puede cambiar los valores
203

ADO.NET 2.0

de estas propiedades por medio de la ventana de Propiedades. Visual Studio tambin vincula la propiedad
Text del control TextBox con la columna del DataSet. Examinaremos ms de cerca cmo puede vincular un
TextBox, tanto mediante la ventana de Propiedades como por medio de cdigo.

Clase BindingSource
La primera vez que se arrastra una columna desde la ventana Origen de datos al formulario Visual
Studio aade una serie de componentes en la bandeja de componentes. Dos de estos componentes son
instancias de clases que ya conoce: el DataSet con tipo y el TableAdapter. Los otros dos componentes son
instancias de clases que aparecen nuevas en la versin 2.0 del marco de trabajo .NET: BindingSource y
BindingNavigator. Veremos ms de cerca la clase BindingNavigator en el paso 4.
Recordar que no existe una posicin actual en DataTable o DataView; todas las filas estn disponibles
en cualquier momento. Aunque este concepto funciona bien cuando se accede mediante programacin, no
se traduce correctamente a una aplicacin de formularios Windows. El usuario espera que exista una fila de
informacin actual disponible en una serie de controles del formulario y tener la posibilidad de desplazarse
adelante y atrs sobre las filas disponibles.
En la versin 1.x del marco de trabajo .NET la clase CurrencyManager gestionaba la fila de datos actual,
sirviendo como intermediario entre los controles vinculados y el DataView. Tenga en cuenta que cuando se
vinculan controles con DataTable en realidad se enlazan con el DataView que devuelve la propiedad
DefaultView de la tabla. Para dar al usuario la posibilidad de navegar sobre los datos era necesario aadir
sus propios controles al formulario y hacer que modificaran la propiedad Position del objeto
CurrencyManager asociado con los controles vinculados. Al vincular controles con un DataView se crea
implcitamente un objeto CurrencyManager.
La clase BindingSource deriva de la clase CurrencyManager, proporcionando funcionalidad adicional a la
vez que simplifica el proceso de escribir cdigo para interactuar con controles vinculados. Vamos a ver
algunas de las formas en que la clase BindingSource facilita la vida a los desarrolladores.
Creacin explcita. Puede crear instancias de la clase BindingSource explcitamente por medio de su
constructor. Esto representa un avance importante para la vinculacin de datos en formularios Windows. La
creacin explcita de objetos BindingSource y la vinculacin de controles con estos objetos precisa una lnea
extra de cdigo, pero elimina mucha confusin.
Indireccin. La clase BindingSource implica una capa extra de indireccin, lo que permite separar los
controles de los datos. El BindingSource se enlaza con el origen de datos, y los controles con el
BindingSource. Tambin puede enlazar los controles con un objeto BindingSource antes de que la
aplicacin reciba realmente los datos.
Este nivel aadido de indireccin es extremadamente til si su aplicacin recibe datos desde un
componente como un servicio Web. Imagine que cuando el usuario pulsa un botn en su aplicacin su
cdigo llama a un servicio Web para ejecutar una consulta y recuperar los resultados en un DataSet. Tras
recuperar los resultados basta configurar las propiedades DataSource y DataMember del objeto
BindingSource de modo que pueda acceder a los datos y alertar a los controles enlazados de que estos
datos estn disponibles. No necesita volver a enlazar todos los controles con el DataSet recin obtenido, lo
que s era necesario para manejar esta situacin en .NET 1.x.
Mtodos de navegacin. La clase BindingSource expone mtodos de navegacin sencillos: MoveFirst,
MovePrevious, MoveNext y MoveLast. Estos mtodos simplifican el proceso de modificacin de la posicin
actual mediante programacin. Utilizando un CurrencyManager era necesario manipular directamente la
propiedad Position. No era difcil, pero ahora lo es an menos.
Propiedades de DataView. La clase BindingSource expone propiedades que ya se encuentran en la clase
DataView, aunque en algunos casos los nombres son diferentes. La exposicin de estas propiedades
permite a los desarrolladores trabajar solo con la clase BindingSource. Antes de aadir la clase
BindingSource los desarrolladores estaban obligados a utilizar propiedades de CurrencyManager para
explorar el conjunto de resultados y de DataView para controlar qu filas son visibles y/o el orden de estas
filas.
Las propiedades Filter y Sort de la clase BindingSource corresponden con las propiedades RowFilter y
Sort de la clase DataView respectivamente. Las propiedades AllowAdd, AllowEdit y AllowRemove de la
clase BindingSource corresponden con AllowNew, AllowEdit y AllowDelete de la clase DataView.

Crear un BindingSource
BindingSource est disponible como elemento de la lengeta Datos del cuadro de herramientas. Puede
aadir un BindingSource a su formulario en tiempo de diseo arrastrando y soltando este elemento al
formulario o su bandeja de componentes.
204

Escuela de Informtica del Ejrcito

Para enlazar el BindingSource con un DataTable del DataSet asigne el DataSet a la propiedad
DataSource usando la ventana de Propiedades y despus indique el DataTable en la propiedad
DataMember.
Hay dos formas de conseguir lo mismo en cdigo. Puede crear un objeto BindingSource y configurar sus
propiedades DataSource y DataMember por separado, o puede establecer estos valores directamente en el
constructor.
BindingSource origen;
//Por separado
origen = new BindingSource();
origen.DataMember = "NombreTabla";
origen.DataSource = dataset;
//En una sola sentencia
origen = new BindingSource(dataset, "NombreTabla");

Enlazar controles simples usando la ventana de propiedades


Enlazar un control simple, como un TextBox, usando la ventana de Propiedades es sencillo, aunque no
muy intuitivo. Aunque lo que queremos es que la propiedad Text del control muestre el valor de la columna
deseada en la fila de datos actual, no se configura directamente la propiedad Text del control.
Debe utilizar la seccin DataBindings de la ventana de Propiedades. Primero seleccione el control que
quiere enlazar. Expanda DataBindings en la ventana de Propiedades. Para la mayora de los controles la
propiedad que quiere vincular aparecer en esta seccin. En este caso, pulse sobre la propiedad y
seleccione la columna que desea en la lista desplegable. Si la propiedad no parece en la lista pulse el botn
elipsis (...) junto al elemento (Avanzado) y seleccione la propiedad en el dilogo que se le muestra.

Enlazar controles simples mediante programacin


Todos los controles exponen una propiedad DataBindings que puede usar para enlazar el control con un
BindingSource o directamente con un DataView. Puede usar el siguiente cdigo para vincular la propiedad
Text de un control TextBox con la columna OrderID disponible por medio de un BindingSource:
orderIDTextBox.DataBindings.Add("Text", origenPedidos, "OrderID");

De hecho, este es el cdigo que genera Visual Studio cuando se enlaza el control en tiempo de diseo,
ya sea arrastrando una columna desde la ventana Orgenes de datos o utilizando la ventana de
Propiedades.

Cambiar el tipo de control asociado con una columna


La columna OrderDate de la tabla Orders contiene la fecha en que se hizo el pedido. Cuando se crea un
DataSet con tipo la columna correspondiente es del tipo DateTime. De forma predeterminada Visual Studio
asocia este tipo de datos con el control DateTimePicker. Si arrastra la columna al formulario Visual Studio
aadir un Label y un DateTimePicker al formulario.
Si examina la columna en la ventana Orgenes de datos ver el icono del control DateTimePicker a la
izquierda de la columna OrderDate. Seleccione esta columna, pulse la flecha hacia abajo a la derecha del
nombre de la columna y ver la lista de controles que puede usar para enlazar esta columna; seleccione
TextBox. Al arrastrar la columna al formulario Visual Studio crear un Label y un TextBox en lugar de un
Label y un DateTimePicker.
El control DateTimePicker es muy til porque permite que el usuario vea y cambie valores DateTime con
facilidad. Sin embargo, no maneja bien valores nulos. Si pasa a una fila que contiene un valor nulo el control
sigue mostrando el valor de la fila anterior. Si su aplicacin tiene alguna columna de tipo DateTime que
acepta valores nulos probablemente tenga que cambiar de control.

12.1.3

Paso 3: Recuperar datos

Ya tiene un DataSet con tipo y algunos controles TextBox enlazados con columnas de la tabla Orders del
DataSet. Si ejecuta el proyecto ver el formulario con sus etiquetas y cajas de texto, y las cajas de texto
mostrarn informacin del primer pedido en la tabla.
No hay nada mgico. Visual Studio aade automticamente cdigo para ayudar a recuperar la
informacin cuando aparece el formulario. Si examina el cdigo en el evento Load del formulario ver algo
similar a esto:
private void frmPedidos_Load(object sender, EventArgs e) {
// TODO: esta lnea de cdigo carga datos en la tabla 'dsNorthwind.Orders'
// Puede moverla o quitarla segn sea necesario.
this.ordersTableAdapter.Fill(this.dsNorthwind.Orders);
}

205

ADO.NET 2.0

Habr observado que en el Paso 2 al arrastrar la primera columna al formulario se cre una instancia del
TableAdapter correspondiente a la tabla Orders. Visual Studio ha observado que se est aadiendo una
columna de la tabla Orders del DataSet con tipo, de modo que aade una instancia del TableAdapter
correspondiente y automticamente aade cdigo al evento Load del formulario para llamar a su mtodo
Fill.
Como sugiere el comentario puede cambiar este cdigo como le convenga. De hecho sugiero que debe
cambiar el cdigo. Cuando se usa el Asistente de configuracin de origen de datos Visual Studio genera
automticamente un TableAdapter para cada tabla a la que se hace referencia, y estos TableAdapter no
hacen ningn filtrado, es decir, cuando se llama al mtodo Fill recuperan todas las filas de sus tablas
correspondientes.
Este mtodo puede funcionar bien en aplicaciones pequeas que interactan con tablas que contienen
un pequeo nmero de filas. Sin embargo, cuando el tamao de las tablas crece la aplicacin est pidiendo
ms informacin bloqueando la base de datos, usando ancho de banda adicional y consumiendo memoria
extra en el cliente, sin mencionar el aumento del tiempo de carga de la aplicacin.
Un mtodo mejor es aadir un filtro a la consulta. En este caso puede aadir una consulta parametrizada
al TableAdapter para solicitar solo los pedidos correspondientes a un cliente, despus de pedir al usuario el
ID o nombre del cliente, o seleccionar un cliente de una lista. En el captulo dedicado a los DataSet con tipo
y TableAdapter se explic como aadir este tipo de consulta. Cree en la base de datos un procedimiento
que utilice un parmetro de tipo char(5) para el identificador de cliente y devuelva los datos que necesita
nuestra aplicacin, y aada al TableAdapter una nueva consulta que utilice este procedimiento. En la pgina
Elija los mtodos que se van a generar llame a los mtodos cargaPorIdCliente y LeePorIdCliente. La
consulta para recuperar pedidos en base a este parmetro es simple porque la columna aparece tanto en la
tabla Orders como en la tabla Customers.
string idCliente = "ALFKI";
this.ordersTableAdapter.CargaPorIdCliente(this.dsNorthwind.Orders, idCliente);

Puesto que la consulta devuelve las mismas columnas que la original no es necesario cambiar la
estructura del DataTable. Esto tambin significa que Visual Studio no necesita generar lgica de
actualizacin basada en la consulta.

12.1.4

Paso 4: Recorrer los resultados

Visual Studio aade automticamente un control BindingNavigator al formulario cuando se arrastra la


primera columna de la ventana Origen de datos al formulario. Vamos a examinar este control.

Qu es un control BindingNavigator
El control BindingNavigator, una novedad de la versin 2.0 del marco de trabajo .NET, est diseado
para ayudar a los desarrolladores a crear aplicaciones que permitan a los usuarios recorrer los datos sin
tener que escribir grandes cantidades de cdigo. En las versiones 1.x los desarrolladores rutinariamente
tenan que aadir una serie de botones a formularios, y tenan que aadir cdigo a los eventos Click de los
botones para ir hacia delante y hacia atrs sobre las filas de datos disponibles. El proceso de aadir estos
botones, y el cdigo de los botones, era tedioso y los desarrolladores a menudo se encuentran escribiendo
el mismo cdigo una y otra vez. Gracias al control BindingNavigator los desarrolladores ya no necesitan
crear sus propios botones de navegacin.
El control BindingNavigator deriva del control ToolStrip. Contiene una serie de botones preconfigurados
que permiten a los usuarios desplazarse sobre los datos disponibles por medio de un objeto BindingSource,
adems de aadir y eliminar elementos.

Cmo funciona?
El control BindingNavigator expone una serie de propiedades AddNewItem, CountItem, DeleteItem,
MoveFirstItem, etc. del tipo ToolStripItem. De forma predeterminada, el BindingNavigator incluye un
ToolStripItem por cada una de estas propiedades.
El control escucha los eventos Click de estos ToolStripItem y realiza la accin correspondiente segn el
elemento pulsado. Por ejemplo, si el usuario pulsa el ToolStripItem contenido en la propiedad MoveFirstItem
del BindingNavigator, ste llama automticamente al mtodo MoveFirst del objeto BindingSource asociado.
El control BindingNavigator tambin escucha eventos difundidos por el BindingSource. Si la posicin del
BindingSource cambia, el BindingNavigator automticamente refleja el cambio, actualizando el rea del
control que muestra la posicin y el nmero de filas, as como activando o desactivando los controles de
navegacin segn la posicin actual.

Crear un BindingNavigator en tiempo de diseo


Puede simplemente arrastrar un control BindingNavigator desde la caja de herramientas al formulario.
Asociar este control con un BindingSource es igual de simple. Con el BindingNavigator seleccionado pulse
206

Escuela de Informtica del Ejrcito

la propiedad BindingSource en la ventana de propiedades y seleccione un BindingSource disponible en la


lista desplegable.

Crear un BindingNavigator en cdigo


Crear un BindingNavigator en cdigo es casi igual de fcil. Pase el BindingSource en el constructor:
BindingNavigator bnPedidos = new BindingNavigator(origenPedidos);
this.Controls.Add(bnPedidos);

12.1.5

Paso 5: Aadir y borrar elementos

El control BindingNavigator incluye automticamente botones que permiten que el usuario aada y borre
elementos. Es casi demasiado fcil. Pero existe mucho espacio para personalizacin.

Aadir lgica propia al evento Click de los ToolStripItem


En este formulario de introduccin de pedidos que estamos creando es conveniente que la fecha de los
nuevos pedidos sea la de hoy. Un mtodo posible es capturar el evento TableNewRow del DataTable y
generar el nuevo valor aqu. Una forma ms intuitiva es aadir cdigo al evento Click del botn Add del
BindingNavigator y asignar la fecha de hoy al TextBox vinculado con la columna OrderDate. Vamos a ver
una tercera forma, hbrida entre estas dos.
Cuando se lanza el evento Click el BindingNavigtor ya ha llamado al mtodo AddNew en el objeto
BindingSource. Esto significa que la propiedad Current del objeto BindingSource apunta a un objeto
DataRowView que contiene la nueva fila de datos. Podemos asignar la fecha de hoy a la columna
OrderDate del DataRowView y despus usar el mtodo ResetCurrentAction de BindingSource para alertar a
los controles vinculados de que hay nueva informacin disponible en el DataRowView:
private void bindingNavigatorAddNewItem_Click(object sender, EventArgs e) {
DataRowView filaActual = (DataRowView)this.bsPedido.Current;
filaActual["OrderDate"] = DateTime.Today;
this.bsPedido.ResetCurrentItem();
}

Lo mejor de este mtodo hbrido es que no interacta directamente con los controles vinculados. Es
decir, si comenzamos con un DateTimePicker y lo cambiamos por un TextBox, o al revs, este cdigo sigue
funcionando. Si modifica la propiedad del control directamente, tendr que cambiar el cdigo si cambia el
control.

Cambiar el comportamiento de un elemento en BindingNavigator


El control BindingNavigator incluye un botn Delete. Podemos modificar el comportamiento de este botn
para que muestre al usuario un dilogo de confirmacin de borrado del elemento actual. Parece sencillo,
salvo por un pequeo problema en el momento en que se ejecuta el cdigo el BindingNavigator ya ha
llamado implcitamente al mtodo RemoveCurrent del objeto BindingSource correspondiente. No hay una
forma sencilla de cancelar esta accin despus de que ha ocurrido. Sin embargo, hay una solucin simple.
El objeto BindingNavigator expone una propiedad DeleteItem. Siempre que se pulsa el ToolStripItem
asociado con esta propiedad el BindingNavigator llamar al mtodo RemoveCurrent del objeto
BindingSource correspondiente. Si se vaca esta propiedad se impide que el BindingNavigator llame
implcitamente al mtodo RemoveCurrent. Seleccione la propiedad DeleteItem del BindingNavigator en la
ventana de Propiedades, pulse sobre la flecha desplegable y seleccione (ninguno).
Ahora que ha impedido que el BindingNavigator elimine elementos del origen de datos, hay que
eliminarlos a mano. Pulse dos veces sobre el DeleteToolStripButton en el control BindingNavigator y Visual
Studio crear un procedimiento para controlar el evento Click del ToolStripButton. Puede aadir el siguiente
cdigo en este evento para mostrar un dilogo de confirmacin y eliminar el elemento actual solo si el
usuario pulsa el botn Si.
private void bindingNavigatorDeleteItem_Click(object sender, EventArgs e) {
DialogResult respuesta;
respuesta = MessageBox.Show("Seguro que quiere borrar este pedido",
"Confirmacin", MessageBoxButtons.YesNo);
if(respuesta == DialogResult.Yes)
this.bsPedido.RemoveCurrent();
}

Este mtodo se puede utilizar para modificar el comportamiento de cualquiera de los botones de
BindingNavigator.

12.1.6

Paso 6: Enviar cambios

Puede que haya observado el botn en el extremo derecho del BindingNavigator con un icono de un
disquete. Si coloca el cursor sobre este botn durante la ejecucin de la aplicacin ver un ToolTip con el
207

ADO.NET 2.0

texto Guardar datos. Pulsando este botn se guardan todos los cambios pendientes en la base de datos.
Vamos a ver cmo lo hace el BindingNavigator.
Si pulsa dos veces sobre el botn en tiempo de diseo ver el siguiente cdigo en el evento de
pulsacin:
this.Validate();
this.bsPedido.EndEdit();
this.ordersTableAdapter.Update(this.dsNorthwind.Orders);

En este mtodo la nica lnea que le resultar familiar es la ltima; la llamada al mtodo Update para
enviar los datos a la base de datos. La llamada al mtodo Validate del formulario provoca la validacin de
los datos. La llamad a EndEdit de BindingSource necesita una pequea explicacin.
Si ha modifica una fila de datos cambiando los contenidos de al menos un control vinculado y despus
llama al mtodo HasChanges del DataSet con tipo ver que devuelve False. El BindingSource no ha escrito
en la fila de datos los cambios hechos en los controles vinculados. El objeto BindingSource le permite
controlar si quiere escribir o descartar los cambios por medio de sus mtodos EndEdit y CancelEdit
respectivamente. El cambio a otra fila llamar implcitamente a EndEdit si ha hecho cambios en los
controles vinculados. Por tanto, antes de enviar cambios el cdigo del botn llama al mtodo EndEdit de
BindingSource.

Aadir Elementos a un control BindingNavigator


El control BindingNavigator no incluye en realidad un botn grabar. Visual Studio aade el
ToolStripButton y el cdigo que hemos examinado a la vez que aade el TableAdapter a la bandeja de
componentes cuando se arrastra la primera columna de informacin al formulario.
Le explico esto por dos motivos. Primero, para que sepa que no ver un botn grabar si crea un
BindingNavigator por si mismo. Segundo, para demostrar que se pueden aadir nuevos elementos a un
control BindingNavigator.
Para aadir un elemento a un control BindingNavigator seleccione el control en el diseador. Ver una
pequea flecha de desplegar a la derecha del ltimo elemento en el control. Pulse esta flecha y ver una
lista con los tipos de elemento disponibles.
Los diferentes tipos de elementos ofrecen diferentes propiedades y eventos. Una vez aadido su nuevo
elemento configure sus propiedades y aada controladores a los eventos adecuados.
En el caso de este ejemplo, necesitar dar nombre a los controles para que correspondan con el cdigo
del procedimiento ModoEdicionPedido que se mostrar en la siguiente seccin.
Vamos a aadir algunos elementos.

12.1.7

Paso 7: Aadir botones para Editar, Aceptar y Rechazar

La aplicacin que tenemos por ahora permite que el usuario empiece a modificar una fila sin entrar
explcitamente en modo de edicin. Los cambios hechos en los controles vinculados se escriben
implcitamente en la fila de datos cuando el usuario pasa a otra fila.
Este mtodo facilita al usuario la modificacin de un nmero grande de filas, pero puede que no sea el
mejor. Dependiendo de la aplicacin y de los usuarios a los que est destinada, puede que quiera que el
proceso de edicin sea ms explcito. Una forma sencilla de conseguirlo es obligar al usuario a pulsar un
botn Editar antas de modificar el contenido de la fila.
Vamos a aadir al formulario un mtodo llamado ModoEdicionPedido, que acepta un valor booleano para
indicar si el usuario puede modificar el pedido. Cuando el usuario se carga inicialmente, el evento Load
llama a este mtodo indicando que no se puede editar el pedido, lo que bloquea todos los controles
vinculados. El cdigo de ModoEdicionPedido pone la propiedad ReadOnly de los TextBox de las columnas
que admiten edicin a true o false segn el valor que recibe.
Desafortunadamente, no todos los controles tienen una propiedad ReadOnly. Sin embargo, la clase base
Control tiene una propiedad Enabled. Prefiero usar la propiedad ReadOnly de TextBox en lugar de Enabled
porque permite que el usuario seleccione el texto que contiene el control, aunque no le deje modificarlo.
Cuando se pone Enabled a false el usuario no puede seleccionar el control.
Si va a obligar al usuario a entrar explcitamente en modo de edicin, tambin debe obligarle a aceptar o
rechazar los cambios que haga. Aadiremos tambin los botones Aceptar y Rechazar. El cdigo en el
evento Click del botn Aceptar llama al mtodo EndEdit del objeto BindingSource para aceptar los cambios
en la fila actual y a ModoEdicionPedido para impedir ms cambios. El cdigo del evento Click del botn
Rechazar llama al mtodo CancelEdit para rechazar los cambios antes de llamar a ModoEdicionPedido para
impedir ms cambios.
208

Escuela de Informtica del Ejrcito

El procedimiento ModoEdicionPedido tambin modifica algunos elementos del control BindingNavigator.


Una vez que el usuario entra en modo de edicin, slo debe poder salir de este modo pulsando los botones
Aceptar o Rechazar. El procedimiento activa o desactiva los elementos responsables de la navegacin,
aadido, borrado y guardar cambios, dependiendo de si el usuario entra o sale del modo de edicin.
private void ModoEdicionPedido(bool Editar) {
//Las cajas de texto deben ser de solo lectura si no es edicin
tbIdPedido.ReadOnly = !Editar;
tbIDCliente.ReadOnly = !Editar;
tbIDEmpleado.ReadOnly = !Editar;
tbFechaPedido.ReadOnly = !Editar;
// Botones activados solo si no es edicin
bindingNavigatorAddNewItem.Enabled = !Editar;
bindingNavigatorDeleteItem.Enabled = !Editar;
bindingNavigatorMoveFirstItem.Enabled = !Editar;
bindingNavigatorMoveLastItem.Enabled = !Editar;
bindingNavigatorMoveNextItem.Enabled = !Editar;
bindingNavigatorMovePreviousItem.Enabled = !Editar;
bindingNavigatorPositionItem.Enabled = !Editar;
btEditar.Enabled = !Editar;
//Botn editar activo si no es edicin
btEditar.Enabled = !Editar;
//Aceptar y rechazar activados si est en edicin
btAceptar.Enabled = Editar;
btCancelar.Enabled = Editar;
}

Aadir esta funcionalidad solo precisa unos minutos, y vale la pena. Como resultado el comportamiento
de la aplicacin es mucho ms intuitivo. El usuario no quiere encontrar problemas debidos a cambios no
consignados al enviar los cambios. Adems, no es necesario pasar un mal rato intentando averiguar cundo
y cmo enva el objeto BindingSource los cambios al DataSet.

12.1.8

Paso 8: Ver datos hijos

Ahora nuestra aplicacin permite que los usuarios vean y modifiquen datos de la tabla Orders. Sin
embargo, esta funcionalidad no es especialmente til si no permitimos que vea y modifique los detalles de
estos pedidos.
Vamos a aadir a la aplicacin una tabla que muestra los detalles de un pedido. Al cambiar de un pedido
a otro la tabla mostrar solo los detalles relacionados. Lo que vamos a hacer para conseguirlo es:
1. Aadir al DataSet un DataTable para la informacin de detalle de pedido.
2. Aadir al TableAdapter una consulta parametrizada para obtener solo los detalles correspondientes a
un nico cliente y ejecute esta consulta cuando se carga el formulario.
3. Aadir al formulario un DataGridView para mostrar los detalles del pedido actual.
4. Aadir lgica al procedimiento del botn grabar para enviar los cambios en ambas tablas.

Aadir informacin de detalle de pedido al DataSet


Visual Studio 2005 hace que aadir datos desde una tabla relacionada resulte simple. Abra el DataSet
en el diseador, seleccione las columnas OrderID, ProductID, Quantity y UnitPrice de la tabla Order Details
en el Explorador de servidores y arrstrelas al diseador. De esta forma aadir la tabla Order Details
relacionada al DataSet.
El diseador del DataSet aade automticamente una DataRelation entre las dos tablas, pero esta
relacin no est asociada con una ForeignKeyConstraint. Pulse dos veces sobre la relacin en el diseador,
marque Tanto relacin como Foreign Key y cambie las reglas de actualizacin y de eliminacin a Cascade.

Aadir una consulta para recuperar los detalles para un cliente concreto
Al aadir las columnas de la tabla Order Details al DataSet se aade tambin un TableAdapter
configurado para llenar la tabla con estas columnas. La consulta del TableAdapter no aplica ningn filtro a la
consulta; recupera todas las filas de la tabla.
Aadir una consulta que recupere solo los detalles de pedido correspondientes a un cliente es fcil.
Pulse con el botn derecho sobre el TableAdapter y seleccione Agregar consulta. Puede usar el Generador
de consultas para aadir la tabla Orders y aadir un filtro a la columna CustomerID de la tabla Orders
usando un parmetro, o puede copiar la siguiente consulta:

209

ADO.NET 2.0

SELECT [Order Details].OrderID, [Order Details].Quantity, [Order Details].ProductID,


[Order Details].UnitPrice
FROM [Order Details]
INNER JOIN Orders ON [Order Details].OrderID = Orders.OrderID
WHERE Orders.CustomerID = @CustomerID

Llame a los nuevos mtodo CargaPorIDCliente y LeePorIDCliente y guarde los cambios en el DataSet.

Aadir un DataGridView que muestre datos hijos


Ahora examine el contenido del DataSet en la ventana de Orgenes de datos. Ver dos nodos Order
Details, uno al mismo nivel que el nodo Orders y otro hijo de este nodo Orders.
Si arrastra cualquiera de los nodos al formulario crear un TableAdapter para recuperar informacin de la
base de datos, un DataGridView para mostrar la informacin recuperada, y un BindingSource para gestionar
la interaccin entre la rejilla y los datos. La diferencia entre las dos posibilidades es cmo configura Visual
Studio las propiedades del objeto BindingSource. Si arrastra el nodo Order Details que est a la misma
altura que el nodo Orders, Visual Studio configura la propiedad Data Source del nuevo BindingSource con el
DataSet y la propiedad DataMember con el nombre del DataTable. Esta configuracin hace el que control
DataGridView muestre todas las filas de la tabla de detalle. Si arrastra el nodo Order Details que depende
del nodo Orders. Visual Studio asignar a la propiedad DataSource del nuevo BindingSource el
BindingSource de pedidos, y a la propiedad DataMember el nombre del DataRelation entre las tablas
Orders y Order Details en el DataSet. Esta configuracin hace que el control DataGridView muestre
solamente las filas de detalle correspondientes al pedido actual.

Cambiar el cdigo de TableAdapter.Fill en el evento Load del formulario


Anteriormente asociamos el DataRelation entre los DataTable Orders y Order Details con un
ForeignKeyConstraint. Como resultado, el DataSet ahora exige que el valor de la columna OrderID en las
filas de Order Details se corresponda con un valor del DataTable Orders.
Si ejecuta la aplicacin obtendr una ConstraintException. La excepcin se produce en la llamada al
mtodo Fill del TableAdapter para el DataTable Order Details. Visual Studio aadi esta lnea de cdigo
cuando se arrastr el nodo Order Details al formulario. Para impedir esta excepcin necesitamos dos cosas.
Primero, modificar el cdigo de modo que recupere solamente los detalles de pedido correspondientes a los
pedidos recuperados. Segundo, mover el cdigo que recupera la informacin de detalle de pedidos de modo
que se encuentre detrs de la llamada para recuperar informacin de pedidos. El cdigo resultante ser
similar a este:
string idCliente = "GROSR";
this.ordersTableAdapter.cargaPorIdCliente(dsNorthwind.Orders, idCliente);
this.order_DetailsTableAdapter.CargaPorIDCliente(
dsNorthwind.Order_Details, idCliente);
ModoEdicionPedido(false);

Enviar los cambios en ambas tablas


Ya hemos discutido las complejidades implicadas en el envo de cambios jerrquicos a una base de
datos. Bsicamente, hay que enviar las nuevas filas empezando por la parte superior de la jerarqua, pero
las borradas empezando por abajo. Como resultado, no basta con pasar todo el DataTable cuando se llama
al mtodo Update de los objetos TableAdapter.
Primero se envan los pedidos nuevos y las modificados; despus se pueden enviar todos los cambios
en los detalles de pedido, y finalmente los pedidos borrados. El cdigo del botn guardar puede ser similar a
este:
DataTable tablaPedidos = this.dsNorthwind.Orders;
DataTable tablaDetalles = this.dsNorthwind.Order_Details;
DataRow[] filas;
//Enva los pedidos nuevos o modificados
filas = tablaPedidos.Select("", "",
DataViewRowState.Added | DataViewRowState.ModifiedCurrent);
this.ordersTableAdapter.Update(filas);
//Enva los detalles de pedido nuevos o modificados
filas = tablaDetalles.Select("", "",
DataViewRowState.Added | DataViewRowState.ModifiedCurrent);
this.order_DetailsTableAdapter.Update(filas);
//Enva los detalles de pedido borrados
filas = tablaDetalles.Select("", "", DataViewRowState.Deleted);
this.order_DetailsTableAdapter.Update(filas);

210

Escuela de Informtica del Ejrcito

//Enva los pedidos borrados


filas = tablaPedidos.Select("", "", DataViewRowState.Deleted);
this.ordersTableAdapter.Update(filas);

En realidad el cdigo es ms complejo, con un bloque Try/Catch, registrando el nmero de cambios, etc.
El fragmento centrado se centra en la lgica necesaria para enviar los cambios de tablas relacionadas en el
orden adecuado.

12.1.9

Paso 9: Enlazar un segundo formulario al mismo origen de datos

Intentar vincular controles en varios formularios con un mismo origen de datos es complicado pero
posible. Antes de explicar cmo hacerlo, voy a tomar un breve desvo para mostrar cmo mejorar el
interface de usuario para ayudar incluso a usuarios los ms novatos a editar datos con facilidad.
El objetivo del paso 7 era hacer que el proceso de modificacin resultara lo ms explcito y predecible
posible. El control DataGridView no ayuda mucho en este aspecto. No puede obligar al usuario a
mantenerse en un mismo registro de la rejilla.
Este es un motivo para obligar a los usuarios a modificar los datos por medio de controles ms sencillos,
como TextBox o ComboBox. El otro es que estos controles son ms sencillos de gestionar. Si quiere aadir
cdigo para validar cambios hechos en un control y aplicar los cambios apropiados a controles relacionados
o cambiar el aspecto de controles en base a su contenido, por ejemplo poner los nmeros negativos en rojo,
este proceso es mucho ms directo con controles sencillos.
El DataGrid expone 170 eventos. Como ya mencion antes, DataGridView es una herramienta potente y
til. Con tiempo, paciencia y documentacin suficientes, posiblemente podra averiguar cmo conseguir el
nivel de control necesarios. Si lo hace, no deje de informarme.
Si necesita un control fino sobre cmo modifican los usuarios mltiples filas de datos, como la
informacin de detalle en el formulario que estamos creando, le recomiendo que use el DataGridView
solamente para presentacin. Puede impedir que el usuario modifique datos por medio del DataGridView
configurando sus propiedades AllowUserToAddRows y AllowUserToDeleteRows a false y su propiedad
ReadOnly a true. Tendr que proporcionar botones que permitan al usuario aadir, modificar o eliminar.
Cuando el usuario pulse uno de estos botones, muestre la fila en un formulario modal separado.
Los controles del formulario de detalle, que se muestra a la derecha,
estn vinculados con un objeto BindingSource. El formulario de detalle
expone una propiedad DetalleEditar a la que el formulario principal puede
asignar el BindingSource de detalle de pedido. El cdigo de la propiedad
asocia su BindingSource con el BindingSource del formulario principal. El
formulario principal muestra el formulario de detalle en un dilogo modal y
llama a los mtodos EndEdit o CancelEdit del BindingSource segn el
usuario pulse el botn Aceptar o el botn Cancelar. Esta forma de trabajo
mantiene los dos formularios sincronizados de una forma simple y fcil.

12.1.10 Paso 10: Mejorar el interface

Fig.13. 1:Detalle de Pedido

Ahora tenemos una aplicacin que permite a los usuarios ver y editar informacin de pedido para un
cliente, pero podemos hacer un par de cosas ms para mejorar la experiencia del usuario.
Si pueden elegir, la mayor parte de los usuarios prefieren ver informacin descriptiva en lugar de claves
en un formulario. Por ejemplo, el formulario hijo que permite al usuario modificar un elemento fuerza al
usuario a saber el valor de la clave que corresponde a un producto. Adems, el formato de los precios
unitarios y el total no parecen muy profesionales.
Vamos a ver cmo presentar los datos en un formato ms amistoso para el usuario. Las tres mejoras
principales son una caja combinada para empleados, mejor formato de los valores numricos y columnas
basadas en expresin, NombreProducto y TotalLinea, para mostrar valores calculados.

Aadir funcionalidad de bsqueda con un control ComboBox


En la nueva versin del formulario se reemplaza el TextBox que muestra el ID de empleado por un
control ComboBox que muestra su nombre. Aadir esta funcionalidad es muy sencillo. De hecho, solo hay
que modificar cuatro propiedades del control.
El formulario permitir que el usuario asocie un pedido con un empleado diferente, pero no modifica
valores en la tabla de empleados. Como no tenemos que preocuparnos de generar lgica de actualizacin
para la informacin de empleado podemos usar una consulta para construir nombres de empleado
amistosos usando las columnas FirstName y LastName de la tabla Employees:
SELECT EmployeeID, LastName + , + FirstName AS NombreEmpleado FROM Employees

211

ADO.NET 2.0

Primero hay que aadir informacin de empleados al DataSet. Pulse con el botn derecho en el
diseador de DataSet, pulse Agregar TableAdapter y utilice la consulta anterior. Como la consulta
NombreEmpleado de la consulta es calculado el asistente no ser capaz de generar la lgica para enviar
informacin nueva o modificada. Como el formulario no hace cambios en el DataTable de informacin de
empleados, esta limitacin no es un problema. Visual Studio aadir automticamente el DataTable de
empleados al DataSet.
Visual Studio reconoce la restriccin de clave externa que enlaza las tablas Orders y Employees y crea
un DataRelation entre los dos DataTable en el DataSet. Visual Studio no asocia el DataRelation con un
ForeignKeyConstraint, pero como el formulario no va a permitir a los usuarios modificar el contenido de la
tabla de empleados, no hay necesidad de crear este ForeignKeyConstraint. Por el mismo motivo no hay
necesidad de modificar las propiedades AutoIncrementSeed y AutoIncrementStep de la columna
EmployeeID.
Ahora que el DataSet contiene informacin de empleados, reemplazar el TextBox que muestra la
columna EmployeeID de la tabla Orders por un ComboBox que muestre la columna NombreEmpleado de la
tabla Employees es sencillo. Borre el TextBox y arrastre un ComboBox a su lugar. Pulse la pequea flecha
en la esquina superior derecha y marque Utilizar elementos enlazados a datos. El dilogo Tareas de
ComboBox se expande. En la lista Origen de datos expanda Otros orgenes de datos, Orgenes de datos
del proyecto, dsNorthwind y seleccione Employees. Esta accin aadir un BindingSource y un DataAdapter
al formulario, aadir una llamada al mtodo Fill en el evento Load del formulario y configurar la propiedad
DataSource del ComboBox con el nuevo BindingSource. Ahora en Mostrar miembro seleccione
NombreEmpleado y en miembro de valor EmployeeID. Finalmente en Miembro seleccionado seleccione la
columna EmployeeID del BindingSource de pedidos. Tendr que actualizar el mtodo ModoEdicionPedido
para activar y desactivar este ComboBox como corresponda.
Puede conseguir el mismo efecto en tiempo de programacin con un cdigo similar al siguiente:
this.cbEmpleado.DataSource = this.bsEmpleados;
this.cbEmpleado.DisplayMember = NombreEmpleado;
this.cbEmpleado.ValueMember = EmployeeID;
this.cbEmpleado.DataBindings.Add(SelectedValue, this.bsPedido, EmployeeID);

El resultado final de estas acciones es que el ComboBox mostrar el nombre de empleado


correspondiente al empleado asociado con el pedido actual, y esto permitir al usuario cambiar el valor de la
columna EmployeeID para el pedido actual seleccionando un nombre diferente en la lista.

Aadir columnas basadas en expresin


Posiblemente a la persona que use la aplicacin le gustara ver la cantidad total de cada lnea de pedido,
as como la del pedido completo. Podemos modificar las consultas del TableAdapter para que devuelvan
esta informacin, pero esto provocara dos problemas. Primero, el asistente de configuracin de
TableAdapter no ser capaz de generar la lgica de actualizacin para enviar cambios a la base de datos.
Segundo, los valores se calcularn una sola vez, cuando se ejecute la consulta, y no reflejar cambios en el
DataSet.
Como ya hemos visto, podemos usar la propiedad Expression de un DataColumn para crear columnas
cuyos valores se calculen en base a una expresin. La tabla Order Details contiene columnas para la
cantidad y el precio unitario. Podemos aadir un DataColumn llamado TotalProducto a la tabla para mostrar
el coste total del elemento de detalle asignando la expresin Quantity * UnitPrice a la propiedad
Expression. Tambin podemos aadir la columna TotalPedido a la tabla Orders para mostrar la suma de los
elementos de pedido usando la expresin Sum(Child(FK_Order_Details_Order).TotalProducto). El
DataType de ambas columnas debe ser System.Decimal.
Podemos aadir cada una de estas nuevas columnas al formulario. Para crear un TextBox para
TotalPedido arrastre la columna correspondiente desde la ventana de Orgenes de datos al formulario. Para
aadir la columna TotalProducto al DataGridView pulse con el botn derecho sobre la rejilla, seleccione
Agregar columna y seleccione la columna.

Controlar el formato de los datos vinculados


El tipo de datos de la nueva columna TotalPedido es System.Decimal. Como resultado, el TextBox
vinculado con esta columna mostrar el contenido usando el formato numrico estndar. Si el valor es
491,20 mostrar 491,2000. Vamos a cambiar el formato de esta columna de modo que se muestre como
moneda.
Con las versin 1.x del marco de trabajo .NET el cambio de presentacin de los datos requera aadir
cdigo para manejar el evento Format del objeto Binding creado al vincular el TextBox con la columna.
Usando la versin 2.0 y Visual Studio 2005 puede cambiar el formato fcilmente en tiempo de diseo.
212

Escuela de Informtica del Ejrcito

Vamos a ver cmo cambiar el formato del TextBox TotalPedido. Seleccione el TextBox en el formulario
en Visual Studio. Vaya a la ventana de propiedades y expanda la entrada (DataBindings), seleccione
(Avanzado) y pulse el botn elipsis () al a derecha. Se abrir el dilogo Formato y enlace de datos
avanzado. Simplemente, seleccione el tipo de formato Moneda. El TextBox mostrar su contenido usando la
configuracin de moneda de la mquina.
El DataGridView tambin permite especificar cadenas de formato para las columnas de la rejilla, aunque
esta posibilidad es difcil de descubrir. Seleccione el DataGridView en el formulario, pulse con el botn
derecho y seleccione Editar columnas. En el dilogo resultante seleccione la columna cuyo formato quiere
cambiar, seleccione la propiedad DefaultCellStyle y pulse el botn elipsis () a la derecha. En el dilogo
Generador de CellStyle seleccione la propiedad Format y pulse el botn elipsis. Finalmente, en el Cuadro de
dilogo de formato de cadenas especifique el formato deseado.
Si necesita ms control sobre el formato puede proporcionar su propio cdigo de formato y anlisis.
Anteriormente vimos cmo vincular la propiedad Text de un TextBox con una columna mediante cdigo.
tbIDPedido.DataBindings.Add(Text, dsPedido, OrderID);

El mtodo Add devuelve un objeto Binding que responde a los eventos del objeto BindingSource y
mueve datos en ambos sentidos entre el TextBox y la columna. La clase Binding expone dos eventos:
Format y Parse. El evento Format se lanza cuando el objeto Binding carga datos desde el origen de datos
en la propiedad con la que est vinculado. El evento Parse se lanza cuando el objeto Binding lee datos de la
propiedad vinculada y los asigna al origen de datos. Podemos usar estos dos eventos para cambiar el
formato de los datos mostrados en los controles TextBox. Este mtodo funciona mejor en escenarios de
formato y anlisis complejos, cuando las opciones disponibles en el dilogo de formato no son suficiente.
El siguiente fragmento de cdigo muestra cmo dar formato a los datos en el TextBox de total de pedido
como moneda. Utiliza una versin sobrecargada del mtodo ToString de la clase Decimal.
//using System.Globalization;
Binding b = tbTotalPedido.DataBindings.Add(Text, dsPedido, TotalPedido);
b.Format += new ConvertEventHandler(DecimalAMoneda);
b.Parse += new ConvertEventHandler(MonedaADecimal);
private void DecimalAMoneda(object sender, ConvertEventArgs e){
if(!e.DesiredType.Equals(typeof(string)))
return;
if(e.Value == DBNull.Value)
e.Value = ((Decimal)0).ToString(c);
else
e.Value = ((Decimal)e.Value).ToSTring(c);
}
private void MonedaADecimal(object sender, ConvertEventArgs e){
if(!e.DesiredType.Equals(typeof(Decimal)))
return;
e.Value = Decimal.Parse(e.Value.ToString(), NumberStyles.Currency, null);
}

12.1.11 Paso 11: Si quiere que salga bien


Vamos a hacer un pequeo alto y a mirar la aplicacin que hemos creado. Gracias a la vinculacin de
datos hace falta muy poco cdigo para permitir que los usuarios vean y editen datos de dos objetos
DataTable relacionados. Este es el objetivo de la vinculacin de datos proporcionar funcionalidad bsica
de modo que se puedan construir interfaces con un mnimo de cdigo.
Cuando vinculamos los controles iniciales tenemos poco control sobre la forma en que interactan con
los datos en el DataSet. En el paso 10 aadimos cdigo para controlar el formato de los datos en controles
TextBox vinculados, pero recuerde que mientras ms cdigo escriba, menos ventaja obtiene del uso de
controles vinculados.
Un ejemplo. He terminado el paso 10 y arrancado la aplicacin, buscando formas de mejorarla. He
descubierto que el formulario de edicin de detalle, el cambio de los datos de cantidad y precio unitario no
actualiza el total de producto. Empec a buscar una forma de actualizar automticamente el TextBox de
total de producto cuando cambian la cantidad y el precio unitario. Intent modificar el total en el evento
Leave de los controles TextBox de cantidad y precio. He intentado llamar al mtodo Refresh del objeto
BindingSource. He intentado usar los mtodos SuspendBinding y ResumeBinding del objeto BindingSource.
No importa lo que intente, no consigo el resultado que busco. Aunque creo que es posible lograr este efecto
usando vinculacin de datos, no es el tipo de escenario para el que est diseada esta capacidad.
213

ADO.NET 2.0

Mientras ms funcionalidad aado a la aplicacin por medio de cdigo, ms relego las caractersticas de
vinculacin de datos a tres tareas simples: navegacin sobre los registros disponibles, mostrar el contenido
de la fila actual en una serie de controles y guardar la entrada de usuario en la fila actual. Este no es un
cdigo demasiado difcil de escribir.
Si confa en su propio cdigo para mostrar datos en controles y escribir lo cambios en la estructura de
datos tomar el control de y la responsabilidad sobre la interaccin entre el interface de usuario y las
estructuras de datos. La aplicacin de ejemplo incluye un formulario llamado OrdersForm_DIY, donde DIS
significa do it yourself (hgalo Vd. mismo). El nico cdigo de vinculacin de datos en este formulario
implica mostrar los contenidos del pedido actual en el DataGridView y la lista de empleados en el
ComboBox. El resto de funcionalidad permitir que el usuario recorra los pedidos disponibles, mostrar el
pedido actual en los controles y aplicar cambios desde los controles en el pedido actual se basa en cdigo
DIY.
Aunque esta forma de trabajo precisa ms tiempo y codificacin, proporciona ms flexibilidad y control
sobre el comportamiento de la aplicacin.

12.2 Consideraciones de diseo de aplicaciones


Crear un interface til e intuitivo es solo una de las muchas facetas de la escritura de una aplicacin
Windows efectiva. Vamos a discutir algunas consideraciones de diseo importantes.

12.2.1 Recupere solo los datos que necesita


Al desarrollar su aplicacin es importante que tenga en cuenta cmo crecer la base de datos. Ejecutar
una consulta SELECT que recupere una tabla completa puede parecer bien cuando la aplicacin est en
desarrollo, pero al crecer el tamao de la tabla recuperar su contenido va tomando ms tiempo. Mientras
ms datos recupere, ms tiempo le tomar hacerlo.
Por ejemplo, nuestra aplicacin de introduccin de pedidos. Al iniciarse la aplicacin emite consultas
para recuperar informacin de todos los pedidos hechos por un cliente. Es correcto?. Tal vez sea una
sobrecarga. Puede que los usuarios de la aplicacin estn interesados en ver los pedidos que an no se
han enviado. O tal vez la aplicacin solo tenga que recuperar pedidos de un cliente concreto hechos en los
tres ltimos meses.
El entorno de la aplicacin puede ser tambin un factor para determinar qu datos recuperar. Puede ser
que el usuario necesite descargar datos en un porttil usando un mdem, consultarlos y modificarlos
desconectado y enviar sus modificaciones a la base de datos. En este caso puede ser atractivo ahorrar
ancho de banda, pero debido al entorno es necesario descargar todos los datos necesarios desde la base
de datos.

12.2.2 Estrategias de actualizacin


La aplicacin de ejemplo almacena los cambios y confa en el bloqueo optimista para enviarlos. Vamos a
ver otras posibilidades.

Actualizacin inmediata o almacenar cambios


El decidir si enviar los cambios inmediatamente o almacenarlos y enviarlos posteriormente depender de
cada aplicacin.
Cuando el usuario modifica un pedido en la aplicacin de ejemplo sta no enva el cambio
inmediatamente a la base de datos. La aplicacin confa en ADO.Net para almacenar los cambios hasta que
el usuario pulse Guardar.
Podemos cambiar la aplicacin con facilidad de modo que enve el cambio en un pedido en cuanto el
usuario pulse el botn Aceptar. Cuando el usuario pulse Editar la aplicacin permite al usuario modificar el
pedido; si el usuario pulsa Cancelar la aplicacin descarta los cambios. Si el usuario pulsa Aceptar la
aplicacin guarda los cambios usando el TableAdapter.
Una ventaja de trabajar con datos desconectados y almacenar los cambios es que la aplicacin no
necesita comunicarse con la base de datos con excesiva frecuencia. Sin embargo, mientras ms espere el
usuario para enviar los cambios, ms posibilidades hay de que otro usuario modifique los mismos datos en
la base de datos, lo que provocar el fallo del intento de actualizacin.
Para determinar lo que es adecuado para la aplicacin debe considerar las ventajas e inconvenientes de
cada mtodo. Si los usuarios van a gestionar pedidos por telfono con un inventario con poco movimiento,
es posible que base con almacenar los cambios. Pero este mtodo no vale para un sistema de reserva de
vuelos. No es conveniente que cuando se intente grabar el itinerario de un cliente resulte que mientras el
cliente consultaba su nmero de socio otro haya reservado el ltimo asiento en el vuelo de vuelta.

214

Escuela de Informtica del Ejrcito

Volver a leer antes de permitir cambios


Cuando se recuperan datos en una estructura desconectada como un DataSet los datos pueden quedar
obsoletos. El DataSet no lanza ningn evento cuando cambian las filas correspondientes en la base de
datos. Cuando el usuario intente enviar sus modificaciones a la base de datos puede encontrarse con que
otro usuario ha modificado los mismos datos, y su intento falla.
Vea nuestra aplicacin de ejemplo. Obtiene los datos cuando arranca la aplicacin. El usuario puede
pulsar el botn Editar segundos despus de arrancar; mientras ms tiempo est abierta la aplicacin, ms
desactualizados estn los datos. De hecho, el usuario puede esperar minutos u horas antes de modificar el
contenido de una fila.
En el momento en el que el usuario pulsa el botn Editar, otro usuario puede haber modificado la fila
correspondiente en la base de datos. Si est desarrollando una aplicacin que pueda encontrarse con esta
situacin puede ser interesante recuperar el contenido de la fila correspondiente desde la base de datos.
Para recuperar los contenidos de la fila puede crear un SqlDataAdapter o aadir una consulta a un
TableAdapter utilizando un parmetro con la clave nica de la tabla. Si ha configurado la propiedad
PrimaryKey el SqlDataAdapter o TableAdapter actualizar el contenido del DataRow con los datos de la
base de datos. Recuerde que esta consulta no genera una excepcin si otro usuario ha borrado la fila
correspondiente en la base de datos, simplemente, no devolver nada. El mtodo Fill tanto de
SqlDataAdapter como de TableAdapter devuelve un entero que indica el nmero de filas recuperadas de la
base de datos. Si devuelve 0 significa que la fila ya no existe en la base de datos. Puede capturar esta
situacin e informar al usuario de que la fila que quiere editar ya no existe.

ADO.NET y bloqueo pesimista


Un inconveniente de ser un optimista es que no siempre todo funciona como quiere. Imagine que es el
usuario de una aplicacin y que pulsa un botn para ejecutar una consulta y recuperar una fila de datos.
Despus de recuperar los resultados de la consulta modifica los datos en el formulario y pulsa un botn para
enviar los cambios. Si la aplicacin est utilizando actualizacin optimista los datos que quiere modificar no
estn bloqueados en el servidor, por lo que en el tiempo entre recuperacin y envo otro usuario puede
haber modificado los datos. Si los datos modificados aparecen en la clusula WHERE de la consulta
utilizada para la actualizacin el intento de actualizacin fallar. Puede usar bloqueo pesimista para
asegurar que una actualizacin nunca fallar por motivos de concurrencia.
El bloqueo pesimista es una caracterstica potente, pero algo peligrosa. Tenga cuidado, mucho cuidado.
Este es un tema avanzado pensado para desarrolladores que realmente comprenden el impacto del
bloqueo de datos en sus servidores. Solo unas pocas aplicaciones, como los sistemas de reserva de billetes
en lnea, precisan bloqueo pesimista.
No recomiendo usar bloqueo pesimista de forma general para evitar intentos de actualizacin fallidos. En
la mayora de aplicaciones es mejor que algn intento de actualizacin falle de tarde en tarde que tener
consultas de recuperacin de datos que fallan porque el servidor est bloqueado.
La actualizacin usando bloqueo pesimista implica bloquear la fila en la base de datos antes de modificar
su contiendo, lo que asegura que nadie podr modificar estos datos. No existe ningn mtodo o propiedad
en ADO.NET que permita establecer bloqueos en el servidor, por lo menos en la versin 2.0 y anteriores.
Puede lograr el bloqueo pesimista por medio de objetos Transaction. Sin embargo, posiblemente
necesitar algo ms que una simple consulta SELECT dentro de la transaccin. El bloqueo de filas en la
base de datos al usar una consulta SELECT dentro de una transaccin depende del gestor de base de
datos, el tipo de consulta y el nivel de aislamiento de la transaccin.
El nivel de aislamiento de transaccin controla cmo afecta el trabajo hecho en una transaccin al que se
hace en otras. SQL Server usa de forma predeterminada lectura consignada. Con este nivel de
aislamiento las filas se bloquean una vez que se modifican en la transaccin. La simple recuperacin de una
fila en una consulta SELECT no la bloquea. Sin embargo, si se usan los niveles lectura repetible o
serializable se bloquean las filas recuperadas en una sentencia SELECT.
Algunas bases de datos permiten el uso de recomendaciones de bloqueo en una consulta. Con SQL
Server puede lanzar la siguiente consulta para bloquear datos en una transaccin independientemente del
nivel de aislamiento:
SELECT CustomerID, CompanyName, ContactName, Phone FROM Customers
WITH (UPDLOCK) Where CustomerID = ALFKI

Consulte la documentacin de su base de datos para averiguar qu tipos de niveles de aislamiento de


transaccin y recomendaciones de bloqueo soporta.
215

ADO.NET 2.0

El siguiente fragmento bloquea en modo pesimista una fila de un servidor SQL Server usando un objeto
SqlTransaction y recomendaciones de bloqueo en la consulta SELECT. Tan pronto como el cdigo recupera
los resultados de la consulta se bloquea la fila de datos en el servidor.
string cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind; +
Integrated Security=True;
string consulta = SELECT CustomerID, CompanyName FROM Customers WITH (UPDLOCK) +
WHERE CustomerID = ALFKI;
using(SqlConnection conexin = new SqlConnection(cadcon)){
conexin.Open();
using(SqlTransaction transaccin = conexin.BeginTransaction()){
SqlCommand comando = new SqlCommand(consulta, conexin, transaccin);
SqlDataAdapter adaptador = new SqlDataAdapter(comando);
DataTable tabla = new DataTable();
adaptador.Fill(tabla);
DataRow fila = tabla.Rows[0];
fila[CompanyName] = Modificado;
adaptador.Update(tabla);
transaccin.Rollback();
}
conexin.Close();
}

12.2.3 Estrategias de conexin


Puede elegir entre dos estrategias de conexin. La ms adecuada depender de las necesidades de su
aplicacin.

Conectar y desconectar
La forma ms sencilla de conectar con la base de datos es permitir que los objetos SqlDataAdapter y
TableAdapter abran la conexin implcitamente. Estos objetos abren implcitamente la conexin cuando se
llama a sus mtodos Fill y Update y la cierran al terminar. Es el mtodo ms simple, pero no siempre el
mejor.
El agrupamiento de conexiones puede ayudar a limitar el nmero de conexiones utilizado por este
comportamiento de gestin de conexiones implcito. Por ejemplo, suponga que tiene dos SqlDataAdapter
que recuperan datos de tablas separadas de la base de datos. El cdigo que ejecuta para construir los dos
adaptadores usa la misma cadena de conexin. Este cdigo crea objetos SqlConnection separados. Si
llama al mtodo Fill de ambos adaptadores y examina la traza de perfil de SQL Server ver que el cdigo
usa una sola conexin con la base de datos.
Vamos a ver paso a paso qu ha pasado. Cada llamada al mtodo Fill abre implcitamente el
SqlConnection asociado con el SelectCommand correspondiente y lo cierra tras recuperar los resultados de
la consulta. Al llamar al mtodo Fill del primer SqlDataAdapter se establece una nueva conexin fsica con
la base de datos. Cuando el SqlDataAdapter cierra esta implcitamente el objeto SqlConnection que utiliza la
conexin fsica, el cdigo de agrupamiento de conexiones del proveedor de datos .NET almacena la
conexin fsica en lugar de cerrarla. El segundo SqlDataAdapter reutiliza esta conexin fsica para ejecutar
su consulta antes de devolverla al agrupamiento.
Dicho esto, sigue siendo recomendable asociar los SqlDataAdapter con un mismo SqlConnection y abrir
y cerrar este objeto explcitamente. Aunque el mecanismo de Fill resulte til con conexiones separadas y
gestin de conexin implcita, el escenario de Update es ms complejo y es ms probable que encuentre
problemas.
Imagine el mismo escenario dos SqlDataAdapter con SqlConnection diferentes pero con la misma
cadena de conexin pero ahora lo que quiere es enviar cambios de ambos DataTable en una misma
transaccin. Si est usando un SqlTransaction, sencillamente no funcionar. Necesita abrir explcitamente el
SqlConnection antes de comenzar la transaccin, y no puede compartir un SqlTransaction entre varios
SqlConnection incluso aunque estn conectados con la misma base de datos.
Podra usar System.Transactions y el cdigo funcionara, pero puede que no haga lo que espera. Puede
crear un objeto TransactionScope y que ste implcitamente abra un SqlConnection dentro de su alcance. Si
el SqlConnection es el nico participante en la transaccin, hasta ahora, y est hablando con SQL Server
2005, esto crear implcitamente una transaccin local (SqlTransaction) en lugar de una distribuida. De
hecho, este es el comportamiento que se obtiene cuando se llama al mtodo Update del primer
SqlDataAdapter. Debido a la lgica interior del cdigo de agrupamiento de SqlConnection la llamada a
Update sobre el segundo adaptador no reutilizar la conexin fsica establecida por el primero, sino que
establecer una segunda conexin fsica. La transaccin local creada por el primer SqlDataAdapter se
promover a distribuida y la segunda conexin fsica se enrolar en la transaccin distribuida.
216

Escuela de Informtica del Ejrcito

En cambio, si ambos SqlDataAdapter usan el mismo SqlConnection y lo abre explcitamente antes de


llamar a los mtodos Fill o Update de los adaptadores, obtendr un mejor rendimiento y unos resultados
ms predecibles. Este es el mtodo que recomiendo.

Agrupamiento de conexiones
El agrupamiento de conexiones puede mejorar mucho el rendimiento de las aplicaciones multicapa. De
hecho, como este agrupamiento est activado de forma predeterminada estar aprovechando sus ventajas
sin darse cuenta. Las conexiones se reutilizan solo si coinciden tanto la cadena de conexin como las
credenciales.
Algunos desarrolladores confan en su base de datos para imponer la seguridad en sus aplicaciones
multicapa. Como los componentes de capa media usan las credenciales de usuario para conectar con la
base de datos, las aplicaciones que usan este mtodo no aprovechan el agrupamiento de conexiones. Para
obtener todo el beneficio del agrupamiento de conexiones debe hacer que los componentes de capa media
utilicen sus propias credenciales. Utilice seguridad de red para asegurarse de que solo los usuarios con
credenciales adecuadas pueden acceder a la capa media, en lugar de hacer que la base de datos se
encargue de determinar qu usuario tiene qu derechos sobre los componentes.
Aunque el agrupamiento de conexiones est pensado especialmente para aplicaciones multicapa
tambin puede mejorar el rendimiento de una simple aplicacin de dos capas. Cuando la aplicacin de
ejemplo cierra su objeto SqlConnection, implcita o explcitamente, la conexin fsica con la base de datos
se mantiene en el agrupamiento de conexiones. Si la aplicacin abre de nuevo el objeto SqlConnection
antes de que la conexin supere su tiempo de espera en el agrupamiento, se reutiliza.
Si est trabajando con el proveedor de datos .NET para cliente SQL y no quiere usar agrupamiento de
conexiones en su aplicacin aada el siguiente atributo en su cadena de conexin:
Pooling=False;

Los desarrolladores que trabajan con el proveedor de datos .NET para OLE DB pueden usar el siguiente
atributo para asegurar que sus conexiones no se agrupan cuando se cierra el objeto OleDbConnection.
OLE DB Services=-4;

12.2.4 Trabajar con datos BLOB


Obtendr mejor rendimiento almacenando sus archivos BLOB en archivos de datos en su servidor y
registrando la ubicacin de estos archivos en su base de datos. Los sistemas operativos estn mejor
adaptados a trabajar con archivos; almacenar los mismos datos en la base de datos es menos eficiente. Por
ejemplo, SQL Server divide los datos BLOB mayores de 8Kb en mltiples pginas en la base de datos. Esto
significa que almacenar el contenido de un archivo de 40Kb implica separar el contenido de este archivo en
cinco partes separadas.
Aunque no soy aficionado a almacenar datos BLOB en bases de datos comprendo que resulta atractivo.
Almacenar unos datos en la base de datos y otros en archivos aumenta el nmero de tecnologas implicado.
La seguridad y las copias de seguridad de los datos se complican.
En caso de que decida almacenar datos BLOB en su base de datos, estos consejos le ayudarn a
trabajar con ellos en ADO.NET.

Retrasar la recuperacin de BLOB


Si su consulta recupera unos centenares de filas e incluye columnas BLOB quiere realmente recuperar
todos estos datos junto con los resultados de su consulta?. Las columnas BLOB de SQL Server pueden
contener hasta 2GB de datos. Sabe qu cantidad de datos BLOB va a recuperar su consulta?.
Una forma de mejorar el rendimiento de su aplicacin es evitar leer los datos BLOB de la base de datos
hasta que los necesite. Recupere primero los datos no BLOB, y deje la recuperacin de datos BLOB hasta
que los necesite. Esta tcnica es especialmente til cuando el usuario accede solamente a los datos BLOB
de la fila con la que est trabajando.

Manejo de BLOB en objetos DataSet


El acceso a los contenidos de una columna BLOB en un DataSet y su modificacin es inmediato.
ADO.NET almacena los textos BLOB como cadenas y los BLOB binarios como arrays de bytes. El objeto
DataRow no incluye mtodos para recuperar partes de un campo BLOB; tendr que recuperar o modificar
todo el contenido de la columna.
Un BLOB de texto se trata como cualquier otra columna de texto.

217

ADO.NET 2.0

DataRow fila;
string cadBlob;
//Inicializa el objeto DataRow
//...
//Accede al contenido de un BLOB de texto
cadBlob = (string)fila[CampoBLOBTexto];
//Modifica el contenido de un BLOB de texto
Fila[CampoBLOBTexto] = unaCadenaLarga;

Igualmente, los BLOB binarios se tratan como columnas binarias menores:


DataRow fila;
Byte[] campoBinario;
//Inicializa el objeto DataRow
//...
//Accede al contenido de un BLOB de texto
cadBlob = (Byte[])fila[CampoBLOBBinario];
//Modifica el contenido de un BLOB de texto
Fila[CampoBLOBBinario] = unArrayDeBytes;

Manejo de BLOB mediante DataReader


El objeto DataReader le ofrece dos opciones: puede acceder a los contenidos de una columna BLOB de
una sola vez, o puede recuperar la columna por partes.
El siguiente fragmento utiliza una nica llamada al DataReader para recuperar los contenidos de una
columna BLOB:
SqlCommand cmd;
SqlDataReader lector;
int numColumnaBlobTexto, numColumnaBlobBinaria;
string blobTexto;
Byte[] blobBinario;
//Inicializa el SqlCommand
//...
lector = comando.ExecuteReader(CommandBehavior.SequentialAccess);
while(lector.Read()){
blobTexto = lector.GetString(numColumnaBlobTexto);
blobBinario = (Byte[])lector[numColumnaBlobBinaria]
}
lector.Close();

El ejemplo anterior recupera el contenido de la columna BLOB de texto usando el mtodo con tipo
GetString, pero el BLOB binario lo recupera indexando el lector y moldeando el resultado al tipo array de
bytes. El objeto DataReader expone un mtodo GetBytes, pero devuelve por partes, no de una sola vez.
Las columnas BLOB pueden ser muy grandes. Almacenar todo el contenido de una columna BLOB en
una sola cadena o un solo array de bytes puede no ser la mejor idea si la columna contiene un par de
cientos de megabytes de datos. En estos casos es mejor recuperar los datos BLOB por fragmentos, escribir
el contenido en disco y acceder al contenido cuando sea adecuado.
El objeto DataReader expone dos mtodos GetBytes y GetChars que le permiten recuperar datos
BLOB por partes. El siguiente fragmento muestra cmo puede usar el mtodo GetBytes para recuperar el
contenido de una columna BLOB binaria de un DataReader en fragmentos de 8Kb y escribir los datos en un
archivo. Puede seguir la misma lgica para recuperar datos BLOB de texto usando el mtodo GetChars.
//using System.IO;
SqlCommand comando;
SqlDataReader lector;
int numColumnaBlobBinaria = 1;
int tamaoBloque = 8192;
int desplazamiento = 0;
int numBytesDevueltos;
Byte[] arrayBytes = new Byte[tamaoBloque];
string rutaArchivo = @C:\BytesLeidos.bin;
FileStream archivoSalida = new FileStream(rutaArchivo, FileMode.Create);
//Inicializa el SqlCommand
//...

218

Escuela de Informtica del Ejrcito

lector = comando.ExecuteReader(CommandBehavior.SequentialAccess);
lector.Read();
do{
numBytesDevueltos = (int)lector.GetBytes(numColumnaBlobBinaria, desplazamiento,
arrayBytes, 0, tamaoBloque);
if(numBytesDevueltos > 0)
archivoSalida.Write(arrayBytes, 0, numBytesDevueltos);
desplazamiento += numBytesDevueltos;
}while(numBytesDevueltos == tamaoBloque);
archivoSalida.Close();
lector.Close();

219

ADO.NET 2.0

220

Escuela de Informtica del Ejrcito

13 Otros proveedores de datos .NET


Hasta ahora nos hemos centrado en el uso de ADO.NET para conectar con bases de datos SQL Server
usando SqlClient. La versin 2.0 del marco de trabajo .NET incluye otros tres proveedores de datos:
OracleClient, ODBC y OleDb.
ADO.NET 2.0 incluye un nuevo punto de inicio para interactuar con diferentes proveedores de datos la
clase DbProviderFactories as como un conjunto de clases base compartidas por todos los proveedores
que se han actualizado al modelo ADO.NET 2.0. Este captulo comenzar tratando este conjunto de
caractersticas comunes antes de describir los tres proveedores de datos .NET indicados.
Los proveedores de datos .NET a los que se hace referencia en el archivo machine.config deben
aadirse al cach de ensamblados global de modo que estn disponibles por medio del modelo de factora
de proveedor. Para proveedores que no estn disponibles en machine.config puede aadir manualmente
una entrada al archivo de configuracin de la aplicacin e incluir el ensamblado correspondiente en la ruta
de la aplicacin.
Este captulo no est pensado para ser ledo entero, sino para consultar el adaptador que le interese, por
eso algunos de los ejemplos son prcticamente idnticos, cambiando solamente los detalles propios del
proveedor que se trate en cada momento.

13.1 El modelo de factora de proveedor


ADO.NET 2.0 incluye un modelo de factora de proveedor, as como clases base comunes para las
diferentes clases de ADO.NET. Antes de examinar las nuevas caractersticas vamos a ver brevemente por
qu los interfaces disponibles en la versin 1.x eran mejorables.

13.1.1 Limitaciones de los interfaces comunes de ADO.NET


Todas las clases Connection de las versiones iniciales de ADO.NET implementaban un interface comn
IDbConnection. Poda moldear cualquier clase Connection al interface IDbConnection y realizar tareas
comunes, como configurar la propiedad ConnectionString o abrir o cerrar la conexin llamando a los
mtodos Open o Close, sin saber exactamente qu clase Connection estaba usando. ADO.NET 1.0 inclua
interfaces para muchas clases IDbCommand, IDbDataAdapter, IDataParameter, IDataReader, etc.
Aunque estos interfaces eran tiles en muchos escenarios, tienen bastantes limitaciones.

Los interfaces no son fciles de extender


En la versin 1.0 del marco de trabajo .NET no haba forma de determinar si un objeto DataReader
contena filas antes de llamar al mtodo Read. Muchos desarrolladores ASP.NET queran manejar una
consulta que no devolva filas como un caso especial. Llamar al mtodo Read para determinar si la consulta
devolva filas causaba problemas cuando se vinculaban controles ASP.NET con el DataReader, haciendo
que la primera fila de datos, si la haba, no apareciera en los controles vinculados.
Microsoft aadi la propiedad HasRows a todas las clases DataReader en la versin 1.1 del marco de
trabajo .NET. Pero no hay forma de comprobar esta propiedad sin conocer qu proveedor de datos est
utilizando. Todas las clases DataReader implementan el interface IDataReader de la versin 1.0, pero la
propiedad no se aadi al interface en la versin 1.1. Por qu no?. Los interfaces no son fciles de
extender porque no hay una forma de ofrecer una implementacin predeterminada de las nuevas
caractersticas; por tanto, todas las aplicaciones compiladas confiando en la versin 1.0 fallaran al intentar
compilar con la 1.1.
La nica forma de extender un modelo basado en interfaces como este es introducir nuevos interfaces
cada vez que se quiere aadir una caracterstica al modelo comn. Las primeras compilaciones de la
versin 1.1 del marco de trabajo .NET incluan un interface IDataReader2, que se elimin antes de la
distribucin definitiva. De haber continuado con este modelo, la lista de interfaces habra ido creciendo con
cada nueva versin de la plataforma.

ADO.NET 1.1 no proporciona una forma de crear instancias de algunas clases


Una vez que tiene una instancia de una clase Connection puede moldearla al interface IDbConnection y
crear una instancia de la clase Command correspondiente al proveedor de datos usando el mtodo
CreateCommand. De forma similar puede crear objetos Parameter llamando al mtodo CreateParameter del
interface IDbCommand.
Sin embargo, no hay forma independiente del proveedor de crear instancias de clases Connection,
DataAdapter o CommandBuilder en ADO.NET 1.1.

ADO.NET 1.1 no ofrece un medio de determinar los proveedores disponibles


La versin 1.1 de ADO.NET no nos permite averiguar qu proveedores de datos .NET estn disponibles.

221

ADO.NET 2.0

13.1.2 Solucin mediante el modelo de factora de proveedor


Vamos a ver ahora cmo el modelo de factora de proveedor y las clases base abstracta introducidas por
ADO.NET 2.0 solucionan estas limitaciones de ADO.NET 1.x.

Extender el modelo ADO.NET mediante clases base abstractas


Aunque en ADO.NET 2.0 siguen existiendo los interfaces ADO.NET 1.0, el modelo de objetos de
ADO.NET 2.0 sigue un mtodo ligeramente diferente clases base abstractas. Todas las clases Connection
de ADO.NET 2.0 descienden de la clase base abstracta DbConnection.
Las clases base abstractas proporcionan un medio ms extensible para aadir funcionalidad a los
modelos de objetos comunes en versiones futuras. Al aadir un mtodo nuevo a la clase base, se puede
incluir una implementacin predeterminada, lo que hace que las aplicaciones compiladas con la versin
anterior sigan siendo vlidas con la posterior, a la vez que permite que las clases descendientes sustituyan
la implementacin predeterminada por la suya propia.

Crear objetos mediante la clase DbProviderFactory


ADO.NET 2.0 permite crear instancias de todas las clases de ADO.NET, con dos excepciones menores
que veremos enseguida, por medio de la clase DbProviderFactory. El nombre de esta clase puede recordar
a algunos desarrolladores el patrn factora, un patrn muy usado en aplicaciones y marcos de trabajo. Una
vez que se tiene una referencia de un objeto factora, puede usar este objeto para crear instancias de otras
clases.
Todos los proveedores de datos .NET que soportan el modelo ADO.NET 2.0 incluyen una clase factora
que permite crear instancias de otras clases. Una vez que ha accedido a la clase factora de su proveedor,
pude crear instancias de las otras clases sin necesidad de saber qu proveedor de datos est usando.
No puede usar un objeto DbFactory para crear instancias de clases DbDataReader o DbTransaction. En
el primer caso, deber crear un objeto DbCommand y llamar a su mtodo ExecuteReader; en el segundo
deber crear un objeto DbConnection y llamar a su mtodo BeginTransaction.
El siguiente ejemplo crea un objeto SqlDataAdapter y otros objetos relacionados para llenar un
DataTable con los resultados de una consulta.
string cadcon = @Data Source=.\SQLExpress;Initial Catalog=Northwind;
Integrated Security=True;;
string consulta = SELECT CustomerID, CompanyName FROM Customers;
//Usa el objeto esttico DbProviderFactory del proveedor Cliente Sql
DbProviderFactory factora = SqlClientFactory.Instance;
//Crea un objeto Connection y configura la cadena de conexin
DbConnection conexin = factora.CreateConnection();
conexin.ConnectionString = cadcon;
//Crea un objeto Command, lo asocia con la conexin y configura CommandText
DbCommand comando = factora.CreateCommand();
comando.Connection = conexin;
comando.CommandText = consulta;
//Crea un objeto DataAdapter y configura SelectCommand
DbDataAdapter adaptador = factora.CreateDataAdapter();
adaptador.SelectCommand = comando;
//Usa el DataAdapter para rellenar una tabla
DataTable tabla = new DataTable();
adaptador.Fill(tabla);

La nica referencia al proveedor de datos concreto se hace al acceder al objeto factora; todo el resto del
cdigo es independiente de proveedor.
Una vez que se tiene el objeto factora puede llamar a sus diferentes mtodos Create para crear objetos
Connection, ConnectionStringBuilder, Command, DataAdapter, Parameter y CommandBuilder. Tambin
puede crear enumeradores de origen de datos, que le permiten indicar los orgenes de datos disponibles, si
la propiedad CanCreateDataSourceEnumerator devuelve true.
Acceder a factoras de proveedor. Hay tres formas de acceder a objetos factora de proveedor usando
ADO.NET. Si a la hora de escribir el cdigo sabe qu proveedor de datos quiere usar, llame a la propiedad
esttica Instance de la clase factora, como en el ejemplo. Todas las clases factora del marco de trabajo
.NET incluyen esta propiedad.
Si al escribir el cdigo no sabe que proveedor de datos debe utilizar, puede acceder a la clase factora
del proveedor usando el nombre invariante de proveedor. El nombre invariante es el espacio de nombres
correspondiente al proveedor, y se puede proporcionar en tiempo de ejecucin:
222

Escuela de Informtica del Ejrcito

DbProviderFactory factora;
Factora = DbProviderFactories.GetFactory(System.Data.SqlClient);

El mtodo GetFactory est sobrecargado, por lo que existe una tercera forma de acceder a una factora,
que trataremos en breve.

Descubrir los proveedores disponibles


La clase DbProviderFactory usada en el ejemplo anterior tambin sirve como mecanismo de
descubrimiento, permitiendo descubrir los proveedores de datos .NET disponibles por medio del mtodo
GetFactory. La clase DbProviderFactory expone un mtodo esttico GetFactoryClasses; este mtodo
devuelve un DataTable que contiene informacin sobre las diferentes clases factora de proveedor.
Una vez que ha obtenido este DataTable puede mostrar informacin sobre las diferentes clases factora.
El DataTable contiene una fila por factora e incluye columnas que describen el nombre del proveedor, su
descripcin, su nombre invariante y el nombre calificado de la clase factora.
DataTable tabla = DbProviderFactories.GetFactoryClasses();
foreach(DataRow fila in tabla.Rows){
Console.WriteLine("Nombre
= {0}", fila["Name"]);
Console.WriteLine("Descripcin
= {0}", fila["Description"]);
Console.WriteLine("Nombre invariante = {0}", fila["InvariantName"]);
Console.WriteLine("Nombre calificado = {0}", fila["AssemblyQualifiedName"]);
Console.WriteLine();
}

Si quiere permitir que el usuario seleccione el proveedor de datos .NET que quiere usar, puede utilizar la
informacin devuelta por el mtodo GetFactoryClasses para mostrar informacin sobre los proveedores
disponibles. Una vez que el usuario selecciona el proveedor deseado puede usar el DataRow
correspondiente para acceder a la clase factora de este proveedor.
El mtodo GetFactoryClasses puede no devolver informacin sobre todos los proveedores de datos
.NET instalados en la mquina. Este mtodo examina solo el contenido del elemento DbProviderFactories
de la seccin system.data del archivo de configuracin de la aplicacin.
<system.data>
<DbProviderFactories>
<add name="Odbc Data Provider" invariant="System.Data.Odbc"
description=".Net Framework Data Provider for Odbc"
type="System.Data.Odbc.OdbcFactory, System.Data, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add name="OleDb Data Provider" invariant="System.Data.OleDb"
description=".Net Framework Data Provider for OleDb"
type="System.Data.OleDb.OleDbFactory, System.Data, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add name="OracleClient Data Provider" invariant="System.Data.OracleClient"
description=".Net Framework Data Provider for Oracle"
type="System.Data.OracleClient.OracleClientFactory, System.Data.OracleClient,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add name="SqlClient Data Provider" invariant="System.Data.SqlClient"
description=".Net Framework Data Provider for SqlServer"
type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</DbProviderFactories>
</system.data>

No hay garanta de que todos estos proveedores estn disponibles por medio de la configuracin de la
aplicacin. Un desarrollador puede aadir la siguiente entrada en el archivo de configuracin de la
aplicacin para que el proveedor de datos .NET para OLE DB deje de estar disponible por medio de los
mtodos GetFactoryClasses o GetFactory.
<system.data>
<DbProviderFactories>
<remove invariant="System.Data.OleDb" />
</DbProviderFactories>
</system.data>

Nada exige que el instalador de un proveedor de datos .NET aada entradas al archivo machine.config
de su ordenador. Puede que necesite aadir entradas al archivo de configuracin del ordenador o de la
aplicacin si quiere acceder a otros proveedores de datos .NET usando los mtodos GetFactoryClasses o
GetFactory.

223

ADO.NET 2.0

13.1.3 Limitaciones del modelo de factora


Aunque este modelo es un paso adelante importante, tiene algunas limitaciones.

Muchas construcciones de consulta son especficas de base de datos


No todas las bases de datos soportan las mismas construcciones de consulta. Por ejemplo, puede usar
una clusula TOP en una consulta contra una base de datos SQL Server o Access, pero Oracle no la
soporta. El modelo de factora de proveedor no est diseado para ayudarle a construir consultas
independientes de servidor.

La configuracin de CommandText para consultar parametrizadas puede precisar


cdigo especfico del servidor
Diferentes proveedores de datos .NET soportan construcciones diferentes para los parmetros de
consulta. Por ejemplo, los proveedores de datos .NET para SQL Server y Oracle soportan parmetros con
nombre, mientras que los proveedores para ODBC y OLE DB slo soportan marcadores de parmetro. Si
quiere construir una consulta parametrizada que devuelve el valor de la columna CompanyName en la tabla
Customers dado un valor para la columna CustomerID, el valor de la propiedad CommandText depender
de la clase Command que est usando:
SqlCommand.CommandText:
SELECT CompanyName FROM Customers WHERE CustomerID = @IDCliente

OracleCommand.CommandText:
SELECT CompanyName FROM Customers WHERE CustomerID = :IDCliente

OleDbCommand.CommandText u OdbcCommand.CommandText:
SELECT CompanyName FROM Customers WHERE CustomerID = ?

El modelo de factora de proveedor casi ayuda a determinar cmo generar el valor de la propiedad
CommandText. Si examina el contenido de la columna ParameterMarkerFormat en el esquema
DataSourceInformation que devuelve un objeto Connection puede usar esta informacin para generar la
cadena adecuada para el parmetro usando el mtodo esttico Format de la clase String. Los proveedores
de datos .NET para ODBC y OLE DB informan de un formato de marcador de parmetro ?, mientras que
el de Oracle indica que su marcador es :{0}. Si usa el formato de marcador de parmetro en cdigo como
el siguiente:
String.Format(formatoMarcadorParametro, "IDCliente");

el resultado ser ?, ? y :IDCliente respectivamente para los proveedores de datos para ODBC, OLE DB
y Oracle. Cada uno de estos valores se corresponde con el que se debe utilizar en CommandText.
Desafortunadamente, el proveedor de datos .NET para SQL Server indica como formato de marcador de
parmetro {0} en lugar de @{0}. Puede esquivar este problema mediante una funcin que trate este caso
como caso especial:
string FormatoMarcadorParametro(string nombreInvarianteProveedor, string cadcon,
string nombreParametro){
if(nombreInvarianteProveedor.ToUpper() == "SYSTEM.DATA.SQLCLIENT")
return "@{0}";
DbProviderFactory factora =
DbProviderFactories.GetFactory(nombreInvarianteProveedor);
DataTable tabla;
using(DbConnection conexin = factora.CreateConnection()){
conexin.ConnectionString = cadcon;
conexin.Open();
tabla = conexin.GetSchema(DbMetaDataCollectionNames.DataSourceInformation);
conexin.Close();
return (string)tabla.Rows[0]["ParameterMarkerFormat"];
}
}

Para especificar tipos de datos de parmetro puede ser necesario cdigo especfico
del proveedor.
Si construye su consulta parametrizada usando las clases base comunes de proveedor es posible que
de todas formas necesite cdigo especfico del proveedor cuando establezca los tipos de datos de los
parmetros.
Hay dos formas independientes de proveedor de configurar el tipo de datos de un parmetro. Puede dar
un valor a la propiedad Value y pedir a la clase parmetro que averige el tipo de datos en base al valor.
Tambin puede utilizar la propiedad DbType expuesta por la clase DbParameter. La propiedad DbType
224

Escuela de Informtica del Ejrcito

acepta un valor de la enumeracin del mismo nombre, que se encuentra en el espacio de nombres
System.Data, e incluye valores para muchos tipos de datos especficos de .NET.
La clase parmetro de cada proveedor expone una propiedad tipo especfica del proveedor que se
puede configurar con una enumeracin especfica. En algunos casos necesitar configurar explcitamente la
propiedad tipo de datos especfica del proveedor. Por ejemplo, si quiere usar el proveedor de datos .NET
para Oracle y obtener los resultados por medio de un cursor REF de Oracle, necesitar configurar la
propiedad OracleType del objeto OracleParameter como OracleType.Cursor.
Puede encontrarse en situaciones en las que est construyendo una consulta parametrizada y no est
seguro de qu valor establecer para la propiedad tipo especfica de proveedor. En la mayora de los casos
el parmetro se corresponde con una columna en una tabla de la base de datos. Puede usar ADO.NET para
recuperar informacin de esquema sobre la columna. Use un Command cuyo CommandText haga
referencia a la columna o columnas deseadas. Pase CommandBehavior.SchemaOnly al mtodo
ExecuteReader para recuperar informacin de esquema para la consulta sin ejecutarla realmente. A
continuacin use el mtodo GetSchemaTable del objeto DataReader resultante para acceder a un
DataTable que contiene informacin de esquema detallada para las columnas de la consulta. Ahora puede
moldear el contenido de la columna de tipo del proveedor con la enumeracin especfica.
string cadcon = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +
"Integrated Security=True";
string consulta = "SELECT * FROM [Order Details]";
DataTable tabla;
using(SqlConnection conexin = new SqlConnection(cadcon)){
SqlCommand comando = new SqlCommand(consulta, conexin);
conexin.Open();
using(SqlDataReader lector = comando.ExecuteReader(CommandBehavior.SchemaOnly)){
tabla = lector.GetSchemaTable();
lector.Close();
}
conexin.Close();
}
foreach(DataRow fila in tabla.Rows)
Console.WriteLine("{0}:\tSqlDbType.{1}", fila["ColumnName"],
(SqlDbType)fila["ProviderType"]);

Si no est seguro de qu proveedor de datos est usando no sabr qu propiedad especfica de tipo
tiene la clase parmetro, ni qu enumeracin debe usar. Puede determinar la propiedad usando reflexin y
buscando la propiedad que incluye un atributo DbProviderSpecificTypeProperty. A continuacin puede
determinar la enumeracin comprobando el tipo de retorno de esta propiedad. El siguiente cdigo usa este
mtodo para mostrar la propiedad tipo especfica de la clase parmetro de cada uno de los proveedores de
datos .NET disponibles por medio de DbProviderFactories.GetFactoryClasses.
Type tipoAtributo = typeof(DbProviderSpecificTypePropertyAttribute);
foreach(DataRow fila in DbProviderFactories.GetFactoryClasses().Rows){
Console.WriteLine("{0}", fila["Name"]);
DbProviderFactory factora = DbProviderFactories.GetFactory(fila);
DbParameter parmetro = factora.CreateParameter();
Type tipoParametro = parmetro.GetType();
PropertyInfo propiedadEspecificaProveedor = null;
Type enumeracionEspecificaProveedor = null;
foreach(PropertyInfo pi in tipoParametro.GetProperties())
if(pi.GetCustomAttributes(tipoAtributo, true).Length != 0) {
propiedadEspecificaProveedor = pi;
enumeracionEspecificaProveedor = pi.PropertyType;
break;
}
Console.WriteLine("\tLa propiedad tipo especfica de proveedor es {0}",
propiedadEspecificaProveedor.Name);
Console.WriteLine("\t5 se convierte en {0}.{1}", propiedadEspecificaProveedor.Name,
Enum.GetName(enumeracionEspecificaProveedor, 5));
Console.WriteLine();
}

13.1.4 Descubrimiento de esquema de base de datos


En la versin 1.0 de ADO.NET la nica clase de base de datos que ofrece capacidad de descubrimiento
de esquema de base de datos por medio de su API es OleDbConnection. Puede usar el mtodo
GetOleDbSchemaTable para obtener tablas que contienen informacin sobre diferentes esquemas tablas,
225

ADO.NET 2.0

columnas, ndices, etc. Tambin puede proporcionar valores para restringir la informacin de esquema
devuelta, por ejemplo, para devolver solo las columnas de una tabla concreta.
Todas las clases Connection en ADO.NET 2.0 exponen un mtodo GetSchema que puede usar para
consultar informacin de esquema. Clases Connection diferentes exponen diferentes esquemas. Por
ejemplo, OracleConnection expone un esquema Sequences que no est disponible por medio de otras
conexiones. Algunos esquemas solo estn disponibles para versiones especficas de una base de datos.
Por ejemplo, un SqlConnection conectado con una base de datos SQL Server 2005 expondr un esquema
UserDefinedTypes, que no est disponible si conecta con una base de datos SQL Server 2000.

Consultar los esquemas disponibles


Puede determinar los esquemas disponibles llamando al mtodo GetSchema sin parmetros. La primera
columna (CollectionName) contiene la lista de nombres de los esquemas que soporta el objeto Connection.
Puede pasar estos nombres como cadena al mtodo GetSchema.

Filtrar los datos devueltos por GetSchema


Suponga que el proveedor que est usando permite consultar informacin de columna. Habr casos en
los que querr recuperar informacin sobre todas las columnas disponibles en la base de datos. Un
escenario ms habitual es solicitar informacin de columnas de una nica tabla.
Una de las sobrecargas de GetSchema acepta un array de cadenas que sirve como filtro o restriccin de
la consulta que ejecutar el objeto Connection para recuperar informacin de esquema. Por ejemplo, el
esquema de columnas de OdbcConnection acepta un array de cadenas con cuatro elementos que
corresponden al catlogo de tablas, esquema de tabla, nombre de tabla y nombre de columna, en este
orden. Un valor null en una entrada del array hace que la conexin ignore esta entrada al generar el filtro.
El siguiente cdigo crea un array de cadenas que puede pasar al mtodo GetSchema para recuperar
solo las columnas de la tabla Order Details que se encuentra en el catlogo Northwind Traders y el
esquema dbo.
string[] filtro = new string[]{"Northwind", "dbo", "Order Details", null};
DataTable tablaColumnas;
string cadcon = @"Driver={SQL Server};Server=.\SQLExpress;" +
"Database=Northwind;Trusted_Connection=True";
DbProviderFactory factora = OdbcFactory.Instance;
using(DbConnection conexin = factora.CreateConnection()){
conexin.ConnectionString = cadcon;
conexin.Open();
tablaColumnas = conexin.GetSchema("Columns", filtro);
conexin.Close();
}
foreach(DataRow fila in tablaColumnas.Rows)
Console.WriteLine("{0,-10}: {1}" fila["COLUMN_NAME"], fila["TYPE_NAME"]);

El mtodo GetSchema es definitivamente til, pero el problema est en averiguar cmo es el array de
restriccin para un esquema concreto; y la respuesta es, con otro esquema.
Existe un esquema de restricciones que devuelve informacin sobre las restricciones disponibles en
diferentes esquemas. Puede aadir el DataTable con esta informacin y el DataTable de informacin sobre
los esquemas al mismo DataSet y establecer un DataRelation basado en la columna comn entre las dos
tablas CollectionName. Puede usar este DataRelation para encontrar las restricciones disponibles para un
esquema dado.
El siguiente ejemplo acepta un nombre de proveedor y una cadena de conexin y usa esta informacin
para construir un objeto Connection. A continuacin recupera informacin sobre los esquemas y
restricciones disponibles. Usando el patrn descrito anteriormente el cdigo muestra informacin sobre los
esquemas y restricciones disponibles.
void ListaEsquemasYRestricciones(string nombreProveedor, string cadcon) {
DataTable tablaEsquemas, tablaRestricciones;
DbProviderFactory factora = DbProviderFactories.GetFactory(nombreProveedor);
using(DbConnection conexin = factora.CreateConnection()) {
conexin.ConnectionString = cadcon;
conexin.Open();
tablaEsquemas = conexin.GetSchema();
tablaRestricciones = conexin.GetSchema("Restrictions");
conexin.Close();
}

226

Escuela de Informtica del Ejrcito

DataSet ds = new DataSet();


tablaEsquemas.TableName = "Esquemas";
tablaRestricciones.TableName = "Restricciones";
ds.Tables.Add(tablaEsquemas);
ds.Tables.Add(tablaRestricciones);
DataRelation relacin = ds.Relations.Add(tablaEsquemas.Columns["CollectionName"],
tablaRestricciones.Columns["CollectionName"]);
foreach(DataRow filaEsquema in tablaEsquemas.Rows) {
Console.WriteLine("Esquema {0}", filaEsquema["CollectionName"]);
DataRow[] filasRestriccion = filaEsquema.GetChildRows(relacin);
if(filasRestriccion.Length != 0) {
Console.WriteLine("\tRestricciones:");
foreach(DataRow restriccin in filasRestriccion)
Console.WriteLine("\t\t{0}: {1}", restriccin["RestrictionNumber"],
restriccin["RestrictionName"]);
} else
Console.WriteLine("\tSin restricciones");
Console.WriteLine();
}
}

13.2 Proveedor de datos ODBC


ADO.NET incluye un proveedor de datos diseado para comunicar con bases de datos usando
controladores ODBC. Si quiere acceder a una base de datos que no tiene su propio proveedor, y no est
usando una base de datos Access, use este proveedor.
El proveedor de datos .NET para ODBC no era parte de la distribucin inicial del marco de trabajo .NET,
pero ser integr en la versin 1.1, y desde entonces se encuentra en el ensamblado System.Data.dll.
Los ejemplos de esta seccin asumen que ha incluido al comienzo de su archivo la sentencia using
System.Data.Odbc.

13.2.1 Conectar con la base de datos


Para conectar con su base de datos usando el proveedor de datos .NET ODBC se utiliza la clase
OdbcConnection. La siguiente lista muestra ejemplos de cadenas de conexin que puede usar para
conectar con su base de datos. Para informacin ms concreta consulte la documentacin de la propiedad
ConnectionString de la clase OdbcConnection y la documentacin del controlador ODBC que vaya a utilizar.
Conectar con una base de datos SQL Server con nombre de usuario y contrasea:
Driver={SQL Server};Server=.\SQLExpress;
Database=Northwind;UID=NombreUsuario;PWD=Contrasea;

Conectar con una base de datos SQL Server usando una conexin de confianza:
Driver={SQL Server};Server=.\SQLExpress;
Database=Northwind;Trusted_Connection=Yes;

Conectar con una base de datos usando un nombre de origen de datos (DSN) de ODBC:
DSN=OrigenDeDatos;

Conectar con una base de datos usando un DSN de archivo de ODBC:


FileDSN=OrigenDeDatosArchivo;

El siguiente ejemplo conecta con la base de datos Northwind en la instancia local de SQL Express:
string cadcon = @"Driver={SQL Server};Server=.\SQLExpress;Database=Northwind;" +
"Trusted_Connection=Yes";
using(OdbcConnection conexin = new OdbcConnection(cadcon)){
conexin.Open();
conexin.Close();
}

13.2.2 Ejecutar consultas


Como todas las clases que derivan de DbCommand, la clase OdbcCommand ofrece un mtodo
ExecuteReader que devuelve los resultados de una consulta en forma de DataReader. El mtodo
ExecuteReader acepta valores de la enumeracin CommandBehavior. Tambin puede devolver un valor
nico usando el mtodo ExecuteScalar o ejecutar una consulta sin manejar resultados usando el mtodo
ExecuteNonQuery.
La clase OdbcCommand no ofrece soporte para ejecucin asncrona de consultas, ni la posibilidad de
recuperar valores usando tipos especficos de proveedor como SqlTypes.
227

ADO.NET 2.0

Examinar los resultados de una consulta usando OdbcDataReader


Los OdbcDataReader se crean igual que los DataReader de otros proveedores llamando al mtodo
ExecuteReader.
string cadcon = @"Driver={SQL Server};Server=.\SQLExpress;Database=Northwind;" +
"Trusted_Connection=Yes";
string consulta = "SELECT CustomerID, OrderID, OrderDate FROM Orders " +
"WHERE CustomerID = 'ALFKI' ORDER BY OrderDate";
using(OdbcConnection conexin = new OdbcConnection(cadcon)) {
using(OdbcCommand comando = new OdbcCommand(consulta, conexin)){
conexin.Open();
using(OdbcDataReader lector = comando.ExecuteReader()){
while(lector.Read())
Console.WriteLine("{0} {1} {2:d}", lector.GetString(0),
lector.GetInt32(1), lector.GetDateTime(2));
lector.Close();
}
conexin.Close();
}

Consultas parametrizadas
El proveedor de datos .NET para ODBC soporta consultas parametrizadas utilizando parmetros
posicionales en lugar de parmetros con nombre. En la cadena de consulta utilice el carcter ? para indicar
un parmetro, y aada los objetos OdbcParameter necesarios a la coleccin Parameter en el orden
correspondiente.
La mayor parte de los desarrolladores proporcionan valores significativos para la propiedad Name de sus
objetos parmetro. Esta prctica es til porque simplifica el proceso de localizar una entrada en la coleccin
de parmetros, pero no tiene ningn efecto sobre la asignacin entre objetos OdbcParameter y marcadores
de parmetro en la consulta.
string cadcon = @"Driver={SQL Server};Server=.\SQLExpress;Database=Northwind;" +
"Trusted_Connection=Yes";
string consulta = "SELECT CustomerID, OrderID, OrderDate FROM Orders " +
"WHERE CustomerID = ? ORDER BY OrderDate";
using(OdbcConnection conexin = new OdbcConnection(cadcon)) {
using(OdbcCommand comando = new OdbcCommand(consulta, conexin)) {
comando.Parameters.AddWithValue("@CustomerID", "ALFKI");
conexin.Open();
using(OdbcDataReader lector = comando.ExecuteReader()) {
while(lector.Read())
Console.WriteLine("{0} {1} {2:d}", lector.GetString(0),
lector.GetInt32(1), lector.GetDateTime(2));
lector.Close();
}
}
conexin.Close();
}

Llamar a un procedimiento almacenado


La clase OdbcCommand no soporta los valores Table o StoredProcedure de la enumeracin
CommandType. Si quiere llamar a un procedimiento almacenado usando el proveedor de datos .NET para
ODBC debe aprender la sintaxis CALL e ODBC. Afortunadamente, esta sintaxis es sencilla:
{? = CALL NombreProcedimiento(?, ?, ?)}

Se utiliza la palabra clave CALL antes del nombre del procedimiento almacenado. Si quiere pasar
parmetros para el procedimiento, independientemente de si son de entrada, de salida o ambos, se usa el
marcador de parmetro ?. Los marcadores de parmetro se separan con coma y la lista se encierra entre
parntesis. Si quiere capturar el valor de retorno se antecede la palabra clave CALL por los caracteres ? =.
Todo ello va entre llaves. La direccin de los parmetros se indicar mediante la propiedad
ParameterDirection de los objetos OdbcParameter.
string cadcon = @"Driver={SQL Server};Server=.\SQLExpress;Database=Northwind;" +
"Trusted_Connection=Yes";
string consulta = "{CALL CustOrdersOrders(?)}";
using(OdbcConnection conexin = new OdbcConnection(cadcon)) {
using(OdbcCommand comando = new OdbcCommand(consulta, conexin)) {
comando.Parameters.AddWithValue("@CustomerID", "ALFKI");
conexin.Open();

228

Escuela de Informtica del Ejrcito

using(OdbcDataReader lector = comando.ExecuteReader()) {


while(lector.Read())
Console.WriteLine("{0} {1:d}", lector.GetInt32(0), lector.GetDateTime(1));
lector.Close();
}
}
conexin.Close();
}

13.2.3 Recuperar los resultados de una consulta


Si quiere recuperar los resultados de una consulta y almacenarlos en un objeto DataTable o DataSet use
el mtodo Fill de la clase OdbcDataAdapter.
string cadcon = @"Driver={SQL Server};Server=.\SQLExpress;Database=Northwind;" +
"Trusted_Connection=Yes";
string consulta = "SELECT CustomerID, OrderID, OrderDate FROM Orders "
+ "WHERE CustomerID = ? ORDER BY OrderDate";
OdbcDataAdapter adaptador = new OdbcDataAdapter(consulta, cadcon);
adaptador.SelectCommand.Parameters.AddWithValue("@CustomerID", "ALFKI");
DataTable tabla = new DataTable("Pedidos");
adaptador.Fill(tabla);
foreach(DataRow fila in tabla.Rows)
Console.WriteLine("{0} {1} {2:d}", fila["CustomerID"], fila["OrderID"],
fila["OrderDate"]);

13.2.4 Recuperar informacin de esquema


La clase OdbcConnection soporta los mtodos GetSchema que le permiten consultar la informacin de
esquema disponible en la base de datos. Este mtodo sobrecargado se comporta como los de otros
proveedores de datos .NET.

Listar esquemas y restricciones


Anteriormente vimos cmo mostrar la lista de esquemas y restricciones dado un nombre de proveedor y
una cadena de conexin usando como punto inicial la clase DbProviderFactories. Si est trabajando con el
proveedor de datos .NET para ODBC puede simplificar este cdigo para usar la clase OdbcConnection
como punto inicial.
string cadcon = @"Driver={SQL Server};Server=.\SQLExpress;" +
"Database=Northwind;Trusted_Connection=Yes";
DataTable tablaEsquemas, tablaRestricciones;
using(OdbcConnection conexin = new OdbcConnection(cadcon)) {
conexin.Open();
tablaEsquemas = conexin.GetSchema();
tablaRestricciones = conexin.GetSchema("Restrictions");
conexin.Close();
}
DataSet ds = new DataSet();
tablaEsquemas.TableName = "Esquemas";
tablaRestricciones.TableName = "Restricciones";
ds.Tables.Add(tablaEsquemas);
ds.Tables.Add(tablaRestricciones);
DataRelation relacin = ds.Relations.Add(tablaEsquemas.Columns["CollectionName"],
tablaRestricciones.Columns["CollectionName"]);
foreach(DataRow filaEsquema in tablaEsquemas.Rows) {
Console.WriteLine("Esquema {0}", filaEsquema["CollectionName"]);
DataRow[] filasRestriccion = filaEsquema.GetChildRows(relacin);
if(filasRestriccion.Length != 0) {
Console.WriteLine("\tRestricciones:");
foreach(DataRow restriccin in filasRestriccion)
Console.WriteLine("\t\t{0}: {1}", restriccin["RestrictionNumber"],
restriccin["RestrictionName"]);
} else
Console.WriteLine("\tSin restricciones");
Console.WriteLine();
}

Devolver informacin de esquema filtrada


La salida del ejemplo anterior indica que la conexin soporta el esquema Columns, y que soporta
restricciones para catlogo, esquema y nombre de la tabla y nombre de la columna, en este orden. Por
tanto, es sencillo recuperar la informacin sobre columnas de una tabla concreta. Simplemente, cree un
229

ADO.NET 2.0

array de cadenas de la misma longitud describa por el esquema de restricciones, proporcione valores para
las restricciones correspondientes y null para las que no le interesen y pase este array al mtodo
GetSchema junto con el nombre del esquema, en este caso Columns:
string[] filtro = new string[] { "Northwind", "dbo", "Order Details", null};
DataTable tablaColumnas;
string cadcon = @"Driver={SQL Server};Server=.\SQLExpress;" +
"Database=Northwind;Trusted_Connection=Yes";
using(OdbcConnection conexin = new OdbcConnection(cadcon)){
conexin.Open();
tablaColumnas = conexin.GetSchema("Columns", filtro);
conexin.Close();
}
foreach (DataRow fila in tablaColumnas.Rows)
Console.WriteLine("{0,-10} - {1}", fila["COLUMN_NAME"], fila["TYPE_NAME"]);

13.3 Proveedor de datos OLE DB


ADO.NET incluye un proveedor de datos diseado para comunicar con bases de datos usando
proveedores OLE DB. Recomiendo usar este proveedor solo si est usando bases de datos Access.
Si existe un proveedor especfico para su base de datos utilice este en lugar del proveedor OLE DB,
puesto que lo ms probable es que su rendimiento sea muy superior y tendr caractersticas propias de la
base de datos. Usando el proveedor OLE DB se incurre en una significativa penalizacin de rendimiento,
puesto que cada vez que el proveedor se comunique con la base de datos tiene que pasar por la capa de
interoperabilidad COM.
Es sorprendente el nmero de proveedores de datos .NET creados por fabricantes de bases de datos,
como IBM, Oracle o Sybase, proveedores de software independientes, como DataDirect, y proyectos de
fuente abierta, por lo que pocas veces ser necesario recurrir al proveedor OLE DB.
De hecho, el nico proveedor OLE DB disponible hoy en da que puede ser necesario usar desde
ADO.NET posiblemente sea el proveedor OLE DB Jet 4.0; actualmente no existe un proveedor de datos
.NET para Access, y el controlador OLE DB se considera ms fiable que su versin ODBC, adems de
ofrecer soporte para la recuperacin del ltimo valor de identidad generado en una conexin.
Los ejemplos hasta ahora han utilizado la instalacin local de SQL Express. Los siguientes utilizarn esta
misma base de datos, por coherencia y para no suponer que tiene instalado Access y la base de datos
Northwind para Access; pero ya sabemos por la explicacin anterior que este no es uso normal de este
proveedor de datos.

13.3.1 Conectar con su base de datos


Para conectar con su base de datos usando el proveedor de datos OLE DB se usa la clase
OleDbConnection. Estos son algunos ejemplos de cadenas de conexin que puede usar con esta clase.
Para ms informacin vea la documentacin de la propiedad ConnectionString de la clase OleDbConnection
y la del proveedor OLE DB que vaya a utilizar.
Conectar con una base de datos SQL Server usando nombre de usuario y contrasea.
Provider=SQLOLEDB;Data Source=.\SQLExpress;Initial Catalog=Northwind;
User ID=NombreUsuario;Password=Contrasea;

Conectar con una base de datos SQL Server usando una conexin de confianza
Provider=SQLOLEDB;Data Source=.\SQLExpress;Initial Catalog=Northwind;
Integrated Security=SSPI;

Conectar con una base de datos Access.


Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Data\Nwind.MDB;

Conectar con una base de datos Access con seguridad Access.


Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Data\Nwind.MDB;
Jet OLEDB:System Database=C:\Data\System.mdw;
User ID=NombreUsuario;Password=Contrasea;

Conectar con una base de datos Access protegida por contrasea


Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Data\Nwind.MDB;
Jet OLEDB:Database Password=Contrasea;

Conectar con una base de datos usando un archivo de enlace de datos OLE DB
File Name=ArchivoUDL.UDL;

230

Escuela de Informtica del Ejrcito

13.3.2 Ejecutar consultas


Como todas las clases que derivan de DbCommand, la clase OleDbCommand ofrece un mtodo
ExecuteReader que devuelve el resultado de una consulta en forma de DataReader. El mtodo
ExecuteReader acepta valores de la enumeracin CommandBehavior. Tambin puede devolver un nico
valor usando el mtodo ExecuteScalar o ejecutar una consulta sin manejar los resultados usando el mtodo
ExecuteNonQuery.
La clase OleDbCommand no ofrece soporte para ejecucin asncrona de consultas, ni tiene la capacidad
de recuperar valores usando tipos especficos de proveedor, como SqlTypes.

Examinar el resultado de una consulta con OleDbDataReader


Los OleDbDataReader se crean igual que el resto de los DataReader llamando al mtodo
ExecuteReader de OleDbCommand:
string cadcon = @"Provider=SQLOLEDB;Data Source=.\SQLExpress;" +
"Initial Catalog=Northwind;Integrated Security=SSPI";
string consulta = "SELECT CustomerID, OrderID, OrderDate FROM Orders " +
"WHERE CustomerID='ALFKI' ORDER BY OrderDate";
using(OleDbConnection conexin = new OleDbConnection(cadcon)){
using(OleDbCommand comando = new OleDbCommand(consulta, conexin)){
conexin.Open();
using(OleDbDataReader lector = comando.ExecuteReader()){
while(lector.Read())
Console.WriteLine("{0} {1} {2:d}", lector.GetString(0),
lector.GetInt32(1), lector.GetDateTime(2));
lector.Close();
}
conexin.Close();
}
}

Consultas parametrizadas
El proveedor de datos .NET para OLE DB, igual que el de ODBC, soporta consultas parametrizadas
usando parmetros posicionales en lugar de parmetros con nombre. En la cadena de consulta se usa el
marcador de parmetro ? y se aade el OleDbParameter correspondiente a la coleccin Parameters del
objeto OleDbCommand.
La mayora de los programadores dan valores significativos a la propiedad Name de sus objetos
parmetro. Esto es til para simplificar la localizacin de una entrada en la coleccin de parmetros, pero no
afecta a la forma en que OleDbCommand asigna los parmetros de la coleccin a los marcadores de
parmetro en la consulta.
string cadcon = @"Provider=SQLOLEDB;Data Source=.\SQLExpress;" +
"Initial Catalog=Northwind;Integrated Security=SSPI";
string consulta = "SELECT CustomerID, OrderID, OrderDate FROM Orders " +
"WHERE CustomerID=? ORDER BY OrderDate";
using (OleDbConnection conexin = new OleDbConnection(cadcon)){
using (OleDbCommand comando = new OleDbCommand(consulta, conexin)) {
comando.Parameters.AddWithValue("@idCliente", "ALFKI");
conexin.Open();
using (OleDbDataReader lector = comando.ExecuteReader()){
while (lector.Read())
Console.WriteLine("{0} {1} {2:d}", lector.GetString(0),
lector.GetInt32(1), lector.GetDateTime(2));
lector.Close();
}
conexin.Close();
}
}

Llamar a un procedimiento almacenado


La clase OleDbCommand soporta el valor StoredProcedure de la enumeracin CommandType, por lo
que no es necesario usar ninguna sintaxis especial en la cadena de consulta para llamar a un procedimiento
almacenado.

231

ADO.NET 2.0

string cadcon = @"Provider=SQLOLEDB;Data Source=.\SQLExpress;" +


"Initial Catalog=Northwind;Integrated Security=SSPI";
using(OleDbConnection conexin = new OleDbConnection(cadcon)) {
using(OleDbCommand comando = new OleDbCommand("CustOrdersOrders", conexin)) {
comando.CommandType = CommandType.StoredProcedure;
comando.Parameters.AddWithValue("@idCliente", "ALFKI");
conexin.Open();
using(OleDbDataReader lector = comando.ExecuteReader()) {
while(lector.Read())
Console.WriteLine("{0} {1:d}", lector.GetInt32(0), lector.GetDateTime(1));
lector.Close();
}
conexin.Close();
}
}

13.3.3 Recuperar resultados de una consulta


Si quiere recuperar los resultados de una consulta y almacenarlos en un objeto DataTable o DataSet use
el mtodo Fill de la clase OleDbDataAdapter.
string cadcon = @"Provider=SQLOLEDB;Data Source=.\SQLExpress;" +
"Initial Catalog=Northwind;Integrated Security=SSPI";
string consulta = "SELECT CustomerID, OrderID, OrderDate FROM Orders " +
"WHERE CustomerID = ? ORDER BY OrderDate";
OleDbDataAdapter adaptador = new OleDbDataAdapter(consulta, cadcon);
adaptador.SelectCommand.Parameters.AddWithValue("@idCliente", "ALFKI");
DataTable tabla = new DataTable();
adaptador.Fill(tabla);
foreach(DataRow fila in tabla.Rows)
Console.WriteLine("{0} {1} {2}", fila["CustomerID"],
fila["OrderID"], fila["OrderDate"]);

13.3.4 Recuperar informacin de esquema


La clase OleDbConnection soporta los mtodos GetSchema que permiten consultar la informacin de
esquema disponible en su base de datos. Este mtodo sobrecargado se comporta como los de los dems
proveedores de datos .NET.

Listado de esquemas y restricciones


Anteriormente vimos como mostrar la lista de esquemas y restricciones usando la clase
DbProviderFactories. Si sabe que va a trabajar con el proveedor de datos .NET para OLE DB puede
simplificar el proceso:
string cadcon = @" Provider=SQLOLEDB;Data Source=.\SQLExpress;" +
"Initial Catalog=Northwind;Integrated Security=SSPI ";
DataTable tablaEsquemas, tablaRestricciones;
using(OleDbConnection conexin = new OleDbConnection(cadcon)) {
conexin.Open();
tablaEsquemas = conexin.GetSchema();
tablaRestricciones = conexin.GetSchema("Restrictions");
conexin.Close();
}
DataSet ds = new DataSet();
tablaEsquemas.TableName = "Esquemas";
tablaRestricciones.TableName = "Restricciones";
ds.Tables.Add(tablaEsquemas);
ds.Tables.Add(tablaRestricciones);
DataRelation relacin = ds.Relations.Add(tablaEsquemas.Columns["CollectionName"],
tablaRestricciones.Columns["CollectionName"]);
foreach(DataRow filaEsquema in tablaEsquemas.Rows) {
Console.WriteLine("Esquema {0}", filaEsquema["CollectionName"]);
DataRow[] filasRestriccion = filaEsquema.GetChildRows(relacin);
if(filasRestriccion.Length != 0) {
Console.WriteLine("\tRestricciones:");
foreach(DataRow restriccin in filasRestriccion)
Console.WriteLine("\t\t{0}: {1}", restriccin["RestrictionNumber"],
restriccin["RestrictionName"]);
} else
Console.WriteLine("\tSin restricciones");
Console.WriteLine();
}

232

Escuela de Informtica del Ejrcito

Informacin de esquema filtrada


La salida del ejemplo anterior indica que la conexin soporta un esquema Columns y que adems
permite restricciones para catlogo, esquema y nombre de la tabla y nombre de columna, en este orden, por
lo que es sencillo recuperar informacin de columnas de una tabla concreta. Sencillamente, construya un
array de cadenas con la misma longitud que el esquema de restricciones, proporcione valores para las
restricciones que quiera imponer e indique null para las restantes, y pase este array al mtodo GetSchema
junto con el nombre del esquema, en este caso Columns.
string[] filtro = new string[] { "Northwind", "dbo", "Order Details", null};
DataTable tablaColumnas;
string cadcon = @" Provider=SQLOLEDB;Data Source=.\SQLExpress;" +
"Initial Catalog=Northwind;Integrated Security=SSPI ";
using(OleDbConnection conexin = new OleDbConnection(cadcon)){
conexin.Open();
tablaColumnas = conexin.GetSchema("Columns", filtro);
conexin.Close();
}
foreach (DataRow fila in tablaColumnas.Rows)
Console.WriteLine("{0,-10} - {1}", fila["COLUMN_NAME"], fila["TYPE_NAME"]);

13.4 Proveedor de datos .NET para Oracle


El marco de trabajo .NET tambin ofrece un proveedor de datos .NET diseado para comunicarse con
bases de datos Oracle. El proveedor de datos .NET para Oracle interacta con bases de datos Oracle
usando el API del cliente Oracle, denominado con frecuencia OCI.
Para utilizar el proveedor de datos .NET para Oracle es imprescindible tener instalado los componentes
clientes de Oracle. Oracle ofrece una amplia gama de opciones de instalacin para su capa cliente. Para
ms informacin vea el sitio Web de Oracle.
El proveedor de datos Oracle incluido en la versin 2.0 del marco de trabajo .NET se ha probado
ampliamente contra las versiones de 32 y 61 bit de las versiones 1 y 2 de Oracle 10g , Oracle 9i y Oracle 8i.
Trabaja tanto con la instalacin bsica como con la simplificada del cliente Oracle.
El cliente Oracle proporciona soporte para muchos tipos de datos especficos de Oracle, como Number,
LOB y BFILE, y permite recuperar el contenido de cursores REF.
El proveedor de datos .NET para Oracle se encuentra en un ensamblado separado
System.Data.OracleClient.dll. Los ejemplos de esta seccin asumen que se ha hecho una referencia a este
ensamblado y que se ha importado el espacio de nombres System.Data.OracleClient.
El proveedor de datos .NET para Oracle necesita la capa OCI para comunicarse con la base de datos.
La clase OracleConnection pasa la informacin de inicio de sesin de la cadena de conexin a la capa OCI;
es esta capa OCI la que establece la conexin con la base de datos en base a cmo se hayan configurado
los componentes del cliente Oracle. Puede usar una archivo de configuracin (TNSNames.ora) que contiene
alias para mltiples servidores de base de datos Oracle, o un servicio centralizado (servidor de nombres
Oracle) que contiene informacin similar.
Los ejemplos de esta seccin asumen que tiene configurados sus componentes de cliente Oracle para
comunicarse con una base de datos que usa el alias Ora10gR2 con el inicio de sesin de ejemplo pepe.
Si tiene problemas para conectar con su base de datos intntelo usando alguna herramienta propia de
Oracle como SQL*Plus; una vez que pueda conectar con una herramienta como esta podr usar las mismas
credenciales en la propiedad ConnectionString de un objeto OracleConnection.

13.4.1 Conectar con su base de datos


Para conectar con una base de datos Oracle se utiliza el objeto OracleConnection. Como con el resto de
los proveedores de datos .NET, para conectar con la base de datos Oracle solamente se necesita crear un
objeto OracleConnection, se configura su propiedad ConnectionString explcitamente o en el constructor del
objeto, y se llama a su mtodo Open. La cadena de conexin ser similar a esta:
string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";

Agrupamiento de conexiones Oracle


La clase OracleConnection usa la misma infraestructura de agrupamiento de conexiones que la clase
SqlConnection, con opciones para Pooling, MinPoolSize y MaxPoolSize en la cadena de conexin. El
proveedor de datos .NET para Oracle no usa la capa OCI para gestionar el agrupamiento de conexiones y
no ofrece caractersticas asociadas con agrupamiento de conexiones OCI como inicio de sesin y
contrasea para un proxy.
233

ADO.NET 2.0

Para ms informacin sobre las opciones de agrupamiento de conexiones disponibles en la clase


OracleConnection vea la documentacin de las diferentes propiedades de la clase
OracleConnectionStringBuilder.

13.4.2 Ejecutar consultas


Como SqlCommand y todas las dems clases que derivan de DbCommand, la clase OracleCommand
ofrece un mtodo ExecuteReader que devuelve los resultados de una consulta en forma de DataReader. El
mtodo ExecuteReader acepta valores de la enumeracin CommandBehavior. Tambin puede devolver un
nico valor usando el mtodo ExecuteScalar o ejecutar una consulta sin tratar los resultados usando el
mtodo ExecuteNonQuery. La clase OracleCommand no ofrece soporte para ejecucin de consultas
asncrona.
Como el proveedor de datos .NET para SQL, el proveedor para Oracle ofrece una serie de estructuras
diseadas para manejar de forma nativa tipos especficos de Oracle.

Examinar los resultados con un OracleDataReader


Los OracleDataReader se crean igual que los DataReader de otros proveedores llamando al mtodo
ExecuteReader.
string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";
string consulta = "SELECT EMPNO, ENAME FROM EMP";
using(OracleConnection conexin = new OracleConnection(cadcon)){
using(OracleCommand comando = new OracleCommand(consulta, conexin)){
conexin.Open();
using(OracleDataReader lector = comando.ExecuteReader()){
while(lector.Read())
Console.WriteLine("{0} - {1}", lector.GetDecimal(0), lector.GetString(1));
lector.Close();
}
conexin.Close();
}
}

Consultas parametrizadas
El proveedor de datos .NET para Oracle soporta solo parmetros con nombre, de una forma muy similar
al proveedor para SQL. La diferencia es que los nombres deben ir precedidos por dos puntos (:).
string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";
string consulta = "SELECT EMPNO, ENAME FROM EMP WHERE DEPTNO = :NUMDEPT";
using(OracleConnection conexin = new OracleConnection(cadcon)){
using(OracleCommand comando = new OracleCommand(consulta, conexin)){
comando.AddWithValue(":NUMDEPT", 20);
conexin.Open();
using(OracleDataReader lector = comando.ExecuteReader()){
while(lector.Read())
Console.WriteLine("{0} - {1}", lector.GetDecimal(0), lector.GetString(1));
lector.Close();
}
conexin.Close();
}
}

Llamar a procedimientos almacenados


La clase OracleCommand soporta el valor StoredProcedure de la enumeracin CommandType.
Sencillamente, asigne a la propiedad CommandText el nombre del procedimiento almacenado, asigne el
valor StoredProcedure a la propiedad CommandType y proporcione la informacin de parmetros.
El siguiente ejemplo llama a un procedimiento almacenado ficticio que recibe dos parmetros de entrada,
pIn1 y pIn2 y otro de salida, pOut, todos ellos numricos. Para utilizar uno existente, consulte la
documentacin de la base de datos de ejemplo que utilice.
string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";
using(OracleConnection conexin = new OracleConnection(cadcon)){
using(OracleCommand comando = new OracleCommand(consulta, conexin)){
comando.CommandText = "Procedimiento"
comando.CommandType = CommandType.StoredProcedure;
comando.AddWithValue("pIn1", 6);
comando.AddWithValue("pIn2", 7);
OracleParameter pOut = comando.Parameter.Add("pOut", OracleType.Number);
pOut.Direction = ParameterDirection.Output;

234

Escuela de Informtica del Ejrcito

comando.ExecuteNonQuery();
Console.WriteLine(pOut.Value);
}
}

Recuperar cursores REF como OracleDataReader


Oracle no proporciona soporte para consultas por lotes que devuelvan resultados, ni la devolucin de
resultados desde un procedimiento almacenado, tal como hace SQL Server. En Oracle estos dos
escenarios precisan el uso explcito de un parmetro que devuelve un cursor REF. Puede recuperar un
cursor REF creando un OracleParameter cuya propiedad OracleType sea Cursor y su Direction sea Output.
Hay dos formas de examinar el resultado de cursores REF utilizando OracleDataReader. La primera es
simplemente llamar al mtodo ExecuteReader del objeto OracleCommand. El OracleCommand devolver el
contenido del primer cursor REF por medio del OracleDataReader resultante, y el resto de cursores REF al
ir llamando al mtodo NextResult del OracleDataReader.
string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";
string consulta = "BEGIN: " +
"OPEN :CURSOR_DEP FOR SELECT DEPTNO, DNAME, LOC FROM DEPT ORDER BY DEPTNO; " +
"OPEN :CURSOR_EMP FOR SELECT DEPTNO, EMPNO, ENAME FROM EMP ORDER BY DEPTNO; " +
"END";
using(OracleConnection conexin = new OracleConnection(cadcon)){
OracleCommand comando = new OracleCommand(consulta, conexin);
OracleParameter pCursorDep =
comando.Parameter.Add(":CURSOR_DEP", OracleType.Cursor);
pCursorDep.Direction = ParameterDirection.Output;
OracleParameter pCursorEmp =
comando.Parameter.Add(":CURSOR_EMP", OracleType.Cursor);
pCursorEmp.Direction = ParameterDirection.Output;
conexin.Open();
using(OracleDataReader lector = comando.ExecuteReader()){
do{
while(lector.Read())
Console.WriteLine("{0} {1}", lector[1], lector[2]);
Console.WriteLine();
}while(lector.NextResult());
lector.Close();
}
conexin.Close();
}

En este ejemplo las consultas devuelven filas de tables que contienen informacin de departamentos y
empleados. Las consultas incluyen clusulas ORDER BY de modo que ambas devuelven las filas
ordenadas por departamento.
Puede abrir varios cursores REF sobre una conexin Oracle sin bloquear la conexin llamando al
mtodo ExecuteNonQuery del objeto OracleCommand y moldeando a OracleDataReader la propiedad
Value de cada OracleParameter que contenga un cursor REF.
De esta forma puede intercalar los resultados de varios cursores. En otras palabras, puede leer una fila
de un cursor REF de departamento, leer una serie de filas del cursor REF de empleados y leer la siguiente
fila de departamento.
string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";
string consulta = "BEGIN: " +
"OPEN :CURSOR_DEP FOR SELECT DEPTNO, DNAME, LOC FROM DEPT ORDER BY DEPTNO; " +
"OPEN :CURSOR_EMP FOR SELECT DEPTNO, EMPNO, ENAME FROM EMP ORDER BY DEPTNO; " +
"END";
using(OracleConnection conexin = new OracleConnection(cadcon)){
OracleCommand comando = new OracleCommand(consulta, conexin);
OracleParameter pCursorDep =
comando.Parameter.Add(":CURSOR_DEP", OracleType.Cursor);
pCursorDep.Direction = ParameterDirection.Output;
OracleParameter pCursorEmp =
comando.Parameter.Add(":CURSOR_EMP", OracleType.Cursor);
pCursorEmp.Direction = ParameterDirection.Output;
conexin.Open();
comando.ExecuteNonQuery();

235

ADO.NET 2.0

using(OracleDataReader cursorDep = (OracleDataReader)pCursorDep.Value,


cursorEmp = (OracleDataReader)pCursorEmp.Value){
bool cursorEmpInicializado = false;
bool cursorEmpTieneDatos = false;
while(cursorDep.Read()){
Console.WriteLine("{0} {1}",
cursorDep.GetString(1), cursorDep.GetString(2));
if(!cursorEmpInicializado){
cursorEmpTieneDatos = cursorEmp.Read();
cursorEmpInicializado = true;
}
while(cursorEmpTieneDatos && cursorEmp.GetDecimal(0) ==
cursorDep.GetDecimal(0)){
Console.WriteLine("\t\t{0} {1}", cursorEmp.GetDecimal(1),
cursorEmp.GetString(2));
cursorEmpTieneDatos = cursorEmp.Read();
}
}
cursorEmp.Close();
cursorDep.Close();
}
conexin.Close();
}

Recuperar informacin de esquema de consultas


Cuando llama al mtodo ExecuteReader de un Command y le pasa al argumento CommandBehavior un
valor que incluya KeyInfo el Command solicita metadatos adicionales sobre las columnas devueltas por la
consulta. Puede examinar esta informacin llamando al mtodo GetSchemaTable del DataReader devuelto
por ExecuteReader. Si examina el contenido del DataTable que recibe en un control vinculado como
DataGridView puede aprender mucho sobre las tablas y columnas a las que hace referencia la consulta.
Ver los nombres de tablas y columnas, as como informacin de clave.
SQL Server puede devolver estos metadatos adicionales de forma nativa junto con los resultados de la
consulta. Como la obtencin de esta informacin produce una penalizacin de rendimiento, esta informacin
no se devuelve junto con los resultados de la consulta de forma predeterminada.
Oracle no proporciona un mecanismo para devolver estos datos adicionales. Puede usar la capa OCI
para ejecutar una consulta y obtener algunos metadatos sobre los resultados, como los nombres y tipos de
datos de las columnas de datos tal como aparecen en los resultados. Sin embargo, no hay forma de usar la
capa OCI para solicitar los nombres de tabla y columna de origen. Pero si llama a ExecuteReader de un
OracleCommand con un CommandBehavior que incluya KeyInfo y examina el contenido del DataTable que
devuelve el mtodo GetSchemaTable del OracleDataReader devuelto, podr ver los nombres de tabla y
columna de origen.
Cmo obtiene OracleCommand estos metadatos adicionales?. Fuerza bruta. El proveedor de datos
.NET para Oracle analiza el texto de la consulta para buscar los nombres de tabla y columna y consulta la
base de datos para obtener detalles de estas tablas y columnas. El analizador del proveedor de datos .NET
para Oracle no es tan robusto ni potente como el contenido en la base de datos Oracle y no puede analizar
algunas consultas que s puede manejar la base de datos.

Recuperar resultados usando tipos de datos especficos de Oracle


El proveedor de datos .NET para Oracle incluye tipos de datos especficos, de forma muy similar a lo que
sucede con el proveedor para SQL Server. Usando estos tipos de datos mejora el rendimiento del cdigo y
se pueden recuperar datos del OracleDataReader ms rpidamente porque se pueden almacenar datos en
estos tipos de datos sin tener que comprobar previamente valores null. Adems, muchos de estos tipos de
datos ofrecen funcionalidad adicional no disponible en el tipo de datos .NET correspondiente.
string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";
string consulta = "SELECT EMPNO, ENAME FROM EMP WHERE DEPTNO = :NUMDEP";
using(OracleConnection conexin = new OracleConnection(cadcon)){
OracleCommand comando = new OracleCommand(consulta, conexin);
comando.Parameters.AddWithValue(":NUMDEP", 20);

236

Escuela de Informtica del Ejrcito

conexin.Open();
using(OracleDataReader lector = comando.ExecuteReader()){
while(lector.Read())
Console.WriteLine("{0} {1}", lector.GetOracleNumber(0).Value,
lector.GetOracleString(1).Value);
lector.Close();
}
conexin.Close();
}

13.4.3 Clase OracleDataAdapter


Ahora que sabe cmo ejecutar consultas bsicas y parametrizadas usando OracleCommand vamos a
ver algunos ejemplos de uso de la clase OracleDataAdapter.

Recuperar los resultados de una consulta


string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";
string consulta = "SELECT EMPNO, ENAME FROM EMP WHERE DEPTNO = :NUMDEP";
OracleDataAdapter adaptador = new OracleDataAdapter(consulta, cadcon);
adaptador.SelectCommand.Parameters.AddWithValue(":NUMDEP", 20);
DataTable tabla = new DataTable();
adaptador.Fill(tabla);
foreach(DataRow fila in Tabla.Rows)
Console.WriteLine("{0} {1}", fila["EMPNO"], fila["ENAME"]);

Ejecutar consultas que devuelven una pgina de filas


El siguiente ejemplo utiliza la funcin RowNum de Oracle en la cadena de consulta para aadir una
columna que indica el nmero de una cierta fila en el resultado de la consulta y usa esta columna en la
clusula WHERE para devolver un rango de filas en base al nmero de pgina y nmero de filas por pgina
especificados como parmetros.
string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";
string consulta = "SELECT * FROM " +
"(SELECT RowNum AS NumFila, EMPNO, ENAME FROM " +
"(SELECT EMPNO, ENAME FROM EMP ORDER BY EMPNO)) " +
"WHERE NumFila > (:NumPagina * :TamPagina) AND " +
"NumFila <= ((:NumPagina + 1) * :TamPagina)";
OracleDataAdapter adaptador = new OracleDataAdapter(consulta, cadcon);
adaptador.SelectCommand.Parameters.AddWithValue(":NumPagina", 1);
adaptador.SelectCommand.Parameters.AddWithValue(":TamPagina", 5);
DataTable tabla = new DataTable();
adaptador.Fill(tabla);
Console.WriteLine("{0,5} {1,5} {2,-10}", "Fila", "EMPNO", "ENAME");
foreach(DataRow fila in tabla.Rows)
Console.WriteLine("{0,5} {1,5} {2,-10}", fila.ItemArray);

Recuperar el contenido de cursores REF


Puede usar la clase OracleDataAdapter junto con la clase OracleParameter para almacenar el contenido
de cursores REF en objetos DataTable:
string cadcon = "Data Source=Ora10gR2;User ID=pepe;Password=tigre;";
string consulta = "BEGIN: " +
"OPEN :CURSOR_DEP FOR SELECT DEPTNO, DNAME, LOC FROM DEPT ORDER BY DEPTNO; " +
"OPEN :CURSOR_EMP FOR SELECT DEPTNO, EMPNO, ENAME FROM EMP ORDER BY DEPTNO; " +
"END";
OracleDataAdapter adaptador = new OracleDataAdapter(consulta, cadcon);
OracleCommand comando = adaptador.SelectCommand;
OracleParameter pCursorDep = comando.Parameter.Add(":CURSOR_DEP", OracleType.Cursor);
pCursorDep.Direction = ParameterDirection.Output;
OracleParameter pCursorEmp = comando.Parameter.Add(":CURSOR_EMP", OracleType.Cursor);
pCursorEmp.Direction = ParameterDirection.Output;
adaptador.TableMappings.Add("Table", "DEPT");
adaptador.TableMappings.Add("Table1", "EMP");
DataSet ds = new DataSet();
adaptador.Fill(ds);
foreach(DataTable tabla in ds.Tables){
Console.WriteLine("{0}", tabla.TableName);
foreach(DataRow fila in tabla.Rows)
Console.WriteLine("\t\t{0} {1}", fila[0], fila[1]);
Console.WriteLine();
}

237

ADO.NET 2.0

Cuando se llama al mtodo Fill de OracleDataAdapter el adaptador llama al mtodo ExecuteReader del
OracleCommand almacenado en la propiedad SelectCommand. El OracleDataReader resultante devuelve
el contenido de los cursores REF en el orden en que aparecen en la coleccin Parameters del
OracleCommand. Como el OracleParameter correspondiente al cursor REF de departamentos se ha
aadido primero, el conjunto de resultados inicial disponible por medio de OracleDataReader y el primer
DataTable generado por el mtodo Fill contienen informacin de departamentos. Puede hacer que el
OracleDataAdapter devuelva la informacin de empleados antes que la de informacin simplemente
cambiando el orden en que se aaden los parmetros, sin modificar el texto de la consulta.

Actualizacin por lotes


La clase OracleDataAdapter soporta la actualizacin por lotes de una forma muy similar a
SqlDataAdapter. Asigne a UpdateBatchSize el tamao deseado para el lote y OracleDataAdapter combinar
las consultas utilizadas para enviar los cambios pendientes en base a este valor. Como en SqlDataAdapter,
la clase OracleDataAdapter soporta actualizaciones por lotes solo si su lgica de actualizacin no devuelve
valores nuevos por medio de consultas que devuelven filas. La propiedad UpdateRowSource de los
OracleCommand usados para enviar estos cambios debe ser None u OutputParameters.

Obtener valores clave de secuencia generados por el servidor


Muchos administradores de bases de datos Oracle utilizan secuencias para generar valores clave. La
siguiente consulta hace referencia a la secuencia en una consulta INSERT INTO usada para enviar nuevas
filas y despus recupera los valores generados usando la clusula RETURNING introducida por Oracle 10g.
INSERT INTO PruebaSecuencia_Tabla(ID, OtraColumna)
VALUES(PruebaSecuencia_Secuencia.NEXTVAL, :OtraColumna RETURNING ID INTO :ID)

Una tcnica alternativa es consultar a la secuencia los nuevos valores antes de enviar las filas nuevas.
Es un mtodo til porque significa que el cdigo conoce los valores clave para las nuevas filas antes de
insertarlas en la base de datos.

13.4.4 Recuperar informacin de esquema


Como otras clases que heredan de Connection la clase OracleConnection soporta el mtodo GetSchema
que permite consultar informacin de esquema a la base de datos. Las sobrecargas de este mtodo se
comportan como las de otros proveedores .NET.

Lista de esquemas y restricciones


Anteriormente vimos como mostrar la lista de esquemas y restricciones usando la clase
DbProviderFactories. Si sabe que va a trabajar con el proveedor de datos .NET para Oracle puede
simplificar el proceso:
string cadcon = "Source=Ora10gR2;User ID=pepe;Password=tigre;";
DataTable tablaEsquemas, tablaRestricciones;
using(OracleConnection conexin = new OracleConnection(cadcon)) {
conexin.Open();
tablaEsquemas = conexin.GetSchema();
tablaRestricciones = conexin.GetSchema("Restrictions");
conexin.Close();
}
DataSet ds = new DataSet();
tablaEsquemas.TableName = "Esquemas";
tablaRestricciones.TableName = "Restricciones";
ds.Tables.Add(tablaEsquemas);
ds.Tables.Add(tablaRestricciones);
DataRelation relacin = ds.Relations.Add(tablaEsquemas.Columns["CollectionName"],
tablaRestricciones.Columns["CollectionName"]);
foreach(DataRow filaEsquema in tablaEsquemas.Rows) {
Console.WriteLine("Esquema {0}", filaEsquema["CollectionName"]);
DataRow[] filasRestriccion = filaEsquema.GetChildRows(relacin);
if(filasRestriccion.Length != 0) {
Console.WriteLine("\tRestricciones:");
foreach(DataRow restriccin in filasRestriccion)
Console.WriteLine("\t\t{0}: {1}", restriccin["RestrictionNumber"],
restriccin["RestrictionName"]);
} else
Console.WriteLine("\tSin restricciones");
Console.WriteLine();
}

238

Escuela de Informtica del Ejrcito

Informacin de esquema filtrada


La salida del ejemplo anterior indica que la conexin soporta un esquema Columns y que adems
permite restricciones para propietario, nombre de tabla y nombre de columna, en este orden, por lo que es
sencillo recuperar informacin de columnas de una tabla concreta. Observe que el conjunto de restricciones
es diferente al de los proveedores OLE DB y ODBC descritos anteriormente en este captlo.
Construya un array de cadenas con la misma longitud que el esquema de restricciones, proporcione
valores para las restricciones que quiera imponer e indique null para las restantes, y pase este array al
mtodo GetSchema junto con el nombre del esquema, en este caso Columns.
string[] filtro = new string[] { "pepe", "EMP", null};
DataTable tablaColumnas;
string cadcon = "Source=Ora10gR2;User ID=pepe;Password=tigre;";
using(OracleConnection conexin = new OracleConnection(cadcon)){
conexin.Open();
tablaColumnas = conexin.GetSchema("Columns", filtro);
conexin.Close();
}
foreach (DataRow fila in tablaColumnas.Rows)
Console.WriteLine("{0,-10} - {1}", fila["COLUMN_NAME"], fila["DATATYPE"]);

239

ADO.NET 2.0

240

Escuela de Informtica del Ejrcito

14 Siglas
Sigla
ACE
ACL
ADO
AES
ANSI
API
APM
ASCII
ASP
AWT
BCP
BLOB
CAS
CASE
CCW
CGI
CLSID
COM
CORBA
CSS
DACL
DBA
DCE
DDL
DES
DHTML
DML
DNS
DCOM
DOM
DSN
DTD
EJB
FIFO
FTP
GAC
GUID
HTML
HTTP
HTTPS
IANA
ICANN
IIOP
IIS
IL
IP
ISAPI
ISP
ISV
IT
J2EE
JAR
JDBC
JDK
JFC
JRMP
JSP
LIFO
MAC
MARS
MDAC
MIME
MSMQ
NCSA
NSAPI

Ingls
Access Control Entry
Access Control List
Active Data Objects
Advanced Encryption Standard
American National Standards Institute
Application Programming Interface
Asynchronous Programming Model
American Standard Code for Interchange of
Information
Active Server Pages
Abstract Window Toolkit
Bulk Copy Protocol
Binary Large Object
Code Access Security
Computer-Aided Software Engineering
COM Callable Wrapper
Common Gateway Interface
Class Identifier
Component Object Model
Common Object Request Broker Architecture
Cascading Style Sheet
Discretionary Control Access List
Data Base Administrator
Distributed Computing Environment
Data Definition Language
Data Encryption Standard
Dynamic HTML
Data Manipulation Language
Domain Name Server
Distributed COM
Document Object Model
Data Source Name
Document Type Definition
Enterprise JavaBeans
First In First Out
File Transfer Protocol
Global Assembly Cache
Globally Unique Identifier
Hypertext Markup Language
Hypertext Transfer Protocol
HTTP Secure
Internet Assigned Numbers Authority
Internet Corporation for Assigned Names and
Numbers
Internet Inter-Orb Protocol
Internet Information Services
Intermediate Language
Internet Protocol
Internet Server API
Internet Service Provider
Independent Software Vendor
Information Technologies
Java 2 Enterprise Edition
Java Archive
Java Database Connectivity
Java Development Kit
Java Foundation Classes
Java Remote Method Protocol
Java Server Pages
Last int First out
Message Authentication Code
Multiple Active Result Sets
Microsoft Data Access Components
Multipurpose Internet Mail Extensions
Microsoft Message Queuing
National
Center
for
Supercomputing
Applications
Netscape Server API

Espaol
Entrada de control de acceso
Lista de control de acceso
Objetos de datos activos
Estndar de encriptacin avanzado (Rijndael).
Instituto nacional americano de estndares
Interface de programacin de aplicaciones
Modelo de programacin asncrono
Cdigo estndar americano para intercambio de
informacin.
Pginas activas de servidor
Herramientas abstractas de ventana
Protocolo de copia masiva
Objeto binario grande
Seguridad de acceso de cdigo
Ingeniera de software asistida por ordenador
Envolvente llamable por COM
Interface de pasarela comn
Identificador de clase
Modelo de objetos componentes
Arquitectura de agente comn de peticin de objetos
Hoja de estilo en cascada
Lista de control de acceso discrecional
Administrador de base de datos
Entorno informtico distribuido
Lenguaje de definicin de datos
Estndar de encriptacin de datos
HTML Dinmico
Lenguaje de manipulacin de datos
Servidor de nombres de dominio
COM distribuido
Modelo de objetos de documento
Nombre de origen de datos (ODBC)
Definicin de tipo de documento
JavaBeans para empresa
Primero en entrar primero en salir (cola)
Protocolo de transferencia de ficheros
Cach de ensamblados global
Identificador nico global
Lenguaje de marcas de hipertexto
Protocolo de transferencia de hipertexto
HTTP Seguro
Autoridad para nmeros asignados de Internet
Corporacin Internet para nombres y nmeros
asignados
Protocolo Inter-Orb Internet
Servicios de informacin de Internet
Lenguaje intermedio
Protocolo de Internet
API de servidor Internet
Proveedor de servicios de Internet
Proveedor de software independiente
Tecnologa de la informacin
Java 2, Edicin Empresa
Archivo Java
Conectividad Java para bases de datos
Conjunto de desarrollo Java
Clases fundamentales Java
Protocolo de mtodo remoto Java
Pginas Java de servidor
ltimo en entrar, primero en salir (LIFO)
Cdigo de autenticacin de mensajes
Mltiples conjuntos de resultados activos
Componentes de acceso a datos de Microsoft
Extensiones multi propsito de correo Internet
Colas de mensajes de Microsoft
Centro
Nacional
para
Aplicaciones
de
Supercomputacin.
API de servidor Netscape

241

ADO.NET 2.0

Sigla
OCI
ODBC
OLE
OLE DB
OMG
OSI
PDA
Perl
PIM
PKI
RCW
RMI
RPC
QBU
RBS
SACL
SCM
SGML
SHA
SMTP
SOAP
SSI
SSL
TCP
TLD
TVF
UDDI
UML
URI
URL
URN
UTC
UTF
W3C
WAE
WMI
WSDL
WYSIWYG
XML
XDR
XSD
XSL

242

Ingls
Oracle Client Interface
Open Data Base Connectivity
Object Linking an Embedding
OLE Data Base
Object Management Group
Open Systems Interconnection
Personal Digital Assistant
Practical Extraction and Reporting Language
Personal Information Manager
Public Key Infrastructure
Runtime Callable Wrapper
Remote Method Invocation
Remote Procedure Call
Query Based Update
Role Based Security
Security Access Control List
Service Control Manager
Standard Generalized Markup Language
Secure Hash Algorithm
Simple Mail Transfer Protocol
Simple Object Access Protocol
Server Side Includes
Secure Sockets Layer
Transmission Control Protocol
Top-Level Domain
Table Valued Function
Universal
Description,
Discovery
and
Integration
Unified Modeling Language
Uniform Resource Identifier
Uniform Resource Locator
Uniform Resource Name
Universal Coordinated Time (?)
Unicode Transformation Format
World Wide Web Consortium
Web Application Extension
Windows Management Instrumentation
Web Services Description Language
What You See Is What You Get
Extensible Markup Language
XML Data Reduced
XML Schema Definition
Extensible Style sheet Language

Espaol
Interface de cliente Oracle.
Conectividad abierta con bases de datos
Enlazado e incrustacin de objetos
OLE para bases de datos
Grupo de gestin de objetos
Interconexin de sistemas abiertos
Asistente digital personal
Lenguaje prctico de extraccin e informes
Gestin de informacin personal
Infraestructura de clave pblica
Envolvente llamable en tiempo de ejecucin
Llamada remota a mtodos
Llamada remota a procedimiento
Actualizacin basada en consulta
Seguridad basada en roles
Lista de control de acceso de seguridad
Administrador de control de servicio
Lenguaje estndar de marcas generalizado
Algoritmo de hash seguro
Protocolo simple de transferencia de correo
Protocolo simple de acceso a objeto
Inclusiones de servidor
Capa de sockets segura
Protocolo de control de transmisin
Dominio de nivel mximo
Funcin con valor tabla
Descripcin, descubrimiento e integracin universal.
Lenguaje unificado de modelado
Identificador uniforme de recurso
Localizador uniforme de recurso
Nombre uniforme de recurso
Tiempo universal coordinado
Formato de transformacin Unicode
Consorcio Web
Extensin para aplicaciones Web
Instrumentacin para administracin de Windows
Lenguaje de descripcin de servicios Web
Lo que ves es lo que obtienes
Lenguaje de marcas extensible
Datos XML reducidos
Definicin de esquema XML
Lenguaje extensible de hojas de estilo

Das könnte Ihnen auch gefallen