Ntese que el tiempo de respuesta medio de las transacciones distribuidas
es de unos 4 segundos. Los tems de las transacciones slo aparecen en la ventana Lista de transacciones cuando estn activados (ver figura de la pgina siguiente). 6.1.5 Listar manualmente SqlTableAdapters en una transaccin explcita Si prefiere el modelo de transaccin "tradicional" con un alistamiento explcito de los objetos transactuados y control granular de las invocaciones de los mtodos Commit o Rollback, puede utilizar el objeto CommittableTransaction, tal como se muestra en el cdi- 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 Pgina 206 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 explcitos de transaccin para las actualizaciones con SqlTableAdapter son, por defecto, las transacciones distribuidas. Las promociones se producen cuando el cdigo lista un segundo objeto SqlTableAdapter.Connection en la transaccin. 6.1.6 Definir las opciones TransactionScope y Transaction El constructor TransactionScope tiene siete sobrecargas, pero las dos siguientes son las ms 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 aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:25 Pgina 207 La enumeracin TransactionScopeOption tiene los tres miembros siguientes: TransactionScopeOption.Requires TransactionScopeOption.RequiresNew TransactionScopeOption.Suppress El valor por defecto es Requires (una transaccin). Especifique Suppress si no quiere que TransactionScope utilice la transaccin ambiente. A continuacin vemos los dos miem- bros TransactionScopeOption: TransactionOption.IsolationLevel TransactionOption.Timeout IsolationLevel es por defecto Serializable, pero puede ser cualquiera de los siete miembros que aparecieron en el primer captulo de este libro. Slo SQL Server 2005 soporta Snapshot en Isolation. El valor por defecto de Timeout es 1 minuto. 6.2 Aadir relaciones a los SelectCommand de la tabla de datos Los DataSets actualizan tablas individuales, pero eso no significa que no se puedan aa- dir relaciones al SelectCommand de una tabla. Las relaciones permiten mejorar las edi- ciones de los usuarios aadiendo columnas de slo lectura desde una relacin "de muchos a uno" con una tabla relacionada. Como ejemplo, si se aaden las columnas ProductName, QuantityPerUnit y UnitPrice de la tabla Products Northwind a un DataGridView 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 por defecto de los registros nuevos y actualizar la columna UnitPrice de la tabla Order Details cuando se realicven cambios en el ProductID. Aadir columnas desde relaciones muchos a uno (many-to-one) no es el sustituto ideal a las columnas de cuadro combinado pobladas por listas lookup. La tcnica descrita anteriormente, es normalmente un mtodo ms efectivo siempre que se trabaje con formularios de entrada de datos donde el nmero de tems del cuadro combinado sea inferior a 100. El proyecto de ejemplo de esta seccin, SelectCommandJoins.sln, demuestra cmo aadir relaciones a los SelectCommand y sacar partido de la relacin many-to-one para simplifi- car la actualizacin de la tabla base Order Details. El proyecto empieza con una fuente de 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 el UnitPrice necesarios para editar y crear nuevos records de Order Details. Aada ProductsTableAdapter y ProductsBindingSource a la bandeja arrastrando el icono de la tabla Products desde la ventana Origenes de datos hasta el formulario Join.vb y des- pus borre el ProductsDataGridView que se ha aadido al formulario. 208 Bases de datos con Visual Basic VisualBasic2005_06.qxp 02/08/2007 16:25 Pgina 208 6.2.1 Aadir una relacin a SelectCommand A continuacin vemos los pasos para aadir un INNER JOIN entre las tablas Order Details y Products de la operacin Fill: 1. En la ventana Diseador de DataSet, pulse con el botn derecho la cabecera del TableAdapter de Order Details y seleccione Propiedades. 2. En la ventana Propiedades, expanda el nodo SelectCommand, pulse el nodo CommandText, y pulse el botn del constructor para abrir el cuadro de dilogo Generador de consultas. 3. Pulse con el botn derecho del ratn el panel de las tablas, seleccione Agregar tabla, y aada la tabla Products. 4. Seleccione las columnas ProductName, QuantityPerUnit y UnitPrice de la tabla Products. 5. Cambie dbo.Products.UnitPriceASExpr1 por dbo.Products.UnitPriceASListPrice. 6. Pulse el botn Ejecutar consulta para ver los resultados en la parrilla. 7. Pulse el botn Aceptar para cerrar el cuadro de dilogo Generador de consultas y pulse el botn No cuando le pregunten si quiere regenerar los comandos de actua- lizacin basndose en el nuevo comando de seleccin. 8. Pulse con el botn derecho la cabecera de Order Details y seleccione Ajustar autom- ticamente para mostrar las columnas ProductName, ListPrice y QuantityPerUnit (ver la figura de la pgina siguiente). 209 La aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 209 9. Abra la ventana Propiedades y verifique que la sentencia SQL CommandText de los nodos DeleteCommand, InsertCommand y UpdateCommand incluye slo columnas de la tabla Order Details. Acontinuacin 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 Aadir las columnas adjuntadas con relaciones al DataGridView Las columnas de la tabla Products se han de aadir manualmente pulsando con el botn derecho el Order_DetailsDataGridView y seleccionando Editar columnas para abrir el cua- dro de dilogo del mismo nombre. Pulse Aadir columnas y aada la columna Product- Name detrs de ProductID. Aada las columnas QuantityPerUnit y List Price. Defina el valor True para la propiedad ReadOnly de las tres columnas y cambie el orden de las columnas por OrderID, Quantity, ProductID, ProductName, QuantityPerUnit, ListPrice, UnitPrice y Discount. 6.2.3 Proporcionar los valores por defecto y columnas de slo lectura Para 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 Pgina 210 nued field, se necesita la ProductsBindingSource que aadi anteriormente en este captu- lo. Defina el valor de la propiedad AllowNew de ProductsBindingSource como False y verifique DataSource que es NorthwindDataSet y DataMember es Products. El siguiente manejador de eventos da intencionadamente valores por defecto que no son vlidos y muestra un icono de error al aadir 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 valores no vlidos 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 aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 211 .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 pgina siguiente muestra el formulario Joins.vb del proyecto de ejemplo SelectCommandJoin.sln en el proceso de aadir un nuevo tem de linea a Order Details. En el apartado siguiente veremos la finalidad de los controles situados sobre el Orders DataGridView. 6.3 Mejorar el rendimiento reduciendo el tamao de los juegos de datos Cargar DataSets y poblar DataGridViews con registros innecesarios puede hacer bajar considerablemente el rendimiento de servidores y clientes, especialmente al reprodu- cir los DataSets perpetuados durante largo tiempo por los usuarios desconectados. Los apartados siguientes describen cmo reducir la carga del servidor y el consumo de recursos locales, y cmo mejorar la edicin de datos limitando el nmero de filas devueltas por las operaciones DataTableAdapter.Fill. Las consultas convencionales TOP n 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 Pgina 212 tcnicas de paginacin, adems, minimizan el consumo de recursos y dan acceso a los usuarios conectados a los datos ms antiguos. 6.3.1 Limitar el nmero de filas devueltas por las consultas TOP n El mtodo ms obvio para limitar el nmero de records devueltos por las operaciones Fill es aadir un modificador TOP n o TOP n PERCENT y una clusula ORDER BY apropiada a la consulta SQL del TableAdapter para el SelectCommand. Por ejemplo, la siguiente consulta SQL carga las 100 ltimas filas de la tabla Orders para poblar el DataGridView 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 debera hacer lo mismo con las operaciones TableAdapter.Fill en las tablas hijo. La consulta SelectCommand de Order Details, que veamos en el apartado anterior, carga todas las filas extendidas de Order Details en el Order_DetailsDataTable, para lo cual se consumen muchos ms recursos de lo necesario. Para devolver slo las filas hijo que dependen de las filas de Orders, hay que aadir un predicado IN con un subselect, tambin llamado subquery, tal como se destaca 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 aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 213 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 captulo utiliza valores literales para asegurar la compatibilidad con SQL Server o MSDE 2000. 6.3.2 Aadir clases Partial para TableAdapters Las clases TableAdapter no estn anidadas en los DataSets de ADO.NET 2.0. En su lugar, los TableAdapters tienen su propio espacio de nombres para impedir que haya nombres de clase autogenerados por duplicado. Nombres de espacios de nombres autogenera- dos son, por ejemplo, DataSetNameTableAdapters, como NorthwindDataSetTableAdapters, que contiene PartialPublicClassOrdersTableAdapter, PublicClassOrder_DetailsTableAdapter y PublicClassProductsTableAdapter. Sustituir sentencias dinmicas SQL SELECT por el SelectCommand que se aadi en el diseador de consultas, implica sobrecargar el mtodo Fill y dar el valor variable de la propiedad CommandText como segundo argu- mento. Si aade la signatura cargada a las clases parciales del DataSet perder los datos aadidos cuando se regenere el Dataset. Por lo tanto, debe aadir un archivo de clase parcial al proyecto en este ejemplo TableAdapters.vb que contenga cdigo similar al siguiente: 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 Pgina 214 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 verificacin Limit Order Details Rows del proyecto y pulsan- do el botn Reload Data se aade el predicado subselect a Order_DetailsDataTable.Se- lectCommand. Probablemente no notar una diferencia notable en el tiempo de carga de los dos tipos de consulta, ya que el predicado IN aumenta el tiempo de ejecucin de la consulta. De todos modos, el predicado IN disminuye el tamao del juego de datos per- petuado, bajando de los 824 KBytes de todas las filas de Orders a slo 182 Kbytes para 100 filas. Pulsando el botn Save Data del Navegador de datos, los DataSet se guardan en un archi- vo AllDetails.xml si la casilla de verificacin est deseleccionada, o en Subselect.xml en caso contrario. 6.4 Trabajar con imgenes en DataGridViews Los DataGridViews requiren una columna DataGridViewImageColumn para mostrar im- genes devueltas por las tablas que contienen grficos 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 sin imgenes (valores nulos) muestran el grfico de Internet Explorer con un vnculo HTML a un archivo de imagen "missed". Las DataGridViewImageColumns comparten la mayora de propiedades y mtodos de otros tipos de datos, pero incorporan dos propiedades, Image e ImageLayout especficas de los grficos. La propiedad Image permite especificar una imagen por defecto del archivo MyResources.resx o cualquier otro archivo de recur- sos. La propiedad ImageLayout permite seleccionar un miembro de la enumeracin DataGridViewImageCellLayout: NotSet, Normal, Stretch o Zoom. Estos miembros corres- ponden aproximadamente a la enumeracin SizeMode del PictureBox. Como era de esperar, Normal es el valor por defecto que centra la imagen con su resolucin original. 6.4.1 Aadir columnas Image a los DataGridViews Cuando se crea una fuente de datos de una tabla con una columna image o varbinary, la ventana de Orgenes 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 o cualquier otro componente de datos, el DataGridView no muestra ninguna DataGridViewImageColumn para el mapa de bits. 215 La aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 215 Para aadir la columna image que falta, pulse con el botn derecho el DataGridView y seleccione la opcin Editar columnas para abrir el cuadro de dilogo del mismo nombre. Pulse el botn Agregar para abrir el cuadro de dilogo y, con el botn de opcin Columna de enlace de datos seleccionado, seleccione la columna y pulse Agregar (ver figu- ra siguiente). A continuacin, especifique en Width un valor apropiado para el diseo del DataGridView. Otra alternativa es seleccionar Rows como valor de la propiedad AutoSizeCriteria. Defina inicialmente AllCellsExceptHeaders como valor de la propiedad AutoSizeRowsMode del DataGridView. Despus de un test inicial, puede darle a la pro- piedad RowTemplate.Height un valor que mantenga el ratio de imagen con el valor Width de 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 tamao de los mapas de bits LargePhoto para el DataGridView es de 240 por 149 pxeles. La siguiente figura muestra tres columnas de las dos primeras filas de la tabla en NormalImageLayout. 6.4.2 Manipular imgenes en DataGridView El cdigo aadido a la clase ProductPhoto permite comprobar el efecto de los cambios ImageLayout en el aspecto las imgenes: guarde el contenido de un DataGridViewImage- Cell seleccionado en el correspondiente archivo LargePhotoFileName(.gif), muestre una imagen en el cuadro de imagen (PictureBox) y sustituya la imagen seleccionada por una copia del archivo que ha guardado. 6.4.3 Cambiar ImageLayout Por defecto, el ancho de la columna LargePhoto y la altura de las filas se ajustan a la dimensin de las imagenes. Para comprobar los tres modos de imagen, arrastre el borde 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 Pgina 216 seleccione a continuacin el botn Stretch para distorsionar la imagen cambiando el ratio de proporcin. Seleccionando Zoom, la propiedad AutoSizeRowsMode toma el valor DataGridViewAutoSizeRowsMode.None, el cual permite manipular la altura de fila y la anchura de la columna y ver los diferentes cambios de tamao que se pueden apli- car a la imagen manteniendo siempre la proporcin de aspecto habitual del mapa de bits. Los siguientes manejadores responden al evento CheckChange de los botones de opcin: 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 aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 217 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 imgenes en DataGridViews no es un proceso intuitivo. La propie- dad Value de un objeto DataGridViewImageCell se basa en el tipo de datos Byte(), no en el tipo Image que cabra esperar. Hay que incrustar Value en Byte y despus crear una instancia FileStream para guardar el array Byte en el correspondiente archivo LargePhotoFileName.gif. Crear una instancia MemoryStream para asignar la propiedad Image de PictureBox del formulario frmPictureBox es ms eficaz que cargar el PictureBox desde el archivo guardado. Sustituir la imagen original por una copia del archivo se hace mediante el mtodo File.ReadAllBytes para simplificar la lectura de un archivo de tamao 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 Pgina 218 '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 aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 219 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 imgenes desde los campos de objeto OLE en Access La base de datos Northwind de SQL Server 2000 contiene las tablas Categories y Employees que se importaron de una versin anterior de Access. La columna Picture de la tabla Categories y la columna Photo de la tabla Employees tienen tipos de datos image, pero los bitmaps de formato BMP tienen un wrapper de objetos OLE. Las imgenes aparecen en DataGridView, 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 yDataGridViews La emergencia de los documentos XML como el nuevo formato de intercambio de documentos ha creado un requerimiento para las aplicaciones cliente que permiten a los usuarios revisar, editar y crear Infosets XML. Los documentos de negocios que uti- lizan Infosets para representar tablas de datos con una jerarqua de una o ms relacio- nes uno-a-muchos (one-to-many), son habituales en la gestin de relacin con el cliente (en ingls: customer relationship management, CRM), gestin de cadena de suministro (supply chain management, SCM), y otras aplicaciones de negocios como BizTalk Server 2004. Estas aplicaciones intentan minimizar la intervencin humana en sus procesos automatizados 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 los documentos jerrquicos con mltiples relaciones uno-a-muchos son difciles de editar en Word o Excel. Access 2003 permite importar esquemas XML para crear tablas con tipos de datos asignados, establecer claves y relaciones, adjuntar y editar datos y des- pus exportar las tablas, o una consulta a un archivo XML. De todos modos, un docu- mento XML jerrquico exportado no guarda ninguna relacin con la estructura origi- nal del documento fuente. Transformar el archivo XML para regenerar la estructura del documento original sera preocuparse ms de lo necesario. InfoPath 2003 maneja la edicin de documentos jerrquicos y mantiene la estructura del documento, pero sus 220 Bases de datos con Visual Basic VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 220 formularios basados en HTML tienen un repertorio limitado de controles y, al igual que otros miembros de Office 2003, los usuarios de InfoPath 2003 necesitan licencia del cliente. Los usuarios acostumbrados a editar tablas de bases de datos con formularios Windows creados con alguna versin de Visual Studio, sin duda preferirn una UI similar o idn- tica para editar los Infosets XML tabulares, con controles DataGridView y, donde sea necesario, con cuadros de texto vinculados u otros controles de formulario Windows. Los controles DataGridView no se pueden vincular directamente a los documentos XML, 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 utilizar el 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 DataSet Microsoft ha diseado los DataSets para guardar en DataTables los datos relacionales; la representacin XML de DataSets y DataTables est pensada bsicamente como un meca- nismo para perpetuar o tratar datos a distancia. Por lo tanto, los documentos XML que sirven de fuente a los juegos de datos, deben tener un esquema adaptable a los juegos. A continuacin indicamos los aspectos ms importantes a tener en cuenta cuando se utilizan esquemas existentes para generar juegos de datos tipificados: El diseador de juegos de datos asigna el juego de datos el nombre del elemento de nivel superior (raz o documento). Si el esquema contiene una declaracin global del espacio de nombres, se convierte en el espacio de nombres del juego de datos. Los elementos subsiguientes con elementos hijos o los elementos hijo con atributos generan DataTables. Esta caracterstica es propia de los documentos centrados en atri- butos, como los representantes XML de los Recordsets ADO, pero tambin puede hacer que 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 sencillos XSD en correspondencia con los tipos de datos del sistema NET. Los DataSets estn centrados en el elemento; si en el esquema se especifican atributos para la tabla, el diseador de juegos de datos aadir los atributos como columnas de tabla. Los esquemas con grupos de elementos hijo anidados establecen automticamente relaciones one-to-many entre las tablas y aaden una clave primaria TableName_Id y una columna de clave fornea por cada relacin con la tabla. La clave primaria TableName_Id es una columna Int32 AutoIncrement; leer un documento XML en el juego de datos genera los valores de TableName_Id. Si los grupos de elementos hijo no estn anidados, hay que especificar la relacin entre las 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 relacin de tabla anidada. 221 La aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 221 El diseador de DataSets tiene problemas para importar esquemas secundarios que soporten espacios de nombres mltiples y elementos calificados como espacios de nombres. El diseador 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 automticamente. Las restricciones anteriores hacen difcil, si no imposible, generar juegos de datos tipi- ficados desde esquemas XML complejos, para documentos de negocios estndar, como Universal 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 mayora de las aplicaciones de edicin XML deben producir un documento de sali- da con la misma estructura que el documento fuente, lo que significa que la edicin slo debe afectar a los contenidos de los elementos. La estructura tabular de los juegos de datos permite exportar todo el contenido o las filas seleccionadas de tablas concre- tas a los streams o archivos XML. Tambin se pueden generar juegos de datos desde documentos fuente relacionados con estructuras definidas en un nico esquema. Si la aplicacin debe reestructurar el documento de salida, se puede aplicar un XSLT transform para la versin final del documento editado. Otra alternativa es sincronizar el juego de datos con una instancia XmlDataDocument y aplicar el transform a la instancia. 6.5.2 Esquemas para documentos XML de jerarqua anidada La estructura ideal de un documento fuente de un juego de datos es un Infoset XML con una jerarqua anidada de elementos relacionados. El diseador de juegos de datos genera DataSets automticamente desde esquemas compatibles con los documentos anidados. El siguiente documento XML, abreviado, es un ejemplo tpico de archivo XML generado al serializar un juego de objetos relacionados con los negocios en una jerarqua 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 Pgina 222 A continuacin vemos el esquema general del documento anterior, con un elemento raz <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 diseador de DataSets interpreta los grupos <xs:complexType> no raz que tienen ele- mentos de campo, los elementos anidados <xsd:complexType>, o ambos, como tablas de datos. Por eso, los elementos de campo deben tener tipos de datos sencillos como xs:string, xs:int o xs:decimal, o grupos <xs:complexType> que representan tablas relacio- nadas. 223 La aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 223 Un documento fuente XML que especifica un atributo de espacio de nombres por defecto con <rootElementxmlns= documentNamespace> requiere un esquema que incluya un atributo targetNamespace="documentNamespace" para el elemento <xs:schema> ms alto de la jerarqua. Si su esquema tiene una estructura tan bsica como la del ejemplo precedente y slo tiene un targetNamespace o ningn espacio de nombres de documen- to, est de suerte. Haga los cambios que se destacan en negrita a continuacin en los dos primeros elementos del esquema para indicar que el esquema representa un juego de 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 botn derecho el icono del archivo en el Explorador de proyectos y seleccione Aadir a proyecto, lo que gene- rar archivos Schema.Designer.vb, Schema.xsc, y Schema.xss. Realice una doble pulsacin sobre Schema.xsd para abrirlo en el Editor DataSet y mostrar la ventana Orgenes de datos. Puede aadir el juego de datos a la bandeja del diseador arrastrando la herramienta DataSetName desde la seccin de componentes ProjectName hasta el formulario, o selec- cionando la herramienta DataSet desde la seccin Data y seleccionando ProjectName.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 de datos para aadir un BindingNavigator y cuadros de texto o un DataGridView para edi- tar parentGroup, y despus aadir DataGridViews para las tablas childGroup y grand- childGroup. 6.5.3 Un ejemplo de esquema anidado La siguiente figura muestra un juego de datos tipificado generado desde un esquema (NorthwindDS.xsd) para un documento XML anidado (NorthwindDS.xml) que contiene un pequeo subjuego de datos de las tablas Customers, Orders y Order Details de Northwind. Al generar el juego de datos, la columna Customers_Id de clave primaria se aade a la tabla Customers y la correspondiente columna de clave fornea Customers_Id se aade a la tabla Orders para crear la relacin Customers_Orders. La tabla Orders gana una clave primaria Orders_Id para la relacin Orders_Order_Details con la clave fornea Orders_Id de la tabla Order_Details. A continuacin vemos el esquema NorthwindDS.xsd para el documento 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 Pgina 224 <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 aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 225 <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> Ntese que el esquema NorthwindDS.xsd no contiene referencias a las columnas aadi- das de clave primaria y clave fornea. Generar un juego de datos desde un esquema de documento fuente anidado no modifica el esquema En el archivo NorthwindDS.De- signer.vb, el mtodo Northwind.InitClass aade esas DataColumns a las DataTables al espe- cificar los ForeignKeyConstraints, y despus aade las DataRelations con la propiedad Nested definida como True. 226 Bases de datos con Visual Basic VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 226 6.5.4 La ventana Propiedades de las columnas Para examinar las propiedades de las columnas aadidas, seleccione la columna y pulse con el botn secundario del ratn 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 fornea de la tabla Order_Details (derecha). En la ventana Propiedades puede editar el tipo de datos, el nombre de la columna y otras propiedades de cualquiera de las columnas de la tabla. Pulse la ventana con el botn derecho y seleccione Aadir para aadir una nueva columna a la tabla de datos. Amodo de ejemplo, puede aadir una columna Extended a la tabla Order_Details que puede cal- cular con la frmula 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 aade un grupo <xs:annota- tion> para especificar la fuente de datos, la mayora de los elementos adquieren una gran cantidad de atributos msprop y el tamao del archivo aumenta considerablemnte. NorthwindDS.xsd, por ejemplo, pasa de 4 KBytes a 35 KBytes. Por lo tanto, si tiene que editar el esquema y conservar la estructura original, pulse el archivo con el botn se- cundario, en el Explorador de soluciones, seleccione Abrir cony, en el cuadro de dilo- go que se abre con el mismo nombre, seleccione XMLEditor. No seleccione DataSet Editor, que es la opcin por defecto, ni tampoco XML Schema Editor. 6.5.5 Un esquema anidado con atributos Al aadir atributos a los elementos que generan tablas de datos se aade a la tabla una columna 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 aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 227 red" /> aade una columna totalAmount a la tabla Order_Details. La siguiente figura muestra el esquema NWAttributes.xsd abierto en el Editor DataSet. La primera columna de cada tabla viene generada por un atributo definido en el equema e incluido en el documento fuente NWAttributes.xsd source document. Cuando se aade un atributo a una tabla, se aade tambin un atributo msdata:Ordinal="n" , en orden consecutivo, a cada nodo hijo que representa una columna de la tabla. Si se aade un atributo obligatorio a un elemento hijo, como por ejemplo ProductID, el diseador 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 prctica comn disear juegos de elementos "envuel- tos" en otros grupos. Un ejemplo es envolver Customer y sus hijos en un grupo Customers, Order en un grupo Orders y Order_Detail en un grupo Order_Details para crear la estructura abreviada que vemos a continuacin: <Customers> <Customer> <CustomerID>GREAL</CustomerID> ... <Fax></Fax> <Orders> <Order> 228 Bases de datos con Visual Basic VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 228 <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> Acontinuacion 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 aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 229 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 el DataSet se pueda editar en DataGridViews hay que aadir relaciones entre los campos CustomersID de las tablas Customers y Orders, y los campos OrderID de las tablas Orders y Order_Details, tal como se describe ms adelante en este captulo. 6.5.7 Un ejemplo de esquema plano Los esquemas anidados pueden exportar tablas como si fueran documentos XML invo- cando el mtodo DataTable.WriteXML(ExportFileName,XmlWriteMode.IgnoreSchema). Los esquemas planos aaden la capacidad de importar documentos XML concretos, que complen el esquema de DataSet para tablas relacionadas. No obstante, el diseador de juegos de datos no aade columnas TableName_Id, ForeignKeyConstraints ni DataRelations. A continuacion, el esquema abreviado de Northwind.xsd para Northwind.xml, que es la versin plana de NorthwindDS.xml, con las claves primaria y fornea destacadas en negrita: <?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 Pgina 230 <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 versin editable de Northwind.xsd hay que seguir los siguientes pasos en la ventana del DataSet Editor: Aadir claves primarias a cada tabla de datos. Seleccionar y pulsar con el botn dere- cho la columna de clave primera y seleccionar a continuacin Establecer clave principal para las tres tablas. Opcionalmente, seleccione Editar clave para abrir el cuadro de dil- go Restriccin 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 el botn derecho la columna OrderID, seleccione Editar clave y marque la casilla de verifi- cacin ProductID. 231 La aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 231 Pulse con el botn derecho el entorno del DataSet Editor y seleccione Agregar/Relation para abrir el cuadro de dilogo Relacin con los valores por defecto para una relacin entre Customers y Orders, que tendr el nombre FK_Customers_Orders. En la lista Columnas 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 relacin, FK_Customers_Orders1 por a FK_Orders_Order_Details, y seleccione Orders en la lista de la tabla padre y Order_Details en la lista de la tabla hijo. Las listas Columnas de clave y Columnas de clave externa muestran el OrderID. Si quiere que los usuarios de la aplicacin puedan aadir nuevos records a Orders y Order_Details, seleccione la columna OrderID de clave primaria, seleccione Propiedades y 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 aadir las claves primarias y las relaciones a las tablas, al final del esquema se aa- 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 Pgina 232 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 fornea. Los atributos msprop son referencias a las rela- ciones entre datos (DataRelations) aadidas por la clase parcial Northwind del archivo Northwind.Designer.vb. 6.5.8 Inferir un esquema XML para generar un juego de datos Si todava no tiene ningn esquema para su documento fuente XML, puede elegir entre las cinco opciones siguientes para generar el esquema con VS 2005: 233 La aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 233 Abra un documento fuente XML representativo en el editor de XML, seleccione XML/CreateSchema para inferir un esquema, y gurdelo en la carpeta del proyecto con el nombre SchemaName.xsd. El generador de esquemas del editor intentar inferir tipos de datos XSD examinando los valores de texto en los campos del documento fuente. Desafortunadamente, el proceso de inferencia no suele tener xito con valores numri- cos unsigned que no tienen valores decimales; les asigna tipos de datos XSD numricos, con los valores ms pequeos posibles. Por ejemplo, calcular 0 dividido entre 255 se convierte en xs:unsignedByte, 256 entre 65.535 se convierte en xs:unsignedShort, y los nmeros con muchas cifras se convierten en xs:unsignedInt o xs:unsignedLong. Amenos que tenga alguna razn para obrar de otra manera, asigne xs:int a todos los valores sin fracciones decimales. Cree un juego de datos vaco en tiempo de ejecucin, invoque el mtodo Data- Set.ReadXml(DocumentFileName) y guarde el archivo del esquema invocando el mtodo DataSet.WriteXmlSchema(SchemaFileName). Este ltimo mtodo genera un esquema no tipificado en el que todos los elementos tienen asignado el tipo de datos xs:string y un atributo minOccurs="0". Abra SchemaFileName.xsd en el editor XML, cambie los tipos de datos de los valores numricos o de fecha/tiempo por el tipo apropiado xs:datatype, y elimine todos los atributos minOccurs="0" que no resulten apropiados. Genere un esquema tipificado con el proceso anterior, pero invoque el mtodo Da- taSet.ReadXml(DocumentFileName,XmlReadMode.InferTypedSchema) para generar un esquema idntico al generado por el editor XML. Abra un VS 2005 Command Prompt, navegue hasta la carpeta del proyecto y escriba xsd.exe DocumentFileName.xml para generar DocumentFileName.xsd. El esquema es idn- tico al generado por el mtodo precedente. Si no dispone de ningn 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 especificar una fuente inicial para inferir el esquema inicial y despus procesar los documentos fuente adicionales para refinar el esquema. Si tiene que inferir y refinar esquemas de forma rutinaria, puede utilizar el mtodo System.Xml.Schema.InferSchema para simular la herramienta de Microsoft, XSD Inference 1.0 Tool. El siguiente cdigo infiere un esquema para una instancia de documento ini- cial (Initial.xml), refina el esquema con tres instancias de documentos adicionales y escribe 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 Pgina 234 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 edicin desde fuentes de datos XML El proceso de crear formularios de edicin para documentos XML es parecido al de edi- tar tablas de bases de datos. Despus de generar un juego de datos tipificado a partir del esquema existente, arrastre la tabla de ms arriba desde la ventana Orgenes de datos hasta el formulario donde quiere aadir un control DataNavigator y DataGridViewo cua- dros de texto para detalles. Repita el mismo proceso con los DataGridViews para las tablas relacionadas y especifique la DataRelation apropiada para generar una DataRelationBindingSource para el valor de la propiedad DataSource. Adiferencia de los DataGridViews vinculados a FK_ParentTable_ChildTableBindingSources generados por tablas de bases de datos, la BindingSource se crea cuando, en la lista desplegable de la propiedad DataSource, se especifica una lista relacionada. Los dos ejemplos siguientes de proyectos ilustran los cambios necesarios para crear DataRelationBindingSource, permitir la adicin de nuevos elementos en el documento y acomodar juegos de datos envueltos y anidados. 6.5.10 El proyecto de ejemplo EditNorthwindDS El proyecto EditNorthwindDS.sln est basado en el documento fuente NorthwindDS.xml y en el esquema NorthwindDS.xsd. El formulario tiene DataGridViews poblados con datos de las tablas Customers, Orders y Order_Details, tal como muestra la siguiente figura. Abra la ventana Orgenes de datos y arrastre el icono del grupo padre de Customers, el icono de su subgrupo Orders y el icono del subgrupo Order Details del grupo Orders 235 La aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 235 hasta el formulario para aadir los tres DataGridViews. Aada cdigo al manejador de evento Form_Load para poblar el juego de datos con el documento NorthwindDS.xml. La siguiente figura muestra la lista Orgenes de datos tras realizar las operaciones ante- riores y cargar el documento NorthwindDS.xml. La instruccin OrdersDataGridView..- Sort(.Columns(0),System. ComponentModel.ListSortDirection.Descending) del manejador de eventos clasifica los OrderID por orden descendente. Si quiere que los usuarios pue- dan aadir nuevos registros a Orders y Order_Details con los valores apropiados de la columna OrderID, deber editar el esquema y darle a la propiedad AutoIncrement de las columnas OrderID y Order_Id el valor True en el cuadro de dilogo Propiedades de ColumnName. En caso contrario, defina el valor False para la propiedad AllowUserTo- AddRows de DataGridViews. Puede aadir los atributos autogenerados Customers_Id, Orders_Id y Order_Details_Id como columnas de los DataGridViews. Mientras personaliza la coleccin Columns de los DataGridViews en el cuadro de dilogo Editar columnas, lleve las columnas autogenera- das al final de la lista SelectedColumns y defina el valor True para sus propiedades ReadOnly. Si no quiere que los usuarios puedan aadir nuevas filas, borre estas colum- nas de los DataGridView. Aada un botn para guardar los cambios e invoque el mto- do NorthwindDS..WriteXml(strFile,Data.XmlWriteMode.IgnoreSchema) para guardar el documento editado con los datos. El proyecto de ejemplo guarda un archivo diffgram (NorthwindDS.xsd) antes de guardar los camibos y tiene botones para mostrar en Internet Explorer el esquema y el documento XML guardado. 236 Bases de datos con Visual Basic VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 236 Para aadir nuevas filas se necesita un procedimiento OrdersDefaultValues que llama al manejador de evento OrdersDataGridView_DefaultValuesNeeded. El cdigo del procedi- miento es similar al que vimos en el captulo anterior para el manejador de evento DefaultValuesNeeded, pero ahora hay que aadir el valor Customers_Id para mantener la relacin, 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 aplicacin de tcnicas avanzadas de los DataSets VisualBasic2005_06.qxp 02/08/2007 16:26 Pgina 237