Vous êtes sur la page 1sur 32

de ejemplo.

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

Vous aimerez peut-être aussi