Beruflich Dokumente
Kultur Dokumente
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
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
6.1
6.2
6.3
2.9.1
2.9.2
2.9.3
6.4
3.1
3.2
3.3
7.1
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
Propiedades ................................................................................................. 64
Mtodos ....................................................................................................... 66
Eventos ........................................................................................................ 67
8.1
8.2
5.2
8.3
8.4
8.6
8.5
QU ES UN SQLDATAADAPTER ................................................................ 57
4.1.1
4.1.2
REFERENCIA ............................................................................................ 49
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.4
Propiedades ................................................................................................. 24
Mtodos ....................................................................................................... 25
Eventos ........................................................................................................ 28
2.8
2.9
5.4
ADO.NET 2.0
9.2
9.3
9.4
9.5
9.7
9.8
9.9
9.10
9.6
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.
ADO.NET 2.0
1.2.1
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
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.
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
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
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
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
1.2.7
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
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.
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.
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.1
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.
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;).
14
Tambin puede usar la antigua palabra clave Database en lugar de Initial Catalog.
2.5.2
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.
Pero esta cadena de conexin ya sabemos construirla, o sea, que an no hemos ganado nada.
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
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;
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.
2.5.3
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.
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.
ADO.NET 2.0
...
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;
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.
2.6.1
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
2.6.2
Qu es agrupamiento de conexiones
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
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
2.6.5
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
2.6.7
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
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
2.6.9
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.
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
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"]);
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
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.
ADO.NET 2.0
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;
24
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
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();
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);
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
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);
}
}
}
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
29
ADO.NET 2.0
30
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.1
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
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
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
3.1.4
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
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
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
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
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
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.
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");
37
ADO.NET 2.0
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.1
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
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
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.
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.
39
ADO.NET 2.0
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
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
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();
3.2.5
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
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
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
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
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.
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
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.
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.1
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
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
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();
propiedades
Si quiere usar como valor del parmetro un objeto SqlTypes utilice la propiedad SqlValue de
SqlParameter en lugar de Value.
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
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.
3.4 Referencia
3.4.1
Propiedades de 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
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.
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
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.
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.
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
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
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
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.
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
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.
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
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"]);
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
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
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
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.
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.
56
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
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
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.
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
4.2.1
Crear un SqlDataAdapter
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
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
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.
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
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);
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
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);
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
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
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
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.
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
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
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);
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
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
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
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
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
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:
5.1.1
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
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
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.1
5.2.2
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
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
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.
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.
5.2.4
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.
5.2.5
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.
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);
ADO.NET 2.0
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.
76
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.
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
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
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
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
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.
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"};
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
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.
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";
Added
tabla.Rows.Add(fila);
Unchanged
fila = tabla.Rows(0);
Modified
fila.BeginEdit();
fila["ColX"] = "Valor Nuevo;
fila.EndEdit();
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.
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;
84
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];
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
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.
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>
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
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.1
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
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
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.
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
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
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.
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
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.
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
ADO.NET 2.0
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
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
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;
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
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.
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.
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
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
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
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.
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>
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
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
5.4.8
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
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
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.
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
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.
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.
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.
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.
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
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.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
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
6.2.1
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
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
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.
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();
}
112
6.2.3
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
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
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
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
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
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
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();
}
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.
ADO.NET 2.0
6.2.8
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.1
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
6.4.1
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
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
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.1
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
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
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 %"
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"
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
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
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.
Recuerde que para filas borradas solo puede examinar la versin original de la fila.
ADO.NET 2.0
7.3.1
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);
7.3.2
Propiedad RowStateFilter
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
7.3.4
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
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
7.3.7
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
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.5 Referencia
7.5.1
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
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.
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
7.5.2
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
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
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
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
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
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.
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.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.
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.
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
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.1
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
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});
8.3.2
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
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
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
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
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.1
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
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
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.
ADO.NET 2.0
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.
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
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.
138
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.
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
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.
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.
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
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
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
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.
144
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.
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
9.1.1
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
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
ADO.NET 2.0
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.
ADO.NET 2.0
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
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
}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.
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
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 = ?
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.
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.
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
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
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
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
Observar que el texto de esta consulta es muy similar al que se ha utilizado al crear la lgica
manualmente.
9.4.1
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
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
Pulse Siguiente. La ltima ventana del asistente le confirmar que se han generado las sentencias de
actualizacin.
9.5.1
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
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
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
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.1
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
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.
158
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.
160
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.10
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
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.
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
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.
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.
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
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
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.
168
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()
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()
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.
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.
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
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.
herramientas para generar su lgica de actualizacin debe hacer algunos cambios en el resultado de la
generacin.
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
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
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
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.
DataTable
principal
ColA
ColB
A1
B1
A2
B2
DataTable
secundario
ColC
ColD
C1
D1
C2
D2
173
ADO.NET 2.0
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
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);
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
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?.
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.
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.
ADO.NET 2.0
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
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();
}
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.
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.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
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.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.1
183
ADO.NET 2.0
10.8.2
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
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
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
10.8.5
Ejemplo
El cdigo para este captulo incluye cdigo funcional que usa la clase SqlBulkCopy en el procedimiento
BulkCopy.
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
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
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.
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.
ADO.NET 2.0
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");
}
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.
Como puede ver, los cambios se almacenan en el DataSet. Ahora podramos usar un SqlDataAdapter
para enviar estos cambios a la base de datos.
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.
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))
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);
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.
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
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
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")]'
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) }
}
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
196
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.
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"]);
198
<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.
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.
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.
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
201
ADO.NET 2.0
202
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.1
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
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
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");
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.
12.1.3
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
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.
12.1.5
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.
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.
Este mtodo se puede utilizar para modificar el comportamiento de cualquiera de los botones de
BindingNavigator.
12.1.6
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.
12.1.7
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
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
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 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
Llame a los nuevos mtodo CargaPorIDCliente y LeePorIDCliente y guarde los cambios en el DataSet.
210
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
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.
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.
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);
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);
}
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.
214
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();
}
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
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;
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;
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
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
221
ADO.NET 2.0
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
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.
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
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
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();
}
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.
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
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;
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();
}
ADO.NET 2.0
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();
}
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
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"]);
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 usando un archivo de enlace de datos OLE DB
File Name=ArchivoUDL.UDL;
230
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();
}
}
231
ADO.NET 2.0
232
ADO.NET 2.0
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();
}
}
234
comando.ExecuteNonQuery();
Console.WriteLine(pOut.Value);
}
}
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
236
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();
}
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.
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.
238
239
ADO.NET 2.0
240
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