Está en la página 1de 30

Pasar de ADO a ADO.NET 9. Al pulsar el botn Finalizar se generar el juego de datos tipificado NorthwindDataSet y se mostrar en el panel Orgenes de datos.

Expanda la rama Customers para mostrar las columnas de la tabla del mismo nombre tal como se muestra en la siguiente figura.

El nuevo objeto SqlConnection creado en los pasos 3 a 5 aparece en el nodo Conexiones de datos del Explorador de servidores como servidor\sqlexpress.Northwind.dbo. El nodo se puede renombrar en el Explorador de servidores con un nombre ms sencillo, por ejemplo localhost.Northwind; el cambio no afecta a los objetos dependientes del proyecto. Aadir un DataSet tipificado genera un esquema XSD, NorthwindDataSet.xsd en este ejemplo, y aade 1.197 lneas de cdigo VB 2005 al archivo de clase parcial NorthwindDataSet.Designer.vb, cuyo tamao es de 73 KBytes. Las clases parciales son una caracterstica nueva de VB 2005 y C# que permite expandir una clase, como la NorthwindDataSet, con archivos de clase adicionales. VB 2005 usa la sentencia Public Partial Class className para identificar archivos de clase parcial. Deber tener seleccionado el botn Mostrar todos los archivos del panel Explorador de soluciones para ver NorthwindDataSet.Designer.vb y los dos archivos vacos NorthwindDataSet.xsc y NorthwindDataSet.xss. Realice una doble pulsacin en el nodo NorthwindDataSet.xsd situado en el Explorador de proyectos para mostrar la tabla de datos Customers y su adaptador de tabla Customers asociado, tal como muestra la siguiente figura, en la ventana principal. El cdigo VB 2005 en DataSetName.Designer.vb proporciona IntelliSense para los objetos DataSet y los objetos, anteriormente vinculados, DataTable y DataSet. El cdigo tambin permite acceso directo a las clases nombradas, mtodos y eventos para DataSet y su adaptador de tabla del NorthwindDataSet.Designer.vb, cdigo de las listas Classes y mtodos de la ventana. 27

Bases de datos con Visual Basic


Si ya ha trabajado con DataSets tipificados en VS 2003, se dar cuenta cuenta de que el esquema para ADO 2.0 DataSets tiene mucha ms verborrea que la versin ADO 1.x, que consta slo de 30 lneas que definen el Customers DataSet. ADO.NET 2.0 prefija el esquema de tiempo de diseo con 258 lneas de informacin <xs:annotation>, que proporcionan una definicin completa del DataSet y su string de conexin, comandos y parmetros, as como los datos del mapping de columnas. La parte del esquema que define los elementos para los campos de tabla crece de 30 a 94 lneas porque las definiciones de los elementos contienen ahora valores para el atributo maxLength y utilizan atributos restrictionBase para especificar los tipos de dato XSD..

En la siguiente figura puede ver Internet Explorer mostrando las primeras lneas de las 352 que componen el esquema.

28

Pasar de ADO a ADO.NET


Private Sub bindingNavigatorSaveItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bindingNavigatorSaveItem.Click Me.CustomersBindingSource.EndEdit() Me.CustomersTableAdapter.Update(Me.NorthwindDataSet.Customers) End Sub

La siguiente figura muestra el formulario final despus de reducir el tamao, ampliar el control de DataGridView para llenar el espacio disponible y pulsar <F5> para crear, depurar y ejecutar el proyecto.

La CustomersDataGridView est vinculada a la tabla Customers y se puede editar por defecto. Los cambios que se hagan en la DataGridView no se validan en la tabla hasta que no se pulsa el botn Save Data. Para facilitar la edicin, el ancho de columna se puede adaptar automticamente al contenido definiendo para la propiedad AutoSizeColumnsMode de DataGridView el valor AllCells o DisplayedCells, que aade una barra de desplazamiento horizontal al control.

1.9 Persistir y reabrir el juego de datos


El manejador de eventos del proyecto frmDataGridView_Load incluye el siguiente cdigo para salvar el documento de datos XML NorthwindDataSet y el esquema solo. Se puede aadir cdigo parecido despus de la ltima invocacin DataComponent.Fill o DataAdapter.Fill de cualquier proyecto para persistir su juego de datos.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'TODO: This line of code loads data into the 'NorthwindDataSet.Customers' table. 'You can move, or remove it, as needed. Me.CustomersTableAdapter.Fill(Me.NorthwindDataSet.Customers) Dim strPath As String = Application.StartupPath With Me.NorthwindDataSet .WriteXml(strPath + "CustsNoSchema.xml", XmlWriteMode.IgnoreSchema)

31

Bases de datos con Visual Basic


.WriteXml(strPath + "CustsWithSchema.xml", XmlWriteMode.WriteSchema) .WriteXmlSchema(strPath + "CustsSchema.xsd") End With End Sub

Persistiendo el DataSet como documento XML, sin el esquema incrustado, permite dar soporte a los usuarios sin conexin, cargando de nuevo el DataSet del archivo. La sentencia siguiente se puede substituir por Me.CustomersTableAdapter.Fill(Me.NorthwindDataSet.Customers) cuando el usuario est desconectado:
Me.NorthwindDataSet.ReadXml(strPath + CustsNoSchema.xml , XmlReadMode.Auto)
El escenario en el mundo real para persistir y cargar de nuevo un juego de datos es mucho ms complejo que lo que hemos visto aqu. En captulos posteriores se describe cmo salvar y cargar de nuevo los cambios pendientes del DataSet que no se han pasado a las tablas base. El argumento XmlReadMode.Auto aparece por defecto, as que incluirlo es opcional.

1.10 Cambiar de un DataViewGrid a un Details Form


La combinacin por defecto de los controles DataViewGrid y DataNavigator acelera la creacin de un formulario utilizable. De todos modos, un DataNavigator es mucho ms til para crear un formulario de detalles que muestre en pantalla los valores de columna en cuadros de texto u otros controles vinculados, como selectores de datos DateTime y cuadros de verificacin para valores booleanos. La ventana Data Sources facilita el cambio de la DataGridView a un formulario de detalle. Borre el control DataGridView, muestre la ventana Orgenes de datos, abra la lista desplegable para la tabla de datos, y seleccione Detalles como se muestra en la siguiente figura.

Arrastre el icono DataTable hasta el formulario para aadir automticamente una columna de etiquetas con controles asociados de vinculacin de datos (cuadros de texto en este ejemplo) al formualrio. La siguiente figura, que es una versin modificada del proyecto GeneratedDataGridView, muestra las etiquetas y los cuadros de texto reordena-

32

Pasar de ADO a ADO.NET dos para reducir la altura del formulario.

1.11 Aadir un control de vnculo de datos relacionado


Al panel Orgenes de datos se le puede aadir una tabla relacionada y despus un control, como DataGridView, que se puede vincular al BindingAdapter relacionado. Para aadir un control relacionado OrdersDataGridView a una copia del proyecto GeneratedDetailView.sln, Debe realiar los siguientes pasos: 1. Copie y pege la carpeta GeneratedDetailView y renombre la nueva carpeta como OrdersDetailView. No renombre el proyecto. 2. Pulse <F5> para crear y compilar el proyecto. Corrija cualquier error de nombre que detecte el depurador. 3. Abra la ventana Orgenes de datos y pulse el botn del ayudante Configurar Dataset con el asistente para abrir la pgina Elija los objetos de la base de datos. 4. Expandir el rbol Tablas y seleccione la casilla de verificacin de la tabla Orders. Pulse el botn Finalizar. De ese modo se aade en panel Orgenes de datos un nodo relacional Orders a la tabla Customers y un nodo individual Orders (ver siguiente figura). 5. Con DataGridView seleccionado en la lista desplegable, arrastre el nodo Orders relacionado por debajo de los cuadros de texto vinculados del formulario para autogenerar un control OrdersDataGridView. 6. Ajuste el tamao y la posicin de los controles y defina para la propiedad OrdersDataGridView.AutoSizeRowsMode el valor DisplayedCells. Opcionalmente se puede modificar la propiedad Text del formulario para reflejar el cambio en el diseo.

33

Bases de datos con Visual Basic

7. Pulse <F5> para crear y ejecutar el proyecto. El formulario aparecer tal como muestra la siguiente figura.

Arrastrando el nodo relacionado de la tabla Orders hasta el formulario se aade un OrdersTableAdapter y OrdersBindingSource a la bandeja, y el control OrdersDataGridView al formulario. El valor de la propiedad OrdersDataGridView del control DataSource es OrdersBindingSource. 34

Pasar de ADO a ADO.NET La propiedad OrdersBindingSource tiene el valor CustomersBindingSource y el valor de la propiedad DataMember es FK_Orders_Customers, el cual es la relacin de clave fornea en el campo CustomerID entre las tablas de Customers y Orders. Para verificar las propiedades de FK_Orders_Customers debe abrir el NorthwindDataSet.xsd en la ventana principal, pulsar con el botn secundario la lnea de relacin entre las tablas Customers y Orders, y seleccionar Editar relacin para abrir el cuadro de dilogo Relacin (ver figura siguiente).

Las relaciones que se definen aadiendo tablas relacionadas a la ventana Orgenes de datos no refuerzan la integridad referencial por defecto. Hay que cambiar el valor por defecto de la propiedad Slo relacin a uno de los otros valores para mantener la integridad referencial. Tambin se puede especificar Cascade u otras opciones para las reglas actualizacin, eliminacin, y aceptacin o rechazo.

35

CAPTULO 2

Las novedades de ADO.NET 2.0


En este captulo trataremos los nuevos objetos de ADO.NET 2.0 y los mtodos, propiedades y eventos utilizados con ellos. De la misma forma que el captulo anterior, este captulo empieza con unas descripciones de los nuevos objetos en tiempo de ejecucin, como DbProviderFactory y SqlBulkCopy, con los correspondientes ejemplos de cdigo para crear y manejar los nuevos objetos. El captulo contina com ms ejemplos avanzados de las componentes y controles de ADO.NET 2.0 para los formularios de Windows, que se pueden agragar con la ayuda de diseadores: DataTables, BindingSources, BindingNavigators y DataGridViews. Todos los ejemplos de cdigo SQLServer de este captulo se pueden ejecutar con SQLServer 2000, SQLServer 2005 o SQLServer 2005 Express Edition (SQLX) y nencesitan los privilegios del administrador del sistema.
Si trabajamos con SQLX, deberemos cambiar la cadena de conexin de cada proyecto de localhost a .\SQLEXPRESS.

2.1 Los objetos de formulario


Este libro define un objeto en tiempo de ejecucin como un tipo de objeto no visual, relacionado con los datos que se genera sin la ayuda de los mltiples asistentes. Los objetos en tiempo de ejecucin de ADO.NET 2.0 se crean escribiendo cdigo VB.NET 2005 sin la ayuda de los ayudantes de tiempo-diseo de VS 2005 ni cdigo autogenerado. Microsoft ha dedicado una parte importante del esfuerzo invertido en el desarrollo de VS 2005 y ADO.NET 2.0 en simplificar con arrastrar y colocar la creacin de formularios bsicos Windows y Web de vinculacin de datos. Otro aspecto que se ha cuidado ha sido dar soporte a las nuevas caractersticas del SQLServer 2005 con los objetos System.Data y System.Xml. Por eso, ADO.NET 2.0 slo incluye algunos objetos y caractersticas nuevas y actualizadas que son compatibles con las fuentes de datos de SQLServer 2000. Ms adelante, en este mismo libro, se tratarn las propiedades de ADO.NET 2.0 y VB.NET 2005 especficas para SQLServer 2005. A continuacin indicamos los objetos en tiempo de ejecucin y actualizados, mtodos y caractersticas de lenguaje, ms importantes para los proyectos de formulario Windows:

37

Bases de datos con Visual Basic


)

El objeto DbProviderFactory permite escribir cdigo comn para proveedores de datos y servidores de bases de datos alternativos. El objeto SqlBulkCopy permite insertar con gran eficiencia datos de SQLServer de fuentes relacionales y XML. El mtodo SqlConnection.RetrieveStatistics proporciona informacin detallada sobre la conexin abierta con el SQLServer. La ejecucin asincrnica de SqlCommand permite entrelazar consultas o actualizaciones mltiples de larga ejecucin. Los nuevos objetos actualizados DataTable soportan las caractersticas comunes de los DataSet, como son los mtodos ReadXml y WriteXml, retornan valores de los servicios Web e interfaces remotas y streaming. A las tablas de datos se les puede asignar espacios-nombre y prefijos para los nombre de espacio. Los tipos Null permiten definir objetos fuertemente tipificados, con miembros a los que se puede asignar el valor DbNull.

En las secciones siguientes se explica cmo utilizar las caractersticas del precedente ADO.NET 2.0 con ejemplos de cdigo derivado de los proyectos-ejemplo de formularios Windows.

2.1.1 Utilizar DbProviderFactories para crear proyectos con bases de datos agnsticas
La nueva clase System.Data.Common.DbProviderFactories proporciona a los desarrolladores de bases de datos la oportunidad de enfrentarse a la creacin de aplicaciones agnsticas frente a las fuentes de datos. Crear aplicaciones de entradas de datos no-triviales que puedan interactuar sin fisuras con todos los administradores de bases de datos relacionales, para los que existen proveedores de datos controlados, no es precisamente algo simple. Las diferencias menores en la sintaxis SQL, tipos de datos, dialectos de procedimientos almacenados, tratamiento de error, y otras caractersticas propias de una base de datos, requerirn sin duda un esfuerzo. Si actualmente utiliza el proveedor de datos controlados .NET Framework OleDb, o ADODB con proveedores OLE DB para asegurar la interoperabilidad de las bases de datos, seguramente encontrar que Microsoft y el tercero en cuestin, ADO.NET, ofrecen mejor rendimiento y, como resultado, mayor escalabilidad. Por otra parte, el nivel de ampliacin y mejora que .NET garantiza a los proveedores de datos, hace difcil escribir cdigo que sea totalmente transparente al proveedor.
Los grupos de terceros de proveedores controlados .NET pueden reducir la interoperabilidad con costes de licencia aadidos. Por ejemplo, DataDirect Technologies ofrece proveedores de datos controlados para IBM DB2 y DB2 UDB; Oracle 8i, 9i, y 10g; SQLServer 7 y 2000; Sybase Adaptive Server 11.5 y 11.9; y Sybase Adaptive Server Enterprise 12.0 y 12.5. Todos los proveedores DataDirect buscan salidas para minimizar las diferencias de sintaxis SQL y comunicarse con servidores a travs de los protocolos de los vendedores de bases de datos.

38

Las novedades de ADO.NET 2.0 Crear un objeto DataReader de la clase DbProviderFactories es un proceso en siete pasos: 1. Crear un objeto DbProviderFactory pasando el nombre completo de la clase del proveedor de datos, como System.Data.SqlClient, al argumento de una sentencia DimFactoryNameAsDbProviderFactory = DbProviderFactories.GetFactory(strProvider). 2. Crear un objeto IdbConnection invocando el mtodo DimConnectionNameAsIDbConnection = FactoryName.CreateConnection(). 3. Definir el valor de la propiedad ConnectionName.Connection.String. 4. Crear un objeto IdbCommand invocando el mtodo DimCommandNameAsIDbCommand = ConnectionName.CreateCommand(). 5. Definir para las propiedades CommandName.CommandType (opcional) y CommandName.CommandText los valores adecuados para el proveedor. 6. Llamar al mtodo ConnectionName.Open(). 7. Crear un objeto IdataReader invocando el mtodo DimReaderNameAsIDataReader = CommandName.ExecuteReader. El objeto IDataReader tiene los miembros que los DataReaders especficos del proveedor para ADO.NET 1.x y 2.0, ms el nuevo mtodo GetSchemaTable que se describe en el siguiente apartado. El proyecto de ejemplo DbFactoryTest.sln muestra datos en pantalla de una de las tres tablas Northwind creando y atravesando los objetos IDataReader de SqlClient, OleDb, u Odbc, que se especifiquen seleccionando la opcin apropiada. El formulario incluye tambin un control DataGridView con el que se muestra en pantalla el esquema de tabla DataTable (del que trata el siguiente apartado) tal como muestra la siguiente figura.

El siguiente listado contiene el cdigo para las declaraciones de variables y el botn de opcin del manejador de eventos de OleDb DbProviderFactory:

39

Bases de datos con Visual Basic


'OleDb provider settings - Products table Private strOleDbProvider As String = "System.Data.OleDb" Private strOleDbConn As String = "Provider=SQLOLEDB;Data Source=.\SQLEXPRESS;" + _ "Initial Catalog=Northwind;Integrated Security=SSPI" Private strOleDbTable As String = "Products" Private Sub optOleDb_CheckedChanged(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles optOleDb.CheckedChanged If optOleDb.Checked = True Then PopulateList(strOleDbProvider, strOleDbConn, strOleDbTable) Me.Text = "DbFactory Test Form - OleDb" End If End Sub El tratador de eventos optOleDB_CheckedChanged pasa los valores requeridos del parmetro OleDb al procedimiento PopulateList, ampliado con el cdigo siguiente: Private Sub PopulateList(ByVal strProvider As String, _ ByVal strConn As String, ByVal strTable As String) Dim cnFactory As IDbConnection = Nothing Dim drData As IDataReader = Nothing Try Dim dpFactory As DbProviderFactory = _ DbProviderFactories.GetFactory(strProvider) cnFactory = dpFactory.CreateConnection() cnFactory.ConnectionString = strConn Dim cmFactory As IDbCommand = cnFactory.CreateCommand cmFactory.CommandType = CommandType.Text cmFactory.CommandText = "SELECT * FROM " + strTable cnFactory.Open() drData = cmFactory.ExecuteReader(CommandBehavior.KeyInfo) lstData.Items.Clear() Dim dtSchema As DataTable With drData While drData.Read lstData.Items.Add(.GetValue(0).ToString + _ " - " + .GetValue(1).ToString) End While dtSchema = drData.GetSchemaTable() With dgvSchema If dtSchema.Columns.Count > 1 Then .RowHeadersVisible = False .DataSource = dtSchema .AutoGenerateColumns = True Application.DoEvents() If .Columns.Count > 0 Then .Columns(0).Frozen = True .Columns("BaseSchemaName").Width = 110 If .Columns.Count = 24 Then .Columns(23).Width = 200 End If End If

40

Las novedades de ADO.NET 2.0


End If End With End With If dgvSchema.Columns.Count > 0 Then Dim intCtr As Integer Dim strDataCols As String = "" For intCtr = 0 To dgvSchema.Rows(0).Cells.Count - 1 strDataCols += dgvSchema.Columns(intCtr).Name + vbTab + _ dgvSchema.Rows(0).Cells(intCtr).Value.ToString + vbCrLf Next intCtr intCtr = 0 End If Catch exc As Exception MsgBox(exc.Message + exc.StackTrace) Finally If Not drData Is Nothing Then drData.Close() End If If Not cnFactory Is Nothing Then cnFactory.Close() End If End Try End Sub Hay que especificar CommandBehavior.KeyInfo como el argumento ExecuteReader para devolver las claves primarias correctas y las propiedades de campo relacionadas.

Si sus proyectos deben incluir independencia respecto al proveedor de datos y est dispuesto a escribir ms para especificar las diferencias, sutiles o no, entre las diferentes mejoras de los proveedores de datos, pruebe con DbProviderFactories. Sin embargo, tenga en cuenta que el cdigo independiente de proveedores tiene que usar tipos de datos originales .NET, antes que los tipos de datos especficos de cada proveedor para los diferentes add-in de SQLServer, Oracle, y otros servidores soportados por terceros.
DbProviderFactories mejora la vinculacin de la base de datos, lo que deja en clara desventaja a muchas propiedades del modelo de programacin de ADO.NET. El SQL especfico de un vendedor y la sintaxis de ejecucin de los procedimientos almacenados hacen que escribir cdigo transparente al vendedor con los proveedores de datos ADO.NET 2.0 sea difcil, si no imposible.

2.1.2 Restablecer los esquemas de las tablas base


Los DataReaders de ADO.NET 1.x y 2.0 y los DataTableReaders de ADO.NET 2.0, tienen un mtodo GetSchemaTable que devuelve los correspondientes esquemas del objeto en un objeto DataTable. Para dar informacin sobre el tipo de datos utilizado en los proyectos, que substituye el cdigo para los controles de vinculacin que muestran y actuali41

Bases de datos con Visual Basic zan las tablas base, se utilizan los valores de propiedad del esquema DataTable. Los esquemas DataTable dan valores ColumnLength para definir la propiedad MaxLength en cuadros de texto y valores IsReadOnly que se pueden aplicar a la propiedad ReadOnly de los controles de entrada de datos normal. Estos DataTable tambin devuelven informacin clave primaria como son los ndices de columna y detalles de autoincrementacin.
El System.Data.ObjectSpaces.ObjectDataReader, que se inclua en las primeras versiones alpha y Community Technical Preview de VS 2005, daban miembros similares a los de otros DataReaders, incluido el mtodo GetSchemaTable. En Mayo del 2004, Microsoft anunci que ObjectSpaces se lanzara como componente dentro de las mejoras del sistema de archivos WinFS.

Para crear un esquema DataTable en un DataReader y poblar una DataGridView para mostrar las propiedades de columna se ha de utilizar cdigo parecido al siguiente:
Dim dtSchema As DataTable With drData While drData.Read lstData.Items.Add(.GetValue(0).ToString + _ " - " + .GetValue(1).ToString) End While dtSchema = drData.GetSchemaTable() With dgvSchema If dtSchema.Columns.Count > 1 Then .RowHeadersVisible = False .DataSource = dtSchema .AutoGenerateColumns = True Application.DoEvents() If .Columns.Count > 0 Then .Columns(0).Frozen = True .Columns("BaseSchemaName").Width = 110 If .Columns.Count = 24 Then .Columns(23).Width = 200 End If End If End If End With End With

El esquema DataTable contiene una fila por cada columna de tabla base y 27 campos de propiedades de columna SqlDataReader. OleDbDataReaders y OdbcDataReaders devuelven 18 propiedades; los DataTableReaders tienen 25 campos de propiedades. Como el objeto DataTableReader es nuevo en ADO.NET 2.0, en la tabla siguiente se comparan el ndice de campos del esquema DataTable y los nombres de propiedades de las tres clases de DataReaders.

42

Las novedades de ADO.NET 2.0 Index


0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

SqlDataReader
ColumnName ColumnOrdinal ColumnSize NumericPrecision NumericScale IsUnique IsKey BaseServerName BaseCatalogName BaseColumnName BaseSchemaName BaseTableName DataType AllowDBNull ProviderType IsAliased IsExpression IsIdentity IsAutoIncrement IsRowVersion IsHidden IsLong IsReadOnly ProviderSpecificDataType DataTypeName XmlSchema Collection Database XmlSchema Collection OwningSchema
XmlSchema CollectionName

OleDb y Odbc DataReaders


ColumnName ColumnOrdinal ColumnSize NumericPrecision NumericScale DataType ProviderType IsLong AllowDBNull IsReadOnly IsRowVersion IsUnique IsKey IsAutoIncrement BaseSchemaName BaseCatalogName BaseTableName BaseColumnName

DataTableReader
ColumnName ColumnOrdinal ColumnSize NumericPrecision NumericScale DataType ProviderType IsLong AllowDBNull IsReadOnly IsRowVersion IsUnique IsKey IsAutoIncrement BaseCatalogName BaseSchemaName BaseTableName BaseColumnName AutoIncrementSeed AutoIncrementStep DefaultValue Expression ColumnMapping BaseTableNamespace BaseColumnNamespace

Las propiedades que se muestran en negrita son miembros de la nueva clase de ADO.NET 2.0 System.Data.Common .SchemaTableColumn y son necesarias. El resto son miembros opcionales de la clase SystemData.Common.SchemaOptionalTableColumn. Los campos XmlSchemaCollection aparecen slo en las tablas del SQLServer 2005 y especifican el esquema, si existe, de los campos para los tipos de datos xml.

Los desarrolladores de bases de datos pueden traducir la mayor parte de las propiedades incluidas en la tabla-lista anterior. Por eso la tabla siguiente slo ofrece la lista de las propiedades cuyo significado no es obvio o que devuelven valores inesperados. 43

Bases de datos con Visual Basic


Nombre de la propiedad Descripcin Devuelve 1 si el dato no est disponible, de lo contrario, el tamao de la columna en bytes. El tipo de datos original de .NET que corresponde al tipo de dato de la columna, como en System.Int32 o System.String. El valor ntegro de una enumeracin de tipo de datos especifcos del proveedor. True indica un tipo de datos text o ntext de SQL, o un image,y un campo de objeto OLE o Jet Memo. Uno de los tipos Sql, como SqlString o SqlInt32 (slo SqlClient) La expresin calculada para una columna de una DataTable (slo DataTable) Un valor String que especifica la columna de la tabla de destino o 1 si la columna no est mapeada (slo DataTable) El nombre de espacio XML asignado a la tabla, heredado del nombre de espacio del DataSet si est vaco (slo DataTable) El nombre de espacio XML asignado a la tabla, heredado del nombre de espacio del DataSet si est vaco (slo DataTable) El nombre de la base de datos del servidor SQL Server 2005 que contiene el conjunto de esquemas para una columna del tipo xml (null si la columna xml no tiene esquema) Esquema relacional del SQL Server 2005 que contiene el conjunto de XmlSchema (null si la columna xml no tiene esquema) Nombre del conjunto de esquemas para una columna del tipo xml (null si la columna xml no tiene esquema)

ColumnSize DataType ProviderType IsLong ProviderSpecificDataType Expression ColumnMapping BaseTableNamespace BaseColumnNamespace XmlSchema Collection Database XmlSchema CollectionOwning Schema XmlSchema CollectionName

Ms adelante en este captulo, se describe cmo cargar y persistir DataTables desde bases de datos y archivos XML, y mostrar en pantalla la informacin del esquema de los objetos DataTable.

2.2 Comprobar las instancias de servidor SQL disponibles y los proveedores de datos ADO.NET 2.0
El mtodo System.Data.Common.SqlDataSourceEnumerator.Instance.GetDataSources devuelve una DataTable que tiene una fila para cada instancia de servidor SQL 2000 y 2005 accesibles. Las columnas muestran las propiedades ServerName, InstanceName, IsClustered, y Version. Al invocar el mtodo DbProviderFactories.GetFactoryClasses(), ste devuelve una tabla similar con una fila para cada proveedor Microsoft de datos controlados .NET instala-

44

Las novedades de ADO.NET 2.0 dos en el sistema, con columnas para las propiedades del proveedor Name, Description, InvariantName, y AssemblyQualifiedName y el nmero de SupportedClasses. Los proveedores de datos a terceros, como Oracle ODP.NET con Oracle 10g (Oracle.DataAccess.dll), no aparecen en la tabla.
El archivo machine.config contiene un elemento para cada uno de los cuatro espacios de nombre de proveedores de datos ADO.NET 2.0, y una seccin system.data que aade estos proveedores a DbProviderFactories. El mtodo GetFactoryClasses lee el archivo machine.config para proporcionar la lista de proveedores instalados.

El siguiente cdigo, del proyecto de ejemplo DataEnums.sln, puebla dos controles DataGridView con una instancia de SQLServer y un proveedor instalado de datos .NET de Microsoft:
Private Sub frmDataEnums_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim dtServers As DataTable = SqlDataSourceEnumerator.Instance.GetDataSources With dgvServers .DataSource = dtServers .AutoGenerateColumns = True .RowHeadersVisible = False .BorderStyle = BorderStyle.None End With Dim dtProviders As DataTable = DbProviderFactories.GetFactoryClasses() With dgvProviders .DataSource = dtProviders .AutoGenerateColumns = True .RowHeadersVisible = False .RowTemplate.Height = 22 .BorderStyle = BorderStyle.None End With End Sub

Al ejecutar el proyecto DataEnums, ste enumera las instancias de SQLServer y los proveedores de datos instalados. La Figura siguiente muestra una instancia por defecto de un SQLServer 2000 (OAKLEAF-W2K3), y una instancia MSDE con nombre (OAKLEAFW2K3\SHAREPOINT), una instancia de SQLServer 2005 (OAKLEAF-MS18), y una instancia SQLExpress (SQLX) con nombre (OAKLEAF-MS18\SQLEXPRESS), as como proveedores de datos accesibles o instalados en el ordenador de desarrollo utilizado para escribir este libro.

45

Bases de datos con Visual Basic

2.2.1 Entradas Batch en tablas de servidor SQL con el objeto SqlBulkCopy


La utilidad BCP del SQLServer y la sentencia BULK INSERT son los mtodos tradicionales para aadir filas muy rpidamente a las tablas del SQL Server. ADO.NET 2.0 ofrece una opcin alternativa: programar el nuevo objeto SqlBulkCopy. La fuente ms habitual para las filas son los DataReader en tablas relacionales. Otra alternativa es insertar filas desde documentos tabulares XML creando un juego de datos en tiempo de ejecucin runtime con una o ms tablas de datos para copiar.
Copiar documentos XML a las tablas del servidor SQL (un proceso llamado shredding) es mucho ms sencillo con SqlBulkCopy que con la propiedad para cargar de SQLXML3.0. Cargar requiere un esquema XML anotado para mapear elementos o atributos y aadirlos a las columnas de las tablas base. SqlBulkCopy tiene una coleccin de ColumnMappings que permite definir la relacin entre las columnas de la tabla de datos fuente y las de la tabla base destino.

Para insertar filas de un DataReader en una tabla base destino ya existente, hay que: 1. Crear una conexin y un comando para los datos fuente. Se puede usar cualquier proveedor .NET para conectarse a la fuente de datos y crear el DataReader. 2. Aplicar el mtodo Command.ExecuteReader para crear el DataReader. 3. Crear un objeto nuevo SqlBulkCopy que tendr como argumentos el string de conexin y la enumeracin apropiada en SqlBulkCopyOptions. 4. Definir el valor de la propiedad SqlBulkCopy.DestinationTableName. 5. Aadir miembros ColumnMapping a la coleccin ColumnMappings si el esquema de la tabla destino difiere de la tabla o la peticin fuente. 6. Definir otros valores opcionales para la propiedad SqlBulkCopy, como BatchSize y BulkCopyTimeout. 46

Las novedades de ADO.NET 2.0 7. Si la operacin de copia implica un nmero muy alto de registros o ejecuciones con una conexin de red muy lenta, aadir un tratador para el evento SqlBulkCopy.SqlRowsCopied a fin de mostrar en pantalla el nmero o el porcentaje de registros copiados. 8. Invocar el mtodo SqlBulkCopy.WriteToServer para ejecutar la operacin de copia. 9. Aplicar el mtodo SqlBulkCopy.Close() y, si ya ha terminado, cierre la conexin. En caso contrario, use de nuevo el objeto SqlBulkCopy para realizar cualquier otra operacin. La tabla siguiente describe los miembros de la enumeracin SqlBulkCopyOptions.
Nombre del miembro Descripcin

CheckConstraints Default FireTriggers KeepIdentity

Aplica un chequeo restringido durante el proceso de copia. No utiliza opciones (por defecto) para la operacin de copiar. Permite a detonadores INSERT dispararse durante el proceso de copia. Utiliza valores de identificacin de la tabla fuente en lugar de generar nuevos valores de identidad basados en los valores de integridad e incremento de la tabla destino. Conserva los valores null de la tabla fuente a pesar de los valores por defecto de las tablas destino. Aplica un candado a toda la tabla durante el proceso de copia, en lugar del candado por defeccto aplicado por filas. Hace que cada batch de la copia bulk se ejecute dentro de una transaccin.

KeepNulls TableLock UseInternalTransaction

KeepIdentity es el miembro ms importante de la enumeracin SqlBulkCopyOptions para tablas que usan una columna de identificacin como clave primaria. Si no se especifica esta opcin, las claves de la tabla destino podran ser distintas de los valores en la tabla fuente. Tambin es conveniente aadir la opcin UseInternalTransaction para prevenir copias parciales si ocurriera alguna excepcin durante el proceso.

El ejemplo ms sencillo de una operacin SqlBulkCopy crea copias de tablas en la misma base de datos. El siguiente cdigo del proyecto BulkCopySameSchema.sln copia las tablas de productos Northwind (Northwind Products) como ProductsCopy:
Private Sub btnCopyProds_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCopyProds.Click Dim sdrProds As SqlDataReader = Nothing Dim sbcProds As SqlBulkCopy = Nothing Try Dim lngTime As Long = Now.Ticks btnCopyProds.Enabled = False cnnNwind.Open() cmdProds.CommandText = "DELETE FROM ProductsCopy" Dim intRecs As Integer = cmdProds.ExecuteNonQuery

47

Bases de datos con Visual Basic


cmdProds.CommandText = "SELECT * FROM Products" sdrProds = cmdProds.ExecuteReader() If chkKeepIdentity.Checked Then sbcProds = New SqlBulkCopy(strConn, _ SqlBulkCopyOptions.UseInternalTransaction Or _ SqlBulkCopyOptions.KeepIdentity) Else sbcProds = New SqlBulkCopy(strConn, _ SqlBulkCopyOptions.UseInternalTransaction) Dim blnUseCm As Boolean = True If blnUseCm Then sbcProds.ColumnMappings.Clear() Dim intCol As Integer For intCol = 1 To 9 sbcProds.ColumnMappings.Add(intCol, intCol) Next intCol End If End If AddHandler sbcProds.SqlRowsCopied, New SqlRowsCopiedEventHandler(AddressOf ProdRowAdded) With sbcProds .DestinationTableName = "ProductsCopy" .BatchSize = CInt(nudBatchSize.Value) .BulkCopyTimeout = 30 .NotifyAfter = 1 .WriteToServer(sdrProds) .Close() End With sdrProds.Close() lngTime = Now.Ticks - lngTime txtTime.Text = Format(lngTime / 10000000, "0.000") FillProdsList(True) Catch excCopy As Exception MsgBox(excCopy.Message + excCopy.StackTrace, , "Products Bulk Copy Exception") Finally If Not sbcProds Is Nothing Then sbcProds.Close() End If If Not sdrProds Is Nothing Then sdrProds.Close() End If If Not cnnNwind Is Nothing Then cnnNwind.Close() End If btnCopyProds.Enabled = True End Try End Sub

48

Las novedades de ADO.NET 2.0 La propiedad SqlBulkCopy.NotifyAfter determina el nmero de filas aadidas antes de dispararse el evento SqlRowsCopied. A continuacin vemos el cdigo para un tratador de eventos SqlRowsCopied que muestra el progreso del proceso de copia de las tablas de productos en un cuadro de texto:
Sub ProdRowAdded(ByVal oSource As Object, ByVal oArgs As SqlRowsCopiedEventArgs) txtProdRows.Text = oArgs.RowsCopied.ToString Application.DoEvents() End Sub Mostrar el progreso de la copia reduce sustancialmente la velocidad de la copia. En las aplicaciones finales que deben proporcionar interaccin al usuario, el valor de la propiedad NotifyAfter debe ser como mnimo el 10 por ciento del nmero total de registros aadidos.

La siguiente figura muestra el formulario del proyecto BulkCopySameSchema.sln despus de copiar las dos tablas. Los scripts Transact-SQL recrean la tabla en el manejador de eventos frmBulkCopy_Load. Los cuadros de lista muestran la clave primera de la tabla fuente y los valores de segunda columna cuando se carga el formulario, y los valores de la tabla destino despus de la copia. El deslizador Batch Size determina el nmero de filas por intervalo; 0 (el valor por defecto) intenta enviar todas las filas al servidor en un solo intervalo. Definiendo 1 para el tamao del intervalo y copiando de nuevo las tablas se puede comparar el rendimiento de la copia frente a las operaciones fila por fila.

Cachear datos y cdigo provoca una diferencia considerable entre el tiempo de ejecucin de la copia bulk inicial y las siguientes. Por lo tanto, habra que comparar los tiempos de ejecucin con batchs de diferentes tamaos despus de una o dos pruebas con un tamao de batch definido en 0.

Deseleccionando el cuadro de verificacin Keep Source Identity, la opcin KeepIdentity se elimina del constructor SqlBulkCopy de la tabla de productos. En este caso, los valores de clave primarios se incrementan en 77 por cada operacin de copia. En el apartado

49

Bases de datos con Visual Basic siguiente se describe el tratador de eventos para el botn Show Connection Statistics (Mostrar estadsticas de conexin).

2.2.2 Obtener las estadsticas de conexin del servidor SQL


El nuevo mtodo SqlConnection.RetrieveStatistics averigua la instancia del servidor SQL con los datos de la conexin actual y devuelve un objeto IDictionary que contiene los 18 pares nombre/valor que muestra la siguiente figura.

Esta propiedad se ha de permitir explcitamente ejecutando una instruccin SqlConnection.EnableStatistics=True antes de invocar el mtodo RetrieveStatistic. El mtodo ms sencillo para para tratar los valores nombre/valor es encrustar el objeto IDictionary en un tipo HashTable y, despus reiterar la tabla Hash en un bucle ForEach...Next. El cdigo siguiente del proyecto BulkCopySameSchema.sln muestra en pantalla las estadsticas en un cuadro de texto de un sencillo formulario frmConnStats:
Private Sub btnShowStats_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnShowStats.Click Try htStats = CType(cnnNwind.RetrieveStatistics(), Hashtable) Dim txtStats As Control = frmConnStats.Controls.Item("txtStats") txtStats.Text = "" Dim oStat As Object Dim strStat As String For Each oStat In htStats.Keys strStat = oStat.ToString If InStr(strStat, "Time") > 0 Then txtStats.Text += strStat + " = " + _

50

Las novedades de ADO.NET 2.0


Microsoft.VisualBasic.Format(CLng(htStats(strStat)) /1000, _ "#,##0.000") + " secs" + vbCrLf Else txtStats.Text += strStat + " = " + htStats(strStat).ToString + vbCrLf End If Next frmConnStats.Show() frmConnStats.Controls.Item("btnClose").Focus() Catch excStats As Exception MsgBox(excStats.Message + excStats.StackTrace, , _ "Exception Displaying Connection Statistics") End Try End Sub

El cdigo anterior y el formulario frmConnStats se pueden aadir a cualquier proyecto que utilice una SqlConnection. Invoque el mtodo SqlConnection.ResetStatistics para inicializar los datos, excepto ConnectionTime.
Recuperar las estadsticas de conexin requiere establecer de nuevo una conexin con el servidor, por lo tanto es mejor reservar el uso de esta funcin para diagnosticar problemas de conexin.

2.3 Ejecutar comandos SQL de forma asincrnica


ADO.NET 2.0 aade los mtodos BeginExecuteReader, BeginExecuteXmlReader, y BeginExecuteNonQuery (junto con los correspondientes mtodos End) para las clases SqlCommand. Estos mtodos permiten ejecutar cdigo mientras se espera a que un comando complete su ejecucin. Para ejecutar un comando SqlCommand hay que aadir Async=True a la cadena de comando que se pas al constructor de la SqlConnection. En los apartados siguientes se describe, con el correspondiente cdigo de ejemplo, para los tres modelos de ejecucin de comandos SqlCommand asncronos que soporta la interfaz IasyncResul. La siguiente figura ilustra las bases de datos, conexiones y comandos que se utilizan con los tres modelos. Obtendr resultados ms interesantes del proyecto de ejemplo AsyncDataOperations.sln si dispone de dos o tres instancias del servidor SQL Server 2000 o 2005 con la base de datos de ejemplo de Northwind para cada instancia (figura en la pgina siguiente).
El proveedor de memoria compartida por defecto del SQLServer 2000 no soporta comando ascronos, por lo que hay que utilizar localhost, y no (local), como valor para el servidor o la fuente de datos de la cadena de conexin en cualquier instancia local del SQLServer 2000.

51

Bases de datos con Visual Basic

2.3.1 El modelo Polling


El modelo Polling es el ms sencillo de los tres. La siguiente figura ilustra el flujo del programa para tres conexiones asncronas (figura en la pgina siguiente). El cdigo siguiente abre un comando asncrono en la base de datos Northwind, en una instancia del servidor local SQL y utiliza un bucle While que consulta constantemente para que se complete el mtodo BeginExecuteReader:
Private Sub PollingAsyncCommand() Try Dim strConn As String = "Data Source=localhost;" + _ "Initial Catalog=Northwind;Integrated Security=SSPI;Async=True" Dim cnnCusts As SqlConnection = New SqlConnection(strConn) cnnCusts = New SqlConnection(strCusts) Dim cmdCusts As SqlCommand = cnnCusts.CreateCommand With cmdCusts .CommandType = CommandType.Text .CommandTimeout = 60 .CommandText = "SELECT * FROM Customers" End With Dim asrCustsReader As IAsyncResult = _ cmdCusts.BeginExecuteReader(CommandBehavior.CloseConnection) While Not asrCustsReader.IsCompleted 'Do something while waiting

52

Las novedades de ADO.NET 2.0

End While Dim sdrCusts As SqlDataReader = cmdCusts.EndExecuteReader(asrCustsReader) 'Do something with the data sdrCusts.Close() sdrCusts.Dispose() Catch excAsync As Exception MsgBox(excAsync.Message + excAsync.StackTrace, , "Async Operation Exception") End Try End Sub

La ejecucin asncrona con polling es muy prctica para las operaciones sencillas dentro del bucle While, como mostrar una barra de progresin cuyo valor vienen definido por las pulsaciones e un contador. Tambin se puede incluir cdigo que permita al usuario cancelar un comando antes del tiempo indicado por su propiedad CommandTimeout. Al salir del bucle, la ejecucin del mismo queda bloqueada hasta que se hayan completado todos los comandos o haya expirado su tiempo de ejecuin. El cdigo se va ejecutando en el hilo del formulario, por lo que los comandos mltiples se eje53

Bases de datos con Visual Basic cutan secuencialmente en conexiones separadas. Si las operaciones mltiples DataReader.Read son complejas, se pueden ejecutar en un hilo dedicado al nuevo objeto BackgroundWorker. Esto permite invocar el siguiente mtodo BeginExecuteReader inmediatamente despus de que la propiead IAsyncResult.IsComplete cambie a True.

2.3.2 El mdelo Callback


El mdelo asncrono callback es ms flexible que el polling porque utiliza un manejador de callback que ejecuta su propio hilo, extrado de la consulta. El modelo callback permite entrelazar comandos con bases de datos mltiples que se ejecutan en lo mismos servidores o en servidores distintos. En ese caso, hay que especificar el tratador callback y pasarle el comando como objeto al segundo parmetro del mtodo sobrecargado BeginExecuteReader. Al pasar el comando se tiene acceso al mtodo EndExecuteReader con la propiedad IAsyncResult.AsyncState en el tratador callback. La siguiente figura muestra el flujo del programa en el modo callback. Las lneas punteadas indican la ejecucin directa de los mtodos Read, sin tener que esperar a que estn disponibles dotos los juegos de filas.

54

Las novedades de ADO.NET 2.0 A continuacin mostramos un ejemplo de cdigo para un comando asncrono sencillo SqlCommand que usa el mtodo callback:
Private Sub CallbackAsyncCommand() Try Dim strConn As String = "Data Source=localhost;" + _ "Initial Catalog=Northwind;Integrated Security=SSPI;Async=True" Dim cnnCusts As SqlConnection = New SqlConnection(strConn) cnnCusts = New SqlConnection(strCusts) Dim cmdCusts As SqlCommand = cnnCusts.CreateCommand With cmdCusts .CommandType = CommandType.Text .CommandTimeout = 60 .CommandText = "SELECT * FROM Customers" End With cnnCusts.Open() Dim objCmdCusts As Object = CType(cmdCusts, Object) Dim asrCustsReader As IAsyncResult = _ cmdCusts.BeginExecuteReader(New AsyncCallback(AddressOf CustomersHandler), _ objCmdCusts, CommandBehavior.CloseConnection) Catch excAsync As Exception MsgBox(excAsync.Message + excAsync.StackTrace, , "Async Operation Exception") End Try End Sub

Y aqu est el cdigo del tratador callback para el procedimiento anterior:


Private Sub CustomersHandler(ByVal iarResult As IAsyncResult) Try Dim sdrData As SqlDataReader = CType(iarResult.AsyncState, SqlCommand).EndExecuteReader(iarResult) With sdrData Dim intCtr As Integer While .Read For intCtr = 0 To .FieldCount - 1 objData = .GetValue(intCtr) Next intCtr End While .Close() .Dispose() End With Dim blnIsPool As Boolean = Thread.CurrentThread.IsThreadPoolThread CustomersDone(Thread.CurrentThread.ManagedThreadId, blnIsPool) Catch excHandler As Exception MsgBox(excHandler.Message + excHandler.StackTrace, , "Customers Handler Exception") End Try End Sub

55

Bases de datos con Visual Basic


La mayor parte de los ejemplos de cliente de este libro conectan con finales back en la misma mquina que el cliente; eso significa que la ejecucin sincrnica de los DataReaders se completa rpidamente o bien arroja inmediatamente una excepcin. La ejecucin asincrnica resulta especialmente eficaz en los proyectos con DataReaders mltiples que conectan individualemente a bases de datos remotas, especialmente si una o ms conexines se ejecutan en un WAN.

El proyecto de ejemplo AsyncDataOperations.sln simula una aplicacin de produccin que conecta a bases de datos trabajadas en red mltiple estableciendo conexiones individuales SqlConnections con tablas Northwind de clientes, pedidos y detalles de pedidos (Customers, Orders, y Order Details). Si tiene acceso a tres instancias de servidor SQL puede modificar las cadenas de conexin cambiando los nombres del segundo y el tercer servidor (OAKLEAF-W2K3 y OAKLEAF-MS2K3) por RemoteServerName, y seleccionar el cuadro de texto Use Multiple Instances para mostrar la secuencia de invocaciones de los mtodos Connection.Open, BeginExecuteReader, y EndExecuteReader. La siguiente figura muestra dos instancias del formulario AsyncDataOperations.

Una clase timer VB.NET, escrita por Alastair Dallas, proporciona la resolucin requerida para obtener datos de sincronizacin con sentido. Los nmeros entre parntesis de las entradas del cuadro de lista son los valores System.Threading.Thread .CurrentThread.ManagedThreadId de las instancias del formulario y los tres manejadores de callback El sufijo P indica que los hilos del manejador son del pool de hilos. La sincronizacin de datos es para una segunda ejecucin (cacheada).

El cdigo de ejemplo ejecuta objetos Customer desde el host local y objeto Orders y Order Details desde los servidores de red. La tabla Orders Details tiene unas 500.000 filas, por lo que leer toda la tabla lleva unos 2 segundos. La velocidad de ejecucin en una LAN de bajo trfico es normalmente suficiente para devolver los datos en la secuencia BeginExecuteReadercalling, como muestra la figura anterior (izquierda). Todas las operaciones de restablecimiento de datos se ejecutan en un solo hilo (13). Para simular una conexin WAN con la tabla Orders, el cdigo en OrdersHandler provoca un retraso de unos segundos mediante mltiples operaciones en cada fila DataReader en un bucle ani56

Las novedades de ADO.NET 2.0 dado. En este caso, DataReader de Orders completa la ejecucin antes que el DataReader de Customers, el cual termina la ejecucin antes que el DataReader de Order Details, tal como muestra la figura anterior (derecha). En este caso, la restauracin de Order Details se ejecuta en un hilo (14P), y Customers y Orders en otro distinto (13P).
El uso del modelo callback en las aplicaciones de formulario Windows es un tema controvertido. Miembros del equipo de datos de VS 2005 de Microsoft recomiendan no utilizar este modelo con los proyectos de formulario de Windows. Los objetos de ADO.NET no son seguros en los hilos, y los problemas con hilos son difciles de depurar.

2.3.3 El modelo WaitAll


Una alternativa al mtodo callback es utilizar un array WaitHandle y asignarlo a un elemento en cada llamada de mtodo BeginExecuteReader. Un WaitHandle.WaitAll(whArray) detiene la ejecucin del cdigo hasta que todos los DataReaders estn listos para sus llamadas EndExecuteReader. Este comportamiento hace al modelo WaitAll especialmente adecuado para clientes que procesan juegos de filas relacionados, ya que no se necesita el bucle de sincronizacin que se mostr anteriormente. La siguiente figura muestra el diagrama de flujo en el modelo WaitAll.

57

Bases de datos con Visual Basic La manera ms sencilla de ver los resultados del mtodo WaitAll en un entorno de formulario Windows es crear una versin multi-hilo o multi-threaded apartment (MTA) de un procedimiento habitual Sub Main. Por defecto, los procedimientos de VB.NET utilizan el modelo de hilo nico (single-threaded apartment, STA) requerido para los formularios basados en Win32. Llamando WaitAll con mltiples WaitHandles, arroja una excepcin dentro de los procedimiento STA, por lo que hay que aadir el prefijo <MTAThreadAttribute()> a la sentencia SharedSubMain. El siguiente listado es una adaptacin del cdigo del modelo callback para implementar el array multi-elemento WaitHandle:
<MTAThreadAttribute()> _ Shared Sub Main() Dim blnIsMultiServer As Boolean Try cnnCusts = New SqlConnection(strCusts) Dim cmdCusts As SqlCommand = cnnCusts.CreateCommand With cmdCusts .CommandType = CommandType.Text .CommandTimeout = 10 .CommandText = "SELECT * FROM Customers" End With If blnIsMultiServer Then cnnOrders = New SqlConnection(strOrders) Else cnnOrders = New SqlConnection(strCusts) End If Dim cmdOrders As SqlCommand = cnnOrders.CreateCommand With cmdOrders .CommandType = CommandType.Text .CommandTimeout = 10 .CommandText = "SELECT * FROM Orders" End With If blnIsMultiServer Then cnnDetails = New SqlConnection(strDetails) Else cnnDetails = New SqlConnection(strCusts) End If Dim cmdDetails As SqlCommand = cnnDetails.CreateCommand With cmdDetails .CommandType = CommandType.Text .CommandTimeout = 10 .CommandText = "SELECT * FROM [Order Details]" End With Dim timHiRes As New clsTimer timHiRes.Start()

58

Las novedades de ADO.NET 2.0


Dim awhHandle(2) As WaitHandle cnnCusts.Open() astrListItems(0) = Format(timHiRes.ElapsedTime, "0.000") + " - 1 Opened Customers connection" Dim asrCustomersReader As IAsyncResult asrCustomersReader = cmdCusts.BeginExecuteReader(CommandBehavior.CloseConnection) awhHandle(0) = asrCustomersReader.AsyncWaitHandle astrListItems(1) = Format(timHiRes.ElapsedTime, "0.000") + " - 2 BeginExecuteReader: Customers" cnnOrders.Open() astrListItems(2) = Format(timHiRes.ElapsedTime, "0.000") + " - 3 Opened Orders connection" Dim asrOrdersReader As IAsyncResult asrOrdersReader = cmdOrders.BeginExecuteReader(CommandBehavior.CloseConnection) awhHandle(1) = asrOrdersReader.AsyncWaitHandle astrListItems(3) = Format(timHiRes.ElapsedTime, "0.000") + " - 4 BeginExecuteReader: Orders" cnnDetails.Open() astrListItems(4) = Format(timHiRes.ElapsedTime, "0.000") + " - 5 Opened Details connection" Dim asrDetailsReader As IAsyncResult asrDetailsReader = cmdDetails.BeginExecuteReader(CommandBehavior.CloseConnection) awhHandle(2) = asrDetailsReader.AsyncWaitHandle astrListItems(5) = Format(timHiRes.ElapsedTime, "0.000") + " - 6 BeginExecuteReader: Order Details" WaitHandle.WaitAll(awhHandle) Dim sdrCustomers As SqlDataReader = cmdCusts.EndExecuteReader(asrCustomersReader) sdrCustomers.Close() sdrCustomers.Dispose() Dim sdrOrders As SqlDataReader = cmdOrders.EndExecuteReader(asrOrdersReader) sdrOrders.Close() sdrOrders.Dispose() Dim sdrDetails As SqlDataReader = cmdDetails.EndExecuteReader(asrDetailsReader) sdrDetails.Close()

59

También podría gustarte