visualbasic2005 05.qxp 02/08/2007 18:27 pÆgina 197...

43
Procesar actualizaciones offline requiere sincronizar el contenido del formulario con el registro actual de cliente llamando el procedimiento SynchronizeOffline- Orders. 197 Añadir código para validar datos y gestionar la concurrencia

Upload: tranminh

Post on 01-Feb-2018

215 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Procesar actualizaciones offline requiere sincronizar el contenido del formulariocon el registro actual de cliente llamando el procedimiento SynchronizeOffline-Orders.

197

Añadir código para validar datos y gestionar la concurrencia

VisualBasic2005_05.qxp 02/08/2007 18:27 PÆgina 197

Page 2: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

199

Capítulo 6

La aplicación de técnicas avanzadas de los DataSets

DataSets y DataGridViews vinculados son los elementos centrales en el acceso a datos deADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores tra-taban sobre los aspectos básicos en torno a los DataSets y formularios Windows vincu-lados. Este capítulo amplía las técnicas de programación de los elementos DataSet yDataGridView con los siguientes puntos principales:

Permitir las transacciones ligeras de código en la actualización de las bases dedatos.

Añadir columnas a las DataTables y DataGridViews desde consultas SELECT con unINNER JOIN.

Mostrar y manipular imágenes en las DataGridViews.

Generar DataSets a partir de esquemas XML existentes.

Editar documentos XML con DataGridViews.

Crear y trabajar con clases de objetos serializables.

Vincular DataGridViews a colecciones genéricas DataList.

Todos, excepto uno, de los proyectos de ejemplo de este capítulo utilizan las bases dedatos de ejemplo Northwind para proporcionar un número suficiente de registros yvariedad de tipos de datos para demostrar el rendimiento relativo de las técnicas deacceso y edición de datos que se verán. En los ejemplos con tablas base sencillas, depocas filas y columnas, y documentos o esquemas fuente en un sencillo XML, no se tra-tarán los problemas de rendimiento y otros aspectos del diseño de código que se veránen este capítulo.

Para los ejemplos SystemTransactions.sln y DataGridViewImages.sln debe tener instalado SQLServer 2005 o SQL Server Express con las bases de datos de ejemplo Northwind y Adventu-reWorks. Los demás proyectos de ejemplo funcionan con SQL Server 2000, MSDE, SQL Server2005 o SQLExpress y la base de datos Northwind.

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 199

Page 3: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

6.1 Aplicar transacciones a las actualizaciones de DataSetsCasi todas las DBAs requieren en "sus" tablas de producción operaciones de actualiza-ción, entrada y eliminación que se realicen con procedimientos almacenados y relacio-nados en una transacción. La transacción garantiza que todas las actualizaciones decada tabla, en una operación batch, se realizarán con éxito (commit) o fallarán (roll back)en grupo. Tal como vimos anteriormente en este libro, ADO.NET 1.0 introducía la pro-piedad IDbCommand.Transaction y la interfaz IdbTransaction para la actualización contransacciones de múltiples tablas. Los objetos SqlTransaction y OracleTransaction songenuinos de CLR, OleDbTransaction y OdbcTransaction son envoltorios gestionados delos componentes de transacciones basados en OLE DB y ODBC COM.

El ejemplo SqlTransaction es relativamente sencillo porque utiliza un par de métodosSqlCommand.ExecuteNonQuery que actualizaban las tablas dentro de una transacciónlocal. De todas formas, los DataSets de ADO.NET 1.x requieren mucho más código paraasignar un único objeto SqlTransaction a las propiedades UpdateCommand.Transaction,InsertCommand.Transaction y DeleteCommand.Transaction de múltiples SqlDataAdapters.Un procedimiento típico de ADO.NET 1.x para actualizar tablas base a partir de modi-ficaciones simuladas realizadas por un usuario en tablas de datos sin conexión, inclu-ye las siguientes acciones:

1. Crear un juego de datos no tipificado con un SqlDataAdapter por cada tabla de latransacción.

2. Crear un CommandBuilder para definir la propiedad ...Command de cada DataAdap-ter desde la sentencia SelectCommand o desde el procedimiento almacenado.

3. Abrir una SqlConnection, poblar las tablas de datos con el método DataAdapter.Filly cerrar la conexión a la base de datos.

4. Modificar algunas filas de cada tabla de datos a modo de prueba.5. Declarar e iniciar un objeto SqlTransaction.6. Abrir la conexión a la base de datos y asignar la SqlTransaction a las tres propieda-

des de lenguaje de gestión de datos, en inglés Data Management Language (DML),...Command.Transaction de cada DataAdapter.

7. Invocar el método Update en cada DataAdapter para que se ejecute el ...Commandapropiado para cada valor de la propiedad DataRowState de cada fila modificada–los valores son: Added, Modified o Deleted.

8. Ejecutar la transacción si no se ha producido ningún error; de lo contrario, desha-cer todos los pasos realizados y cerrrar la conexión a la base de datos.

El siguiente código, en el que se incluyen las operaciones que acabamos de mencionar,muestra en negrita las instrucciones directamente relacionadas con el procesamiento dela SqlTransaction:

Dim trnUpdate As SqlTransaction = Nothing

Dim cnNwind As New SqlConnection(My.Settings.NorthwindConnectionString)

Dim dsNwind As New DataSet("dsNwind")

Try

200

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 200

Page 4: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Dim daOrders As New SqlDataAdapter("SELECT * FROM Orders " + _

"WHERE OrderID > 11077;", cnNwind)

Dim cbOrders As SqlCommandBuilder = New SqlCommandBuilder(daOrders)

daOrders.UpdateCommand = cbOrders.GetUpdateCommand

daOrders.InsertCommand = cbOrders.GetInsertCommand

daOrders.DeleteCommand = cbOrders.GetDeleteCommand

Dim daDetails As New SqlDataAdapter("SELECT * FROM [Order Details] " + _

"WHERE OrderID > 11077;", cnNwind)

Dim cbDetails As New SqlCommandBuilder(daDetails)

daDetails.UpdateCommand = cbDetails.GetUpdateCommand

daDetails.InsertCommand = cbDetails.GetInsertCommand

daDetails.DeleteCommand = cbDetails.GetDeleteCommand

cnNwind.Open()

daOrders.Fill(dsNwind, "Orders")

daDetails.Fill(dsNwind, "OrderDetails")

cnNwind.Close()

Dim dtOrders As DataTable = dsNwind.Tables("Orders")

Dim intRow As Integer

For intRow = 0 To dtOrders.Rows.Count - 1

If blnReset Then

dtOrders.Rows(intRow).Item("ShippedDate") = DBNull.Value

Else

dtOrders.Rows(intRow).Item("ShippedDate") =

Today.ToShortDateString

End If

Next intRow

Dim dtDetails As DataTable = dsNwind.Tables("OrderDetails")

For intRow = 0 To dtDetails.Rows.Count - 1

If blnReset Then

dtDetails.Rows(intRow).Item("Quantity") = _

dtDetails.Rows(intRow).Item("Quantity") - 1

Else

dtDetails.Rows(intRow).Item("Quantity") = _

dtDetails.Rows(intRow).Item("Quantity") + 1

End If

Next intRow

If chkViolateConstraint.Checked Then

dtDetails.Rows(intRow - 1).Item("OrderID") = 100

End If

cnNwind.Open()

trnUpdate = cnNwind.BeginTransaction

daOrders.UpdateCommand.Transaction = trnUpdate

daOrders.InsertCommand.Transaction = trnUpdate

201

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 201

Page 5: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

daOrders.DeleteCommand.Transaction = trnUpdate

daOrders.Update(dsNwind, "Orders")

daDetails.UpdateCommand.Transaction = trnUpdate

daDetails.InsertCommand.Transaction = trnUpdate

daDetails.DeleteCommand.Transaction = trnUpdate

daDetails.Update(dsNwind, "OrderDetails")

trnUpdate.Commit()

Catch exc As Exception

If trnUpdate IsNot Nothing Then

trnUpdate.Rollback()

End If

Finally

cnNwind.Close()

End Try

End If

Si no se define explícitamente el valor de la propiedad DataAdapter.TypeCommand con el méto-do CommandBuilder.GetTypeCommand, tampoco se podrá incluir el comando en la transaccióncon el valor de la propiedad SQLDataAdapter.TypeCommand.Transaction.

El proyecto SystemTransactions.sln contiene el código de ejemplo de este apartado y losdos siguientes. El procedimiento DataAdapterTransactions de Transactions.vb, contiene elejemplo anterior. Para ejecutar el procedimiento, abra, construya y ejecute el proyectoy, a continuación, pulse el botón Update con el cuadro de verificación Show Update inGrid seleccionado. Entionces, el código actualiza los valores ShippedDate de la tablaOrders, con los datos actuales del sistema, y suma uno al valor de Quantity en la tablaOrder Details, en todos los records con un OrderID mayor que 11077 (ver figura 6.1).Pulse el botón Reset para asignar el valor Null a ShippedDate y restar uno a los valoresQuantity.

La implementación de IdbTransaction que los proveedores de datos originales de ADO1.x han realizado, limitan la posibilidad de las transacciones locales a una sola base dedatos. Las transacciones distribuidas, efectuadas por el Distributed TransactionCoordinator (MSDTC), toman como base el espacio de nombres System.EnterpriseServicesy la herencia de ServicedComponent.

6.1.1 Simplificar el listado con System.Transactions.NET Framework 2.0 incluye el espacio de nombres System.Transactions con el que sedefinen varias clases de clave que mejoran las posibilidades de transacción conADO.NET 2.0 y simplifican la programación. Las clases más usadas son Transaction-Scope, Transaction y CommittableTransaction. La principal ventaja que aportan las clasesSystem.Transactions a la gestión de transacciones es el listado automático de un gestorlocal de fuentes (RM, Resource Manager), como SQL Server 2005, en una transacción ges-tionada, por defecto, por un gestor de transacción ligera –en inglés: LightweightTransaction Manager (LTM). El listado posterior de un RM remoto promueve, de forma

202

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 202

Page 6: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

automática, la transacción local y la convierte en una transacción distribuida con unOleTx Transaction Manager (OTM).

El listado de un RM local no soporta las transacciones promovibles, como SQLServer2000, que también promueve las transacciones ligeras. El LTM ofrece un alto rendi-miento con un consumo mínimo de recursos; la promoción a OTM y DTC implica unrendimiento y un consumo de recursos similares a los de las ServicedComponents.

6.1.2 Listar SqlDataAdapters en una transacción implícitaPara sacar partido al nuevo modelo de transacción de .NET 2.0 hay que añadir una refe-rencia de proyecto al espacio de nombres System.Transactions y una sentenciaImportsSystem.Transactions al archivo de clase. Se puede obtener una transacción implí-cita alistable creando un objeto TransactionScope y asignándolo a un bloque Using...-EndUsing que incluya un bloque Try...EndTry. Los métodos transaccionables, comoSqlDataAdapter.Update o SqlTableAdapter.Update, que se ejecutan dentro del bloqueUsing, automáticamente se alistan en la transacción. Si los métodos se desarrollan conéxito, al ejecutar el método TransactionScope.Complete y deshacerse del objeto Transac-tionScope saliendo del bloque Using, se hace válida la transacción. Si un método arrojauna excepción, al salir del bloque Using sin ejecutar el método TransactionScope.-Complete, se volverá atrás en la transacción.

El siguiente procedimiento remplaza las diez líneas de código del listado anterior(empezando en cnNwind.Open()), que crea el objeto SqlTransaction y alista los objetosDataAdapter.TypeCommand de la transacción:

203

La aplicación de técnicas avanzadas de los DataSets

Figura 6.1: si no ha añadido datos en las tablas Orders y OrderDetails de la base de datos Northwind en los capítulos anteriores,deberá hacerlo ahora para poder actualizar con SqlDataAdapters.

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 203

Page 7: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

'cnNwind.Open() 'Opening here disables enlistment (no transaction)

Dim tsExplicit As New TransactionScope

Using tsExplicit

Try

'cnNwind.Open() 'Opening here uses one connection for transaction

daOrders.Update(dsNwind, "Orders")

daDetails.Update(dsNwind, "OrderDetails")

tsExplicit.Complete()

Catch exc As Exception

MsgBox(exc.Message)

Finally

cnNwind.Close()

End Try

End Using

Si utiliza los DataAdapters para abrir (y cerrar) sus conexiones automáticamente, elanterior bloque Using abrirá dos conexiones en el SQL Server 2005 (normalmente SPID51 y SPID 53) y promoverá la transacción, causando así un leve descenso en el rendi-miento. Si se abre explícitamente una sola conexión (cnNwind), antes de crear la trans-acción implícita con el constructor TransactionScope, las transacciones quedarán desac-tivadas para los métodos Update. Pero si la conexión se abre explícitamente después decrear la transacción, las dos operaciones Update se ejecutarán en la misma conexión(normalmente SPID 51), maximizando así la velocidad de ejecución.

Nota: Para la ejecución del ejemplo anterior con el proyecto de ejemplo SystemTransactions.sln,defina blnSysTran=True en el procedimiento DataAdapterTransactions y pulse el botón Updateo Reset. Para verificar que las operaciones de Update se están efectuando, seleccione el cuadro deverificación Violate constraint (Rollback), pulse Update, y compruebe que una sola transgresiónde restricción de clave foránea en la tabla Order Details vuelve atrás todos los cambios realiza-dos en las tablas Orders y Order Details.

6.1.3 Autolistar SqlTableAdapters en una transacción implícitaEl código siguiente realiza una actualización transactual de dos SqltableAdapters deADO.NET 2.0 autolistando sus métodos Update en un LTM:

Dim tsImplicit As New TransactionScope

Using tsImplicit

Try

'Adapter opens connections automatically

Me.Order_DetailsTableAdapter.Update(Me.NorthwindDataSet.Order_Details)

Me.OrdersTableAdapter.Update(Me.NorthwindDataSet.Orders)

tsImplicit.Complete()

Catch exc As Exception

'Error handling

Finally

'Adapter closes connections automatically

End Try

End Using

204

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 204

Page 8: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Tal como sucede con los SqlDataAdapters de ADO.NET 1.x, los SqlTableAdapters deADO.NET 2.0 también abren dos conexiones automáticamente y promueven así unatransacción implícita. El código siguiente abre una sola conexión y la asigna a los dosSqlTableAdapters para impedir que promuevan la transacción:

Dim tsImplicit As New TransactionScope

Using tsImplicit

Try

'Open a single connection and assign it to both SqlTableAdapters

Dim cnNwind As New

SqlConnection(My.Settings.NorthwindConnectionString)

cnNwind.Open()

Me.Order_DetailsTableAdapter.Connection = cnNwind

Me.OrdersTableAdapter.Connection = cnNwind

Me.Order_DetailsTableAdapter.Update(Me.NorthwindDataSet.Order_Details)

Me.OrdersTableAdapter.Update(Me.NorthwindDataSet.Orders)

tsImplicit.Complete()

Catch exc As Exception

'Error handling

Finally

cnNwind.Close()

End Try

End Using

Para abrir una sola conexión para transacciones implícitas, defina blnOpenConnection=True enel manejador del evento bindingNavigatorSaveData, modifique un record de la tabla Orders yotro, como mínimo en su tabla Order Details, y pulse el botón Save o el botón Save Data de latabla de herramientas.

6.1.4 SQL Profiler para rastrear transaccionesLa herramienta Profiler de SQL Server 2005 ha sido actualizada con nuevas característi-cas tales como las transacciones promovibles. Para rastrear los eventos BEGIN TRAN,PROMOTE TRAN, COMMIT TRAN y ROLLBACK TRAN, deberá pasar esos eventosdesde la categoría Transactions a la plantilla por defecto T-SQL, u otra plantilla similarde rastreo personalizada. La siguiente figura muestra el rastreo realizado por SQL Pro-filer de una actualización transactuada con SqlTableAdapter, con dos conexiones autoge-neradas que provocan que la transacción se promueva. La figura siguiente ilustra lamisma transacción pero con una sola conexión asignada explícitamente en la propie-dad Connection de los dos SqlTableAdapters.

La edición SQL Server Express no incluye ni soporta el uso de SQL Profiler. De todosmodos, se puede usar el Component Services Manager para contabilizar instancias de lastransacciones distribuidas que resultan de promover transacciones implícitas o de eje-cutar transacciones explícitas y que son el tema de los apartados siguientes. La segun-da figura de la página siguiente muestra el cuadro de diálogo Servicios de cmponentescon las estadísticas de 94 transacciones promovidas, generadas por el mismo proyecto

205

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 205

Page 9: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

de ejemplo. Nótese que el tiempo de respuesta medio de las transacciones distribuidases de unos 4 segundos. Los ítems de las transacciones sólo aparecen en la ventana Listade transacciones cuando están activados (ver figura de la página siguiente).

6.1.5 Listar manualmente SqlTableAdapters en una transacción explícitaSi prefiere el modelo de transacción "tradicional" con un alistamiento explícito de losobjetos transactuados y control granular de las invocaciones de los métodos Commit oRollback, puede utilizar el objeto CommittableTransaction, tal como se muestra en el códi-go siguiente:

Dim tsExplicit As New CommittableTransaction

Try

Me.Order_DetailsTableAdapter.Connection.Open()

Me.OrdersTableAdapter.Connection.Open()

Me.Order_DetailsTableAdapter.Connection.EnlistTransaction(tsExplicit)

Me.OrdersTableAdapter.Connection.EnlistTransaction(tsExplicit)

Me.Order_DetailsTableAdapter.Update(Me.NorthwindDataSet.Order_Details)

206

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 206

Page 10: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Me.OrdersTableAdapter.Update(Me.NorthwindDataSet.Orders)

tsExplicit.Commit()

Catch exc As Exception

tsExplicit.Rollback()

Finally

Me.OrdersTableAdapter.Connection.Close()

Me.Order_DetailsTableAdapter.Connection.Close()

End Try

Envoltorios explícitos de transacción para las actualizaciones con SqlTableAdapter son,por defecto, las transacciones distribuidas. Las promociones se producen cuando elcódigo lista un segundo objeto SqlTableAdapter.Connection en la transacción.

6.1.6 Definir las opciones TransactionScope y TransactionEl constructor TransactionScope tiene siete sobrecargas, pero las dos siguientes son lasmás útiles en las transacciones de base de datos:

Public Sub New(ByVal scopeOption As System.Transactions.TransactionScopeOption,

ByVal scopeTimeout As System.TimeSpan)

Public Sub New(ByVal scopeOption As System.Transactions.TransactionScopeOption,

ByVal transactionOptions As System.Transactions.TransactionOptions)

207

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 207

Page 11: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

La enumeración TransactionScopeOption tiene los tres miembros siguientes:

TransactionScopeOption.Requires

TransactionScopeOption.RequiresNew

TransactionScopeOption.Suppress

El valor por defecto es Requires (una transacción). Especifique Suppress si no quiere queTransactionScope utilice la transacción ambiente. A continuación vemos los dos miem-bros TransactionScopeOption:

TransactionOption.IsolationLevel

TransactionOption.Timeout

IsolationLevel es por defecto Serializable, pero puede ser cualquiera de los siete miembrosque aparecieron en el primer capítulo de este libro. Sólo SQL Server 2005 soportaSnapshot en Isolation. El valor por defecto de Timeout es 1 minuto.

6.2 Añadir relaciones a los SelectCommand de la tabla de datos

Los DataSets actualizan tablas individuales, pero eso no significa que no se puedan aña-dir relaciones al SelectCommand de una tabla. Las relaciones permiten mejorar las edi-ciones de los usuarios añadiendo columnas de sólo lectura desde una relación "demuchos a uno" con una tabla relacionada. Como ejemplo, si se añaden las columnasProductName, QuantityPerUnit y UnitPrice de la tabla Products Northwind a unDataGridView de items de Order Details, se mejora la legibilidad y se minimizan los erro-res en la entrada de datos. La columna UnitPrice se puede utilizar para dar valores pordefecto de los registros nuevos y actualizar la columna UnitPrice de la tabla OrderDetails cuando se realicven cambios en el ProductID.

Añadir columnas desde relaciones muchos a uno (many-to-one) no es el sustituto ideal a lascolumnas de cuadro combinado pobladas por listas lookup. La técnica descrita anteriormente, esnormalmente un método más efectivo siempre que se trabaje con formularios de entrada de datosdonde el número de ítems del cuadro combinado sea inferior a 100.

El proyecto de ejemplo de esta sección, SelectCommandJoins.sln, demuestra cómo añadirrelaciones a los SelectCommand y sacar partido de la relación many-to-one para simplifi-car la actualización de la tabla base Order Details. El proyecto empieza con una fuentede la base de datos Northwind que incluye las tablas Orders, Order Details, y Products.Los componentes de datos incluyen Orders autogenerados, Order_Details DataGrid-Views, TableAdapters y BindingSources. La tabla Products porporciona el ProductName y elUnitPrice necesarios para editar y crear nuevos records de Order Details.

Añada ProductsTableAdapter y ProductsBindingSource a la bandeja arrastrando el iconode la tabla Products desde la ventana Origenes de datos hasta el formulario Join.vb y des-pués borre el ProductsDataGridView que se ha añadido al formulario.

208

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:25 PÆgina 208

Page 12: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

6.2.1 Añadir una relación a SelectCommandA continuación vemos los pasos para añadir un INNER JOIN entre las tablas OrderDetails y Products de la operación Fill:

1. En la ventana Diseñador de DataSet, pulse con el botón derecho la cabecera delTableAdapter de Order Details y seleccione Propiedades.

2. En la ventana Propiedades, expanda el nodo SelectCommand, pulse el nodoCommandText, y pulse el botón del constructor para abrir el cuadro de diálogoGenerador de consultas.

3. Pulse con el botón derecho del ratón el panel de las tablas, seleccione Agregar tabla,y añada la tabla Products.

4. Seleccione las columnas ProductName, QuantityPerUnit y UnitPrice de la tablaProducts.

5. Cambie dbo.Products.UnitPriceASExpr1 por dbo.Products.UnitPriceASListPrice.

6. Pulse el botón Ejecutar consulta para ver los resultados en la parrilla.7. Pulse el botón Aceptar para cerrar el cuadro de diálogo Generador de consultas y

pulse el botón No cuando le pregunten si quiere regenerar los comandos de actua-lización basándose en el nuevo comando de selección.

8. Pulse con el botón derecho la cabecera de Order Details y seleccione Ajustar automá-ticamente para mostrar las columnas ProductName, ListPrice y QuantityPerUnit (verla figura de la página siguiente).

209

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 209

Page 13: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

9. Abra la ventana Propiedades y verifique que la sentencia SQL CommandText de losnodos DeleteCommand, InsertCommand y UpdateCommand incluye sólo columnas dela tabla Order Details.

A continuación vemos el valor de la propiedad CommandText de SelectCommand:

SELECT dbo.[Order Details].OrderID, dbo.[Order Details].ProductID,

dbo.[Order Details].UnitPrice, dbo.[Order Details].Quantity,

dbo.[Order Details].Discount, dbo.Products.ProductName,

dbo.Products.UnitPrice AS ListPrice, dbo.Products.QuantityPerUnit

FROM dbo.[Order Details] INNER JOIN

dbo.Products ON dbo.[Order Details].ProductID = dbo.Products.ProductID

6.2.2 Añadir las columnas adjuntadas con relaciones al DataGridViewLas columnas de la tabla Products se han de añadir manualmente pulsando con el botónderecho el Order_DetailsDataGridView y seleccionando Editar columnas para abrir el cua-dro de diálogo del mismo nombre. Pulse Añadir columnas y añada la columna Product-Name detrás de ProductID. Añada las columnas QuantityPerUnit y List Price. Defina elvalor True para la propiedad ReadOnly de las tres columnas y cambie el orden de lascolumnas por OrderID, Quantity, ProductID, ProductName, QuantityPerUnit, ListPrice,UnitPrice y Discount.

6.2.3 Proporcionar los valores por defecto y columnas de sólo lecturaPara navegar por la tabla de datos Products y proporcionar valores ProductName,QuantityPerUnit y UnitPrice y comprobar, opcionalmente el valor del campo Disconti-

210

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 210

Page 14: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

nued field, se necesita la ProductsBindingSource que añadió anteriormente en este capítu-lo. Defina el valor de la propiedad AllowNew de ProductsBindingSource como False yverifique DataSource que es NorthwindDataSet y DataMember es Products.

El siguiente manejador de eventos da intencionadamente valores por defecto que noson válidos y muestra un icono de error al añadir un nuevo ítem en Order Details.

Private Sub Order_DetailsDataGridView_DefaultValuesNeeded(ByVal sender As Object, _

ByVal e As System.Windows.Forms.DataGridViewRowEventArgs) _

Handles Order_DetailsDataGridView.DefaultValuesNeeded

'Set invalid default values

With e.Row

'Illegal Quantity

.Cells(1).Value = 0

'Illegal ProductID

.Cells(2).Value = 0

'ProductName

.Cells(3).Value = "ProductID not selected"

'Quantity per Unit

.Cells(4).Value = "Not applicable"

'ListPrice

.Cells(5).Value = 0D

'UnitPrice

.Cells(6).Value = 0D

'Discount

.Cells(7).Value = 0D

.ErrorText = "Default values: You must enter ProductID and Quantity."

End With

End Sub

El manejador del evento CellValueChanged muestra un icono de error para los valoresno válidos de ProductID, Quantity, o ambos, y los productos con discontinuidades:

Private Sub Order_DetailsDataGridView_CellValueChanged(ByVal sender As Object, _

ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) _

Handles Order_DetailsDataGridView.CellValueChanged

If blnIsLoaded AndAlso e.ColumnIndex = 2 Then

'User edited ProductID value

With Order_DetailsDataGridView

'Clear error icon

.Rows(e.RowIndex).ErrorText = ""

'Get the new ProductID value

Dim intProductID As Integer = _

CType(.Rows(e.RowIndex).Cells(2).Value, Integer)

Dim srtQuantity As Short = CType(.Rows(e.RowIndex).Cells(1).Value,Short)

If intProductID = 0 OrElse intProductID > ProductsBindingSource.Count

Then

'Bad ProductID value

211

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 211

Page 15: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

.Rows(e.RowIndex).ErrorText = "ProductID value must be between " + _

"1 and " + ProductsBindingSource.Count.ToString

Return

End If

'Get the required data from the ProductsBindingSource

Dim drvItem As DataRowView

drvItem = CType(ProductsBindingSource(intProductID - 1), DataRowView)

If CBool(drvItem.Item(9)) Then

'Discontinued products (5, 9, 17, 24, 28, 29, 42, 53)

.Rows(e.RowIndex).ErrorText = "ProductID " +

intProductID.ToString + _

" (" + drvItem.Item(1).ToString + ") is discontinued."

Else

'ProductName

.Rows(e.RowIndex).Cells(3).Value = drvItem.Item(1)

'Quantity per Unit

.Rows(e.RowIndex).Cells(4).Value = drvItem.Item(4)

'ListPrice

.Rows(e.RowIndex).Cells(5).Value = drvItem.Item(5)

'UnitPrice

.Rows(e.RowIndex).Cells(6).Value = drvItem.Item(5)

'Discount

.Rows(e.RowIndex).Cells(7).Value = 0D

If srtQuantity = 0 Then

.Rows(e.RowIndex).ErrorText = "Quantity of 0 is not permitted."

End If

End If

End With

End If

End Sub

La siguiente figura de la página siguiente muestra el formulario Joins.vb del proyectode ejemplo SelectCommandJoin.sln en el proceso de añadir un nuevo ítem de linea aOrder Details. En el apartado siguiente veremos la finalidad de los controles situadossobre el Orders DataGridView.

6.3 Mejorar el rendimiento reduciendo el tamaño de los juegos de datos

Cargar DataSets y poblar DataGridViews con registros innecesarios puede hacer bajarconsiderablemente el rendimiento de servidores y clientes, especialmente al reprodu-cir los DataSets perpetuados durante largo tiempo por los usuarios desconectados. Losapartados siguientes describen cómo reducir la carga del servidor y el consumo derecursos locales, y cómo mejorar la edición de datos limitando el número de filasdevueltas por las operaciones DataTableAdapter.Fill. Las consultas convencionales TOPn basadas en tipos descendientes de los valores de las columnas int identity y datetime,son útiles para la mayor parte de clientes, tanto conectados como desconectados. Las

212

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 212

Page 16: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

técnicas de paginación, además, minimizan el consumo de recursos y dan acceso a losusuarios conectados a los datos más antiguos.

6.3.1 Limitar el número de filas devueltas por las consultas TOP nEl método más obvio para limitar el número de records devueltos por las operacionesFill es añadir un modificador TOP n o TOP n PERCENT y una cláusula ORDER BYapropiada a la consulta SQL del TableAdapter para el SelectCommand. Por ejemplo, lasiguiente consulta SQL carga las 100 últimas filas de la tabla Orders para poblar elDataGridView del proyecto de ejemplo SelectCommandJoins.sln:

SELECT TOP 100OrderID, CustomerID, EmployeeID, OrderDate, RequiredDate,

ShippedDate, ShipVia, Freight, ShipName, ShipAddress, ShipCity, ShipRegion,

ShipPostalCode, ShipCountry

FROM dbo.Orders ORDER BY OrderID DESC

Cuando se aplican consultas TOP n a una tabla padre, se debería hacer lo mismo conlas operaciones TableAdapter.Fill en las tablas hijo. La consulta SelectCommand de OrderDetails, que veíamos en el apartado anterior, carga todas las filas extendidas de OrderDetails en el Order_DetailsDataTable, para lo cual se consumen muchos más recursos delo necesario. Para devolver sólo las filas hijo que dependen de las filas de Orders, hayque añadir un predicado IN con un subselect, también llamado subquery, tal como sedestaca en negrita en la consulta siguiente:

SELECT dbo.[Order Details].OrderID, dbo.[Order Details].ProductID,

dbo.[Order Details].UnitPrice, dbo.[Order Details].Quantity,

dbo.[Order Details].Discount, dbo.Products.ProductName,

213

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 213

Page 17: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

dbo.Products.QuantityPerUnit, dbo.Products.UnitPrice AS ListPrice

FROM dbo.[Order Details] INNER JOIN

dbo.Products ON dbo.[Order Details].ProductID = dbo.Products.ProductID

WHERE dbo.[Order Details].OrderID IN (SELECT TOP 100 dbo.Orders.OrderID

FROM dbo.Orders ORDER BY dbo.Orders.OrderID DESC)

SQL Server 2005 y SQL Express permiten sustituir variables bigint o float por consultas lite-rales TOP n [PERCENT]. El ejemplo de este capítulo utiliza valores literales para asegurar lacompatibilidad con SQL Server o MSDE 2000.

6.3.2 Añadir clases Partial para TableAdaptersLas clases TableAdapter no están anidadas en los DataSets de ADO.NET 2.0. En su lugar,los TableAdapters tienen su propio espacio de nombres para impedir que haya nombresde clase autogenerados por duplicado. Nombres de espacios de nombres autogenera-dos son, por ejemplo, DataSetNameTableAdapters, como NorthwindDataSetTableAdapters,que contiene PartialPublicClassOrdersTableAdapter, PublicClassOrder_DetailsTableAdaptery PublicClassProductsTableAdapter. Sustituir sentencias dinámicas SQL SELECT por elSelectCommand que se añadió en el diseñador de consultas, implica sobrecargar elmétodo Fill y dar el valor variable de la propiedad CommandText como segundo argu-mento. Si añade la signatura cargada a las clases parciales del DataSet perderá los datosañadidos cuando se regenere el Dataset. Por lo tanto, debe añadir un archivo de claseparcial al proyecto –en este ejemplo TableAdapters.vb– que contenga código similar alsiguiente:

Namespace NorthwindDataSetTableAdapters

'************************************

'Partial classes to set SelectCommand

'************************************

Partial Class OrdersTableAdapter

Public Overloads Function Fill(ByVal DataTable As

NorthwindDataSet.OrdersDataTable, ByVal strSelect As String) As Integer

Me.Adapter.SelectCommand = Me.CommandCollection(0)

'Replace the CommandText

Me.Adapter.SelectCommand.CommandText = strSelect

If (Me.ClearBeforeFill = True) Then

DataTable.Clear()

End If

Dim returnValue As Integer = Me.Adapter.Fill(DataTable)

Return returnValue

End Function

End Class

Partial Class Order_DetailsTableAdapter

Public Overloads Function Fill(ByVal DataTable As

NorthwindDataSet.Order_DetailsDataTable,

214

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 214

Page 18: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

ByVal strSelect As String) As Integer

Me.Adapter.SelectCommand = Me.CommandCollection(0)

'Replace the CommandText

Me.Adapter.SelectCommand.CommandText = strSelect

If (Me.ClearBeforeFill = True) Then

DataTable.Clear()

End If

Dim returnValue As Integer = Me.Adapter.Fill(DataTable)

Return returnValue

End Function

End Class

End Namespace

Seleccionando la casilla de verificación Limit Order Details Rows del proyecto y pulsan-do el botón Reload Data se añade el predicado subselect a Order_DetailsDataTable.Se-lectCommand. Probablemente no notará una diferencia notable en el tiempo de carga delos dos tipos de consulta, ya que el predicado IN aumenta el tiempo de ejecución de laconsulta. De todos modos, el predicado IN disminuye el tamaño del juego de datos per-petuado, bajando de los 824 KBytes de todas las filas de Orders a sólo 182 Kbytes para100 filas.

Pulsando el botón Save Data del Navegador de datos, los DataSet se guardan en un archi-vo AllDetails.xml si la casilla de verificación está deseleccionada, o en Subselect.xml encaso contrario.

6.4 Trabajar con imágenes en DataGridViewsLos DataGridViews requiren una columna DataGridViewImageColumn para mostrar imá-genes devueltas por las tablas que contienen gráficos almacenados como datos bina-rios, como las columnas image o varbinary del SQL Server. Las DataGridViewImageCo-lumns contienen una DataGridViewImageCell en cada fila. Por defecto, las celdas sinimágenes (valores nulos) muestran el gráfico de Internet Explorer con un vínculo HTMLa un archivo de imagen "missed". Las DataGridViewImageColumns comparten la mayoríade propiedades y métodos de otros tipos de datos, pero incorporan dos propiedades,Image e ImageLayout específicas de los gráficos. La propiedad Image permite especificaruna imagen por defecto del archivo MyResources.resx o cualquier otro archivo de recur-sos. La propiedad ImageLayout permite seleccionar un miembro de la enumeraciónDataGridViewImageCellLayout: NotSet, Normal, Stretch o Zoom. Estos miembros corres-ponden aproximadamente a la enumeración SizeMode del PictureBox. Como era deesperar, Normal es el valor por defecto que centra la imagen con su resolución original.

6.4.1 Añadir columnas Image a los DataGridViewsCuando se crea una fuente de datos de una tabla con una columna image o varbinary, laventana de Orígenes de datos muestra el nodo de la nueva columna desactivado. Si arras-tra el nodo de la tabla hasta el formulario para autogenerar un DataGridView, DataSet ocualquier otro componente de datos, el DataGridView no muestra ningunaDataGridViewImageColumn para el mapa de bits.

215

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 215

Page 19: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Para añadir la columna image que falta, pulse con el botón derecho el DataGridView yseleccione la opción Editar columnas para abrir el cuadro de diálogo del mismo nombre.Pulse el botón Agregar para abrir el cuadro de diálogo y, con el botón de opciónColumna de enlace de datos seleccionado, seleccione la columna y pulse Agregar (ver figu-ra siguiente). A continuación, especifique en Width un valor apropiado para el diseñodel DataGridView. Otra alternativa es seleccionar Rows como valor de la propiedadAutoSizeCriteria. Defina inicialmente AllCellsExceptHeaders como valor de la propiedadAutoSizeRowsMode del DataGridView. Después de un test inicial, puede darle a la pro-piedad RowTemplate.Height un valor que mantenga el ratio de imagen con el valor Widthde la columna.

La tabla ProductPhoto de la base de datos AdventureWorks de SQLServer 2005 proporcio-na la fuente de datos para el proyecto ejemplo de este apartado, DataGridViewIma-gesAW.sln. La tabla ProductPhoto tiene las columnas varbinary, ThumbNailPhoto y Large-Photo con 101 mapas de bits GIF; el tamaño de los mapas de bits LargePhoto para elDataGridView es de 240 por 149 píxeles. La siguiente figura muestra tres columnas delas dos primeras filas de la tabla en NormalImageLayout.

6.4.2 Manipular imágenes en DataGridViewEl código añadido a la clase ProductPhoto permite comprobar el efecto de los cambiosImageLayout en el aspecto las imágenes: guarde el contenido de un DataGridViewImage-Cell seleccionado en el correspondiente archivo LargePhotoFileName(.gif), muestre unaimagen en el cuadro de imagen (PictureBox) y sustituya la imagen seleccionada por unacopia del archivo que ha guardado.

6.4.3 Cambiar ImageLayoutPor defecto, el ancho de la columna LargePhoto y la altura de las filas se ajustan a ladimensión de las imagenes. Para comprobar los tres modos de imagen, arrastre elborde derecho de las cabeceras de columna hasta el borde derecho del DataGridView, y

216

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 216

Page 20: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

seleccione a continuación el botón Stretch para distorsionar la imagen cambiando elratio de proporción. Seleccionando Zoom, la propiedad AutoSizeRowsMode toma elvalor DataGridViewAutoSizeRowsMode.None, el cual permite manipular la altura de filay la anchura de la columna y ver los diferentes cambios de tamaño que se pueden apli-car a la imagen manteniendo siempre la proporción de aspecto habitual del mapa debits. Los siguientes manejadores responden al evento CheckChange de los botones deopción:

Private Sub rbNormal_CheckedChanged(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles rbNormal.CheckedChanged

'Normal layout

If blnLoaded And rbNormal.Checked Then

With ProductPhotoDataGridView

Dim colImage As DataGridViewImageColumn = _

CType(.Columns(2), DataGridViewImageColumn)

colImage.ImageLayout = DataGridViewImageCellLayout.Normal

.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.ColumnsAllRows

End With

End If

End Sub

Private Sub rbStretch_CheckedChanged(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles rbStretch.CheckedChanged

'Stretch layout

If blnLoaded And rbStretch.Checked Then

With ProductPhotoDataGridView

Dim colImage As DataGridViewImageColumn = _

217

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 217

Page 21: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

CType(.Columns(2), DataGridViewImageColumn)

colImage.ImageLayout = DataGridViewImageCellLayout.Stretch

.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.ColumnsAllRows

End With

End If

End Sub

Private Sub rbZoom_CheckedChanged(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles rbZoom.CheckedChanged

'Zoom layout

If blnLoaded And rbZoom.Checked Then

With ProductPhotoDataGridView

Dim colImage As DataGridViewImageColumn = _

CType(.Columns(2), DataGridViewImageColumn)

colImage.ImageLayout = DataGridViewImageCellLayout.Zoom

.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None

End With

End If

End Sub

6.4.4 Guardar una imagen seleccionada, mostrarla en un PictureBox y remplazarla

Manipular datos de imágenes en DataGridViews no es un proceso intuitivo. La propie-dad Value de un objeto DataGridViewImageCell se basa en el tipo de datos Byte(), no enel tipo Image que cabría esperar. Hay que incrustar Value en Byte y después crear unainstancia FileStream para guardar el array Byte en el correspondiente archivoLargePhotoFileName.gif. Crear una instancia MemoryStream para asignar la propiedadImage de PictureBox del formulario frmPictureBox es más eficaz que cargar el PictureBoxdesde el archivo guardado. Sustituir la imagen original por una copia del archivo sehace mediante el método File.ReadAllBytes para simplificar la lectura de un archivo detamaño desconocido. Estas operaciones vienen resaltadas en negrita en el procedimien-to siguiente (que es llamado por el manejador de evento bindingNavigatorSaveI-tem_Clickevent):

Private Sub SaveGifFile()

'Save the selected file

Dim strFile As String = Nothing

Try

With ProductPhotoDataGridView

If .CurrentCell.ColumnIndex = 2 Then

If Not frmPictureBox Is Nothing Then

frmPictureBox.Close()

End If

Dim strType As String = .CurrentCell.ValueType.ToString

'Create a Byte array from the value

Dim bytImage() As Byte = CType(.CurrentCell.Value, Byte())

218

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 218

Page 22: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

'Specify the image file name

Dim intRow As Integer = .CurrentCell.RowIndex

strFile = .Rows(intRow).Cells(1).Value.ToString

'Save the image as a GIF file

Dim fsImage As New FileStream("..\" + strFile, FileMode.Create)

fsImage.Write(bytImage, 0, bytImage.Length)

fsImage.Close()

'Create a MemoryStream and assign it as the image of a PictureBox

Dim msImage As New MemoryStream(bytImage)

frmPictureBox.pbBitmap.Image = Image.FromStream(msImage)

If frmPictureBox.ShowDialog = Windows.Forms.DialogResult.Yes Then

'Replace the CurrentCell's image from the saved version,

'if possible

If File.Exists(Application.StartupPath + "\" + strFile) Then

'The easy was to obtain a Byte array

Dim bytReplace() As Byte = File.ReadAllBytes(Appli-

cation.StartupPath + "\" + strFile)

.CurrentCell.Value = bytReplace

If AdventureWorksDataSet.HasChanges Then

AdventureWorksDataSet.AcceptChanges()

Dim strMsg As String = "File '" + strFile + _

" has replaced the image in row " +

intRow.ToString + _

" cell 2 (" + Format(bytReplace.Length,

"#,##0") + " bytes). " + _

vbCrLf + vbCrLf + "AcceptChanges has been

applied to the DataSet."

MsgBox(strMsg, MsgBoxStyle.Information, "Image

Replaced from File")

Else

Dim strMsg As String = "Unable to replace image

with file '" + _

strFile + "'. DataSet does not have changes."

MsgBox(strMsg, MsgBoxStyle.Exclamation, "Image

Not Replaced")

End If

End If

End If

Else

MsgBox("Please select the image to save.",

MsgBoxStyle.Exclamation, _

"No Image Selected")

End If

End With

Catch exc As Exception

219

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 219

Page 23: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

With ProductPhotoDataGridView

If strFile = Nothing Then

Dim intRow As Integer = .CurrentCell.RowIndex

strFile = .Rows(intRow).Cells(1).Value.ToString

End If

End With

Dim strExc As String = "File '" + strFile + "' threw the following "

+ _

"exception: " + exc.Message

MsgBox(strExc, MsgBoxStyle.Exclamation, "Exception with Image")

End Try

End Sub

El valor de transparencia RGB no corresponde al fondo blanco, por lo que la imagen selecciona-da muestra áreas sombreadas como transparentes.

6.4.5 Evitar crear imágenes desde los campos de objeto OLE en AccessLa base de datos Northwind de SQL Server 2000 contiene las tablas Categories y Employeesque se importaron de una versión anterior de Access. La columna Picture de la tablaCategories y la columna Photo de la tabla Employees tienen tipos de datos image, pero losbitmaps de formato BMP tienen un wrapper de objetos OLE. Las imágenes aparecen enDataGridView, pero el wrapper impide que se puedan mostrar en un PictureBox ni guar-dar el archivo en formato BMP.

6.5 Editar documentos XML con DataSets yDataGridViewsLa emergencia de los documentos XML como el nuevo formato de intercambio dedocumentos ha creado un requerimiento para las aplicaciones cliente que permiten alos usuarios revisar, editar y crear Infosets XML. Los documentos de negocios que uti-lizan Infosets para representar tablas de datos con una jerarquía de una o más relacio-nes uno-a-muchos (one-to-many), son habituales en la gestión de relación con el cliente(en inglés: customer relationship management, CRM), gestión de cadena de suministro(supply chain management, SCM), y otras aplicaciones de negocios como BizTalk Server2004. Estas aplicaciones intentan minimizar la intervención humana en sus procesosautomatizados de workflow, pero el procesamiento manual de documentos es inevita-ble en la mayor parte de las actividades de negocios.

Microsoft Word, Excel e InfoPath 2003, todos pueden editar documentos XML, pero losdocumentos jerárquicos con múltiples relaciones uno-a-muchos son difíciles de editaren Word o Excel. Access 2003 permite importar esquemas XML para crear tablas contipos de datos asignados, establecer claves y relaciones, adjuntar y editar datos y des-pués exportar las tablas, o una consulta a un archivo XML. De todos modos, un docu-mento XML jerárquico exportado no guarda ninguna relación con la estructura origi-nal del documento fuente. Transformar el archivo XML para regenerar la estructuradel documento original sería preocuparse más de lo necesario. InfoPath 2003 maneja laedición de documentos jerárquicos y mantiene la estructura del documento, pero sus

220

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 220

Page 24: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

formularios basados en HTML tienen un repertorio limitado de controles y, al igualque otros miembros de Office 2003, los usuarios de InfoPath 2003 necesitan licencia delcliente.

Los usuarios acostumbrados a editar tablas de bases de datos con formularios Windowscreados con alguna versión de Visual Studio, sin duda preferirán una UI similar o idén-tica para editar los Infosets XML tabulares, con controles DataGridView y, donde seanecesario, con cuadros de texto vinculados u otros controles de formulario Windows.Los controles DataGridView no se pueden vincular directamente a los documentosXML, sino que primero hay que generar un juego de datos desde el esquema del docu-mento. Si no tiene el esquema o no consigue generar el juego de datos, puede utilizarel editor XML de VS 2005 para inferir el esquema a partir de los contenidos del docu-mento.

6.5.1 Adaptar un esquema XML existente para generar un DataSetMicrosoft ha diseñado los DataSets para guardar en DataTables los datos relacionales; larepresentación XML de DataSets y DataTables está pensada básicamente como un meca-nismo para perpetuar o tratar datos a distancia. Por lo tanto, los documentos XML quesirven de fuente a los juegos de datos, deben tener un esquema adaptable a los juegos.A continuación indicamos los aspectos más importantes a tener en cuenta cuando seutilizan esquemas existentes para generar juegos de datos tipificados:

El diseñador de juegos de datos asigna el juego de datos el nombre del elemento denivel superior (raíz o documento). Si el esquema contiene una declaración global delespacio de nombres, se convierte en el espacio de nombres del juego de datos.

Los elementos subsiguientes con elementos hijos o los elementos hijo con atributosgeneran DataTables. Esta característica es propia de los documentos centrados en atri-butos, como los representantes XML de los Recordsets ADO, pero también puede hacerque se genere una tabla de datos para un atributo en lugar de una columna.

Los elementos hijo que representan las columnas de la tabla deben tener tipos sencillosXSD en correspondencia con los tipos de datos del sistema NET.

Los DataSets están centrados en el elemento; si en el esquema se especifican atributospara la tabla, el diseñador de juegos de datos añadirá los atributos como columnas detabla.

Los esquemas con grupos de elementos hijo anidados establecen automáticamenterelaciones one-to-many entre las tablas y añaden una clave primaria TableName_Id y unacolumna de clave foránea por cada relación con la tabla. La clave primaria TableName_Ides una columna Int32 AutoIncrement; leer un documento XML en el juego de datosgenera los valores de TableName_Id.

Si los grupos de elementos hijo no están anidados, hay que especificar la relación entrelas tablas en el editor de juegos de datos.

Si las tablas se han de cargar de documentos XML concretos y relacionados, en el es-quema no se debe especificar ninguna relación de tabla anidada.

221

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 221

Page 25: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

El diseñador de DataSets tiene problemas para importar esquemas secundarios quesoporten espacios de nombres múltiples y elementos calificados como espacios denombres. El diseñador de juegos de datos utiliza el XML Schema Definition Tool (Xsd.exe)para generar los juegos de datos tipificados. Xsd.exe no utiliza el atributo<xs:import>schemaLocation para cargar esquemas secundarios automáticamente.

Las restricciones anteriores hacen difícil, si no imposible, generar juegos de datos tipi-ficados desde esquemas XML complejos, para documentos de negocios estándar, comoUniversal Business Language (UBL) 1.0 o Human Resources XML (HR-XML). Los esque-mas UBL 1.0 utilizan ampliamente las directrices <xs:import> y especifican tipos com-plejos para elementos que representan las columnas de las tablas.

La mayoría de las aplicaciones de edición XML deben producir un documento de sali-da con la misma estructura que el documento fuente, lo que significa que la ediciónsólo debe afectar a los contenidos de los elementos. La estructura tabular de los juegosde datos permite exportar todo el contenido o las filas seleccionadas de tablas concre-tas a los streams o archivos XML. También se pueden generar juegos de datos desdedocumentos fuente relacionados con estructuras definidas en un único esquema.

Si la aplicación debe reestructurar el documento de salida, se puede aplicar un XSLTtransform para la versión final del documento editado. Otra alternativa es sincronizar eljuego de datos con una instancia XmlDataDocument y aplicar el transform a la instancia.

6.5.2 Esquemas para documentos XML de jerarquía anidadaLa estructura ideal de un documento fuente de un juego de datos es un Infoset XML conuna jerarquía anidada de elementos relacionados. El diseñador de juegos de datosgenera DataSets automáticamente desde esquemas compatibles con los documentosanidados. El siguiente documento XML, abreviado, es un ejemplo típico de archivoXML generado al serializar un juego de objetos relacionados con los negocios en unajerarquía de tres niveles:

<rootElement>

<parentGroup>

<parentField1>String</parentField1>

...

<parentFieldN>1000</parentFieldN>

<childGroup>

<childField1>String</childField1>

...

<childFieldN>15.50</childFieldN>

<grandchildGroup>

<grandchildField1>String</grandchildField1>

...

<grandchildFieldN>15</grandchildFieldN>

</grandchildGroup>

</childGroup>

</parentGroup>

</rootElement>

222

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 222

Page 26: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

A continuación vemos el esquema general del documento anterior, con un elemento

raíz <xs:complexType> y sus <xs:complexType> que contienen a su vez un grupo de

elementos de campo <xs:sequence> y otros <xs:complexType> descendientes anidados:

<?xml version= 1.0 encoding= utf-8 ?>

<xs:schema attributeFormDefault= unqualified elementFormDefault= qualified

xmlns:xs= http://www.w3.org/2001/XMLSchema >

<xs:element name= rootElement >

<xs:complexType>

<xs:sequence>

<xs:element maxOccurs= unbounded name= parentGroup >

<xs:complexType>

<xs:sequence>

<xs:element name= parentField1 type= xs:string />

...

<xs:element name= parentFieldN type= xs:int />

<xs:element maxOccurs= unbounded name= childGroup >

<xs:complexType>

<xs:sequence>

<xs:element name= childField1 type= xs:string />

...

<xs:element name= childFieldN type= xs:decimal />

<xs:element maxOccurs= unbounded name= grandChildGroup >

<xs:complexType>

<xs:sequence>

<xs:element name= grandChildField1 type= xs:string />

...

<xs:element name= grandChildFieldN type= xs:short />

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:schema>

El diseñador de DataSets interpreta los grupos <xs:complexType> no raíz que tienen ele-mentos de campo, los elementos anidados <xsd:complexType>, o ambos, como tablas dedatos. Por eso, los elementos de campo deben tener tipos de datos sencillos comoxs:string, xs:int o xs:decimal, o grupos <xs:complexType> que representan tablas relacio-nadas.

223

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 223

Page 27: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Un documento fuente XML que especifica un atributo de espacio de nombres pordefecto con <rootElementxmlns= documentNamespace> requiere un esquema que incluyaun atributo targetNamespace="documentNamespace" para el elemento <xs:schema> másalto de la jerarquía. Si su esquema tiene una estructura tan básica como la del ejemploprecedente y sólo tiene un targetNamespace o ningún espacio de nombres de documen-to, está de suerte. Haga los cambios que se destacan en negrita a continuación en losdos primeros elementos del esquema para indicar que el esquema representa un juegode datos tipificado:

<xs:schema attributeFormDefault= unqualified elementFormDefault= qualified

xmlns:xs= http://www.w3.org/2001/XMLSchema

xmlns:msdata= urn:schemas-microsoft-com:xml-msdata >

<xs:element name= rootElement msdata:IsDataSet= true >

Copie el archivo Schema.xsd en la carpeta del proyecto, pulse con el botón derecho elicono del archivo en el Explorador de proyectos y seleccione Añadir a proyecto, lo que gene-rará archivos Schema.Designer.vb, Schema.xsc, y Schema.xss. Realice una doble pulsaciónsobre Schema.xsd para abrirlo en el Editor DataSet y mostrar la ventana Orígenes de datos.Puede añadir el juego de datos a la bandeja del diseñador arrastrando la herramientaDataSetName desde la sección de componentes ProjectName hasta el formulario, o selec-cionando la herramienta DataSet desde la sección Data y seleccionandoProjectName.DataSet en la lista de juegos de datos tipificados (Typed DataSet list).

En este punto, ya puede arrastrar la tabla parentGroup desde la ventana de fuentes dedatos para añadir un BindingNavigator y cuadros de texto o un DataGridView para edi-tar parentGroup, y después añadir DataGridViews para las tablas childGroup y grand-childGroup.

6.5.3 Un ejemplo de esquema anidadoLa siguiente figura muestra un juego de datos tipificado generado desde un esquema(NorthwindDS.xsd) para un documento XML anidado (NorthwindDS.xml) que contieneun pequeño subjuego de datos de las tablas Customers, Orders y Order Details deNorthwind.

Al generar el juego de datos, la columna Customers_Id de clave primaria se añade a latabla Customers y la correspondiente columna de clave foránea Customers_Id se añade ala tabla Orders para crear la relación Customers_Orders. La tabla Orders gana una claveprimaria Orders_Id para la relación Orders_Order_Details con la clave foránea Orders_Idde la tabla Order_Details. A continuación vemos el esquema NorthwindDS.xsd para eldocumento anidado:

<?xml version= 1.0 encoding= utf-8 ?>

<xs:schema id= Northwind xmlns= xmlns:xs= http://www.w3.org/2001/XMLSchema

xmlns:msdata= urn:schemas-microsoft-com:xml-msdata >

<xs:element name= Northwind msdata:IsDataSet= true >

<xs:complexType>

<xs:choice minOccurs= 0 maxOccurs= unbounded >

224

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 224

Page 28: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

<xs:element name= Customers >

<xs:complexType>

<xs:sequence>

<xs:element name= CustomerID type= xs:string />

<xs:element name= CompanyName type= xs:string />

<xs:element name= ContactName type= xs:string minOccurs= 0 />

<xs:element name= ContactTitle type= xs:string minOccurs= 0 />

<xs:element name= Address type= xs:string />

<xs:element name= City type= xs:string />

<xs:element name= Region type= xs:string minOccurs= 0 />

<xs:element name= PostalCode type= xs:string minOccurs= 0 />

<xs:element name= Country type= xs:string />

<xs:element name= Phone type= xs:string />

<xs:element name= Fax type= xs:string minOccurs= 0 />

<xs:element name= Orders minOccurs= 0 maxOccurs= unbounded >

<xs:complexType>

<xs:sequence>

<xs:element name= OrderID type= xs:int />

<xs:element name= CustomerID type= xs:string />

<xs:element name= EmployeeID type= xs:int />

<xs:element name= OrderDate type= xs:dateTime />

225

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 225

Page 29: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

<xs:element name= RequiredDate type= xs:dateTime

minOccurs= 0 />

<xs:element name= ShippedDate type= xs:dateTime

minOccurs= 0 />

<xs:element name= ShipVia type= xs:int />

<xs:element name= Freight type= xs:decimal minOccurs= 0 />

<xs:element name= ShipName type= xs:string />

<xs:element name= ShipAddress type= xs:string />

<xs:element name= ShipCity type= xs:string />

<xs:element name= ShipRegion type= xs:string minOccurs= 0 />

<xs:element name= ShipPostalCode type= xs:string

minOccurs= 0 />

<xs:element name= ShipCountry type= xs:string />

<xs:element name= Order_Details minOccurs= 0

maxOccurs= unbounded >

<xs:complexType>

<xs:sequence>

<xs:element name= OrderID type= xs:int />

<xs:element name= ProductID type= xs:int />

<xs:element name= UnitPrice type= xs:decimal />

<xs:element name= Quantity type= xs:short />

<xs:element name= Discount type= xs:decimal />

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:choice>

</xs:complexType>

</xs:element>

</xs:schema>

Nótese que el esquema NorthwindDS.xsd no contiene referencias a las columnas añadi-das de clave primaria y clave foránea. Generar un juego de datos desde un esquema dedocumento fuente anidado no modifica el esquema En el archivo NorthwindDS.De-signer.vb, el método Northwind.InitClass añade esas DataColumns a las DataTables al espe-cificar los ForeignKeyConstraints, y después añade las DataRelations con la propiedadNested definida como True.

226

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 226

Page 30: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

6.5.4 La ventana Propiedades de las columnasPara examinar las propiedades de las columnas añadidas, seleccione la columna ypulse con el botón secundario del ratón para mostrar la ventana Propiedades. La siguien-te figura muestra la ventana Propiedades de la columna de clave primaria Orders_Id(izquierda) de la tabla Orders, y la columna Orders_Id de clave foránea de la tablaOrder_Details (derecha).

En la ventana Propiedades puede editar el tipo de datos, el nombre de la columna y otraspropiedades de cualquiera de las columnas de la tabla. Pulse la ventana con el botónderecho y seleccione Añadir para añadir una nueva columna a la tabla de datos. A modode ejemplo, puede añadir una columna Extended a la tabla Order_Details que puede cal-cular con la fórmula Quantity*UnitPrice*(1 Discount).

Cualquier cambio en alguno de los valores de la ventana Propiedades provoca un cam-bio importante en el archivo de esquema: al archivo se le añade un grupo <xs:annota-tion> para especificar la fuente de datos, la mayoría de los elementos adquieren unagran cantidad de atributos msprop y el tamaño del archivo aumenta considerablemnte.NorthwindDS.xsd, por ejemplo, pasa de 4 KBytes a 35 KBytes. Por lo tanto, si tiene queeditar el esquema y conservar la estructura original, pulse el archivo con el botón se-cundario, en el Explorador de soluciones, seleccione Abrir con… y, en el cuadro de diálo-go que se abre con el mismo nombre, seleccione XMLEditor. No seleccione DataSetEditor, que es la opción por defecto, ni tampoco XML Schema Editor.

6.5.5 Un esquema anidado con atributosAl añadir atributos a los elementos que generan tablas de datos se añade a la tabla unacolumna del mismo nombre que el atributo. Por ejemplo, un atributo del campo Or-der_Details definido por <xs:attributename= "totalAmount" type= "xs:decimal" use= "requi-

227

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 227

Page 31: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

red" /> añade una columna totalAmount a la tabla Order_Details. La siguiente figuramuestra el esquema NWAttributes.xsd abierto en el Editor DataSet. La primera columnade cada tabla viene generada por un atributo definido en el equema e incluido en eldocumento fuente NWAttributes.xsd source document. Cuando se añade un atributo auna tabla, se añade también un atributo msdata:Ordinal="n" , en orden consecutivo, acada nodo hijo que representa una columna de la tabla.

Si se añade un atributo obligatorio a un elemento hijo, como por ejemplo ProductID, eldiseñador crea una tabla ProductID, y probablemente no es eso lo que usted desea.

6.5.6 Ejemplo de esquema anidado y "envuelto" (wrapped)Con los documentos XML es una práctica común diseñar juegos de elementos "envuel-tos" en otros grupos. Un ejemplo es envolver Customer y sus hijos en un grupoCustomers, Order en un grupo Orders y Order_Detail en un grupo Order_Details paracrear la estructura abreviada que vemos a continuación:

<Customers>

<Customer>

<CustomerID>GREAL</CustomerID>

...

<Fax></Fax>

<Orders>

<Order>

228

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 228

Page 32: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

<OrderID>11061</OrderID>

...

<ShipCountry>USA</ShipCountry>

<Order_Details>

<Order_Detail>

<OrderID>11061</OrderID>

...

<Discount>0.075</Discount>

</Order_Detail>

</Order_Details>

</Order>

</Orders>

</Customer>

<Customers>

A continuacion vemos el esquema abreviado del documento fuente anterior con los elementos envol-ventes destacados en negrita:<?xml version= 1.0 encoding= utf-8 ?>

<xs:schema id= Customers xmlns= xmlns:xs= http://www.w3.org/2001/XMLSchema

xmlns:msdata= urn:schemas-microsoft-com:xml-msdata >

<xs:element name= Customers msdata:IsDataSet= true >

<xs:complexType>

<xs:choice minOccurs= 0 maxOccurs= unbounded >

<xs:element name= Customer >

<xs:complexType>

<xs:sequence>

<xs:element name= CustomerID type= xs:string minOccurs= 0 />

...

<xs:element name= Fax type= xs:string minOccurs= 0 />

<xs:element name= Orders minOccurs= 0 />

<xs:complexType>

<xs:sequence>

<xs:element name= Order minOccurs= 0 maxOccurs= unbounded >

<xs:complexType>

<xs:sequence>

<xs:element name= OrderID type= xs:string

minOccurs= 0 />

...

<xs:element name= ShipCountry type= xs:string

minOccurs= 0 />

<xs:element name= Order_Details minOccurs= 0 />

<xs:complexType>

<xs:sequence>

<xs:element name= Order_Detail

minOccurs= 0 maxOccurs= unbounded >

<xs:complexType>

<xs:sequence>

<xs:element name= OrderID type= xs:string

229

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 229

Page 33: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

minOccurs= 0 />

...

<xs:element name= Discount type= xs:string

minOccurs= 0 />

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:choice>

</xs:complexType>

</xs:element>

</xs:schema>

El esquema CustomersDS.xsd genera dos tablas adicionales para establecer las relacio-nes entre los elementos Orders y Order, y Order_Details y Order_Detail. Para que elDataSet se pueda editar en DataGridViews hay que añadir relaciones entre los camposCustomersID de las tablas Customers y Orders, y los campos OrderID de las tablas Ordersy Order_Details, tal como se describe más adelante en este capítulo.

6.5.7 Un ejemplo de esquema planoLos esquemas anidados pueden exportar tablas como si fueran documentos XML invo-cando el método DataTable.WriteXML(ExportFileName,XmlWriteMode.IgnoreSchema). Losesquemas planos añaden la capacidad de importar documentos XML concretos, quecomplen el esquema de DataSet para tablas relacionadas. No obstante, el diseñador dejuegos de datos no añade columnas TableName_Id, ForeignKeyConstraints niDataRelations.

A continuacion, el esquema abreviado de Northwind.xsd para Northwind.xml, que es laversión plana de NorthwindDS.xml, con las claves primaria y foránea destacadas ennegrita:

<?xml version= 1.0 encoding= utf-8 ?>

<xs:schema id= Northwind attributeFormDefault= unqualified

elementFormDefault= qualified xmlns:xs= http://www.w3.org/2001/XMLSchema

xmlns:msdata= urn:schemas-microsoft-com:xml-msdata >

<xs:element name= Northwind msdata:IsDataSet= true >

230

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 230

Page 34: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

<xs:complexType>

<xs:sequence>

<xs:element maxOccurs= unbounded name= Customers >

<xs:complexType>

<xs:sequence>

<xs:element name= CustomerID type= xs:string />

...

<xs:element minOccurs= 0 name= Fax type= xs:string />

</xs:sequence>

</xs:complexType>

</xs:element>

<xs:element minOccurs= 0 maxOccurs= unbounded name= Orders >

<xs:complexType>

<xs:sequence>

<xs:element name= OrderID type= xs:int />

<xs:element name= CustomerID type= xs:string />

...

<xs:element name= ShipCountry type= xs:string />

</xs:sequence>

</xs:complexType>

</xs:element>

<xs:element minOccurs= 0 maxOccurs= unbounded name= Order_Details >

<xs:complexType>

<xs:sequence>

<xs:element name= OrderID type= xs:int />

<xs:element name= ProductID type= xs:int />

...

<xs:element name= Discount type= xs:decimal />

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:schema>

Para crear una versión editable de Northwind.xsd hay que seguir los siguientes pasos enla ventana del DataSet Editor:

Añadir claves primarias a cada tabla de datos. Seleccionar y pulsar con el botón dere-cho la columna de clave primera y seleccionar a continuación Establecer clave principalpara las tres tablas. Opcionalmente, seleccione Editar clave para abrir el cuadro de diál-go Restricción UNIQUE y cambiar el nombre por PK_TableName o algo similar.

La tabla Order_Details tiene una clave primaria compuesta, por lo tanto pulse con elbotón derecho la columna OrderID, seleccione Editar clave y marque la casilla de verifi-cación ProductID.

231

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 231

Page 35: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Pulse con el botón derecho el entorno del DataSet Editor y seleccione Agregar/Relationpara abrir el cuadro de diálogo Relación con los valores por defecto para una relaciónentre Customers y Orders, que tendrá el nombre FK_Customers_Orders. En la listaColumnas de clave externa, cambie la entrada OrderID de la lista Columnas de clave exter-na por CustomerID.

Seleccione de nuevo Agregar/Relation, cambie el nombre actual de la relación,FK_Customers_Orders1 por a FK_Orders_Order_Details, y seleccione Orders en la lista dela tabla padre y Order_Details en la lista de la tabla hijo. Las listas Columnas de clave yColumnas de clave externa muestran el OrderID.

Si quiere que los usuarios de la aplicación puedan añadir nuevos records a Orders yOrder_Details, seleccione la columna OrderID de clave primaria, seleccione Propiedadesy cambie el valor de la propiedad AutoIncrement de False a True.

La siguiente figura muestra el editor de juegos de datos con los pasos anteriores com-pletados.

Al añadir las claves primarias y las relaciones a las tablas, al final del esquema se aña-den los siguientes elementos <xs:unique> y <xs:keyref> del elemento Northwind:

<xs:schema id= Northwind xmlns= xmlns:xs= http://www.w3.org/2001/XMLSchema

xmlns:msdata= urn:schemas-microsoft-com:xml-msdata

xmlns:msprop= urn:schemas-microsoft-com:xml-msprop >

<xs:element name= Northwind msdata:IsDataSet= true

232

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 232

Page 36: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

msprop:User_DataSetName= Northwind

msprop:DSGenerator_DataSetName= Northwind >

...

<xs:unique name= PK_Customers msdata:PrimaryKey= true >

<xs:selector xpath= .//Customers />

<xs:field xpath= CustomerID />

</xs:unique>

<xs:unique name= PK_Orders msdata:PrimaryKey= true >

<xs:selector xpath= .//Orders />

<xs:field xpath= OrderID />

</xs:unique>

<xs:unique name= PK_Order_Details msdata:PrimaryKey= true >

<xs:selector xpath= .//Order_Details />

<xs:field xpath= OrderID />

<xs:field xpath= ProductID />

</xs:unique>

<xs:keyref name= FK_Orders_Order_Details refer= PK_Orders

msprop:rel_Generator_RelationVarName= relationFK_Orders_Order_Details

msprop:rel_User_ParentTable= Orders

msprop:rel_User_ChildTable= Order_Details

msprop:rel_User_RelationName= FK_Orders_Order_Details

msprop:rel_Generator_ParentPropName= OrdersRow

msprop:rel_Generator_ChildPropName= GetOrder_DetailsRows >

<xs:selector xpath= .//Order_Details />

<xs:field xpath= OrderID />

</xs:keyref>

<xs:keyref name= FK_Customers_Orders refer= PK_Customers

msprop:rel_Generator_RelationVarName= relationFK_Customers_Orders

msprop:rel_User_ParentTable= Customers msprop:rel_User_ChildTable= Orders

msprop:rel_User_RelationName= FK_Customers_Orders

msprop:rel_Generator_ParentPropName= CustomersRow

msprop:rel_Generator_ChildPropName= GetOrdersRows >

<xs:selector xpath= .//Orders />

<xs:field xpath= CustomerID />

</xs:keyref>

</xs:element>

</xs:schema>

Los elementos <xs:unique> definen claves primarias, y los elementos <xs:keyref> especi-fican las restricciones de clave foránea. Los atributos msprop son referencias a las rela-ciones entre datos (DataRelations) añadidas por la clase parcial Northwind del archivoNorthwind.Designer.vb.

6.5.8 Inferir un esquema XML para generar un juego de datosSi todavía no tiene ningún esquema para su documento fuente XML, puede elegir entrelas cinco opciones siguientes para generar el esquema con VS 2005:

233

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 233

Page 37: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Abra un documento fuente XML representativo en el editor de XML, seleccioneXML/CreateSchema para inferir un esquema, y guárdelo en la carpeta del proyecto conel nombre SchemaName.xsd. El generador de esquemas del editor intentará inferir tiposde datos XSD examinando los valores de texto en los campos del documento fuente.Desafortunadamente, el proceso de inferencia no suele tener éxito con valores numéri-cos unsigned que no tienen valores decimales; les asigna tipos de datos XSD numéricos,con los valores más pequeños posibles. Por ejemplo, calcular 0 dividido entre 255 seconvierte en xs:unsignedByte, 256 entre 65.535 se convierte en xs:unsignedShort, y losnúmeros con muchas cifras se convierten en xs:unsignedInt o xs:unsignedLong. A menosque tenga alguna razón para obrar de otra manera, asigne xs:int a todos los valores sinfracciones decimales.

Cree un juego de datos vacío en tiempo de ejecución, invoque el método Data-Set.ReadXml(DocumentFileName) y guarde el archivo del esquema invocando el métodoDataSet.WriteXmlSchema(SchemaFileName). Este último método genera un esquema notipificado en el que todos los elementos tienen asignado el tipo de datos xs:string y unatributo minOccurs="0". Abra SchemaFileName.xsd en el editor XML, cambie los tipos dedatos de los valores numéricos o de fecha/tiempo por el tipo apropiado xs:datatype, yelimine todos los atributos minOccurs="0" que no resulten apropiados.

Genere un esquema tipificado con el proceso anterior, pero invoque el método Da-taSet.ReadXml(DocumentFileName,XmlReadMode.InferTypedSchema) para generar unesquema idéntico al generado por el editor XML.

Abra un VS 2005 Command Prompt, navegue hasta la carpeta del proyecto y escribaxsd.exe DocumentFileName.xml para generar DocumentFileName.xsd. El esquema es idén-tico al generado por el método precedente.

Si no dispone de ningún documento XML representativo de todas las instancias posi-bles de documento XML, o si no quiere crear uno manualmente, puede usar la herra-mienta Microsoft XSD Inference 1.0, que encontrará en http://apps.gotdotnet.com/-xmltools/xsdinference/ para generar y refinar un esquema tipificado. Debe especificaruna fuente inicial para inferir el esquema inicial y después procesar los documentosfuente adicionales para refinar el esquema.

Si tiene que inferir y refinar esquemas de forma rutinaria, puede utilizar el métodoSystem.Xml.Schema.InferSchema para simular la herramienta de Microsoft, XSD Inference1.0 Tool. El siguiente código infiere un esquema para una instancia de documento ini-cial (Initial.xml), refina el esquema con tres instancias de documentos adicionales yescribe el esquema refinado como Initial.xsd:

Private Sub InferAndRefineSchema()

Dim alFiles As New ArrayList

alFiles.Add(Initial.xml)

alFiles.Add(Refine2.xml)

alFiles.Add(Refine3.xml)

alFiles.Add(Refine4.xml)

Dim intCtr As Integer

Dim xss As XmlSchemaSet = Nothing

234

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 234

Page 38: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Dim xsi As Inference = Nothing

For intCtr = 0 To alFiles.Count - 1

Dim xr As XmlReader = XmlReader.Create(alFiles(intCtr).ToString)

If intCtr = 0 Then

Infer(schema)

xss = New XmlSchemaSet()

xsi = New Inference()

End If

xss = xsi.InferSchema(xr)

xr.Close()

Next

Dim strXsdFile As String = Replace(alFiles(0).ToString, .xml, .xsd)

Dim xsd As XmlSchema

For Each xsd In xss.Schemas()

Dim sw As StreamWriter = Nothing

sw = My.Computer.FileSystem.OpenTextFileWriter(strXsdFile, False)

xsd.Write(sw)

sw.Close()

Exit For

Next

End Sub

6.5.9 Crear formularios de edición desde fuentes de datos XMLEl proceso de crear formularios de edición para documentos XML es parecido al de edi-tar tablas de bases de datos. Después de generar un juego de datos tipificado a partirdel esquema existente, arrastre la tabla de más arriba desde la ventana Orígenes de datoshasta el formulario donde quiere añadir un control DataNavigator y DataGridView o cua-dros de texto para detalles. Repita el mismo proceso con los DataGridViews para lastablas relacionadas y especifique la DataRelation apropiada para generar unaDataRelationBindingSource para el valor de la propiedad DataSource. A diferencia de losDataGridViews vinculados a FK_ParentTable_ChildTableBindingSources generados portablas de bases de datos, la BindingSource se crea cuando, en la lista desplegable de lapropiedad DataSource, se especifica una lista relacionada.

Los dos ejemplos siguientes de proyectos ilustran los cambios necesarios para crearDataRelationBindingSource, permitir la adición de nuevos elementos en el documento yacomodar juegos de datos envueltos y anidados.

6.5.10 El proyecto de ejemplo EditNorthwindDSEl proyecto EditNorthwindDS.sln está basado en el documento fuente NorthwindDS.xmly en el esquema NorthwindDS.xsd. El formulario tiene DataGridViews poblados condatos de las tablas Customers, Orders y Order_Details, tal como muestra la siguientefigura.

Abra la ventana Orígenes de datos y arrastre el icono del grupo padre de Customers, elicono de su subgrupo Orders y el icono del subgrupo Order Details del grupo Orders

235

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 235

Page 39: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

hasta el formulario para añadir los tres DataGridViews. Añada código al manejador deevento Form_Load para poblar el juego de datos con el documento NorthwindDS.xml.

La siguiente figura muestra la lista Orígenes de datos tras realizar las operaciones ante-riores y cargar el documento NorthwindDS.xml. La instrucción OrdersDataGridView..-Sort(.Columns(0),System. ComponentModel.ListSortDirection.Descending) del manejadorde eventos clasifica los OrderID por orden descendente. Si quiere que los usuarios pue-dan añadir nuevos registros a Orders y Order_Details con los valores apropiados de lacolumna OrderID, deberá editar el esquema y darle a la propiedad AutoIncrement de lascolumnas OrderID y Order_Id el valor True en el cuadro de diálogo Propiedades deColumnName. En caso contrario, defina el valor False para la propiedad AllowUserTo-AddRows de DataGridViews.

Puede añadir los atributos autogenerados Customers_Id, Orders_Id y Order_Details_Idcomo columnas de los DataGridViews. Mientras personaliza la colección Columns de losDataGridViews en el cuadro de diálogo Editar columnas, lleve las columnas autogenera-das al final de la lista SelectedColumns y defina el valor True para sus propiedadesReadOnly. Si no quiere que los usuarios puedan añadir nuevas filas, borre estas colum-nas de los DataGridView. Añada un botón para guardar los cambios e invoque el méto-do NorthwindDS..WriteXml(strFile,Data.XmlWriteMode.IgnoreSchema) para guardar eldocumento editado con los datos. El proyecto de ejemplo guarda un archivo diffgram(NorthwindDS.xsd) antes de guardar los camibos y tiene botones para mostrar enInternet Explorer el esquema y el documento XML guardado.

236

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 236

Page 40: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Para añadir nuevas filas se necesita un procedimiento OrdersDefaultValues que llama almanejador de evento OrdersDataGridView_DefaultValuesNeeded. El código del procedi-miento es similar al que vimos en el capítulo anterior para el manejador de eventoDefaultValuesNeeded, pero ahora hay que añadir el valor Customers_Id para mantener larelación, tal como se destaca en negrita en el siguiente listado:

Private Sub OrdersDefaultValues(ByVal rowNew As DataGridViewRow)

Try

With CustomersDataGridView

Dim intRow As Integer = .CurrentCell.RowIndex

rowNew.Cells(1).Value = .Rows(intRow).Cells(0).Value

rowNew.Cells(2).Value = 0

rowNew.Cells(3).Value = Today

rowNew.Cells(4).Value = Today.AddDays(14)

'Leave ShippedDate empty

rowNew.Cells(6).Value = 3

'Freight defaults to 0

'CompanyName

rowNew.Cells(8).Value = .Rows(intRow).Cells(1).Value

'Address to Country fields

Dim intCol As Integer

For intCol = 9 To 13

rowNew.Cells(intCol).Value = .Rows(intRow).Cells(intCol -

5).Value

237

La aplicación de técnicas avanzadas de los DataSets

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 237

Page 41: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Next

'Add the current Customers_Id value

rowNew.Cells(15).Value = .Rows(intRow).Cells(11).Value

OrdersDataGridView.EndEdit(DataGridViewDataErrorContexts.Commit)

'Store the autoincremented Orders_Id for Order_Details default values

intNewOrder_ID = CInt(rowNew.Cells(14).Value)

'Store the autoincremented OrderID value

intOrderID = CInt(rowNew.Cells(0).Value)

End With

Catch exc As Exception

MsgBox(exc.Message + exc.StackTrace, , )

End Try

End Sub

El procedimiento DetailsDefaultValues requiere una modificación similar para losvalores de OrdersID y Orders_Id:

Private Sub DetailsDefaultValues(ByVal rowNew As DataGridViewRow)

'Default values for Order_Details

Try

With OrdersDataGridView

Dim intRow As Integer = .CurrentCell.RowIndex

'Add OrderID

rowNew.Cells(0).Value = .Rows(intRow).Cells(0).Value

'Add Orders_Id

rowNew.Cells(5).Value = .Rows(intRow).Cells(14).Value

End With

With Order_DetailsDataGridView

rowNew.Cells(1).Value = 0

rowNew.Cells(2).Value = .Rows.Count * 10

rowNew.Cells(3).Value = .Rows.Count * 5

rowNew.Cells(4).Value = .Rows.Count * 0.01

End With

Catch exc As Exception

rowNew.Cells(5).Value = intNewOrder_ID

Finally

Order_DetailsDataGridView.EndEdit(DataGridViewDataErrorContexts.Commit)

End Try

End Sub

238

Bases de datos con Visual Basic

VisualBasic2005_06.qxp 02/08/2007 16:26 PÆgina 238

Page 42: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

239

Capítulo 7

Trabajar con las fuentes de datos y controles vinculados de

ASP.NET 2.0

Los formularios Windows, sus fuentes de datos, componentes y controles vinculados dela versión .NET Framework 2.0 son un desarrollo de la versión anterior .NET Framework1.0. El ayudante y las herramientas de Visual Studio 2005 simplifican las tareas máscomunes, como generar juegos de datos tipificados y diseñar formularios maestros y dedetalle, pero las herramientas y el ayudante se parecen mucho a sus predecesores. Latransición desde las herramientas y componentes de Visual Studio implica una modes-ta curva de aprendizaje para los desarrolladores .NET con más experiencia. Sustituir losobsoletos DataGrid por los nuevos DataGridWiews exige algo más de esfuerzo, pero laspropiedades y el rendimiento mejorado de estos elementos justifica la complejidad desu modelo de objeto.

Por otra parte, ASP.NET 2.0 representa una diferencia radical respecto a ASP.NET 1.x.La herramienta de libre desarrollo Web Matrix ASP.NET, de Microsoft, fue un éxito ins-tantáneo y una contribución remarcable a su populardidad fue que no requería ningúnprerrequisito para VS 2002 o 2003 ni los Internet Information Services (IIS). Web Matrixcombina un diseñador gráfico de páginas Web y un editor de código (su nombre codi-ficado es Venus) para ASP.NET 1.1 con un servidor Web ligero (Cassini). Venus y Cassiniconstituyen los fundamentos de Visual Web Developer UI y el servidor Visual WebDeveloper de VS 2005. La edición Express 2005 de Visual Web Developer (VWD) es el equi-valente a la actualización de Web Matrix para VS 2005 UI y ASP.NET 2.0. A diferenciade las ediciones Express para un lenguaje de programación específico, la VWD 2005Express soporta VB, C#, y J#.

Este capítulo presupone que el lector ya tiene cierta experiencia en la creación y desarrollo desitios Web controlados por datos con Active Server Pages (ASP), ASP.NET 1.x o Web Matrix.

Las cadenas de conexión de los proyectos de ejemplo presuponen que se trabaja con SQLServer2000, MSDE 2000 o SQLServer 2005, como instancia localhost por defecto y la base de datosNorthwind.

VisualBasic2005_07.qxp 02/08/2007 16:28 PÆgina 239

Page 43: VisualBasic2005 05.qxp 02/08/2007 18:27 PÆgina 197 …galeon.com/melissacalleja1/basesdatos6.pdf · ADO.NET 2.0 y las herramientas de Visual Studio 2005. Los dos capítulos anteriores

Si está utilizando Visual Web Developer 2005 Express Edition o una instancia de nombreSQLServer 2005, debe modificar la siguiente sección del archivo Web.config para señalarla instancia nombrada:

<connectionStrings>

<add name= NorthwindConnection connectionString= Server=localhost;Integrated

Security=True;Database=Northwind providerName= System.Data.SqlClient />

</connectionStrings>

Cambie localhost por .\SQLExpress para usar el proveedor Shared Memory con SQLServer 2005 Express.

7.1 Las nuevas características de ASP.NET 2.0La creación de formularios Web con VS 2005 es muy diferente a la de VS 2002 y 2003,que dependían de un directorio virtual IIS definido previamente. El cuadro de diálogoNuevo proyecto de VS 2005 no incluye los iconos Sitio Web ASP.NET, Servicio WebASP.NET y otros relacionados con la Web. El menú Archivo/Nuevo ofrece una selecciónde sitios Web que abre el cuadro de diálogo Nuevo sitio Web con una serie de iconosbasados en el sistema de archivos, como Sitio Web ASP.NET, Servicio Web ASP.NET yotros iconos de plantillas.La carpeta raíz por defecto para añadir nuevos sitios Web osubcarpetas de servicios es .\WebSites. El cuadro de diálogo Seleccionar ubicación sepuede abrir pulsando el botón Examinar, aceptando la opción por defecto Sistema dearchivos y añadiendo un nombre de acceso más apropiado para el cuadro de textoUbicación (ver siguiente figura).

Pulse el botón Aceptar para generar una carpeta con los ítems del proyecto, añada unacarpeta vacía App_Data, un archivo de página Default.aspx y un archivo de código ocul-to Default.aspx.vb. Si no encuentra el archivo Default.aspx.vb, pulse con el botón derecho

240

Bases de datos con Visual Basic

VisualBasic2005_07.qxp 02/08/2007 16:28 PÆgina 240