Tablas relacionadas y Valores poco explícitos

Anuncio
Tablas relacionadas y valores poco explícitos/Harvey Triana
Mayo/1999
Tablas relacionadas y Valores poco explícitos
Solución aplicando clases de reconocimiento de datos y algo más
Por Harvey Triana
«Tablas Relacionadas y Valores Poco Explícitos», es el título en la documentación de Visual
Basic para referirse a las listas relacionadas, que algunos llaman Picklists (antiguo término de
DBASE). En todo caso este es un viejo problema de las Bases de Datos Relacionales y que
aún los programadores Visual Basic de debaten por una solución eficaz. En años pasados,
programe un sistema basado en controles ActiveX, que hoy día usan algunas de mis
aplicaciones de gestión escritas con Visual Basic 5. Omití el uso de los controles Data Bound
List (DBCombo y DBList) de Visual Basic 5, ya que estos controles requieren de un control
Data adicional para trabajar, y en resumen, el consumo de recursos es muy alto (formularios
con 20 o más listas enlazadas a controles DBCombo son demasiado cargados). Mi técnica con
controles ActiveX se baso en las propiedades de los objetos Field y de los DataBindings de
controles estándar. Si bien esta técnica es más eficiente que usar controles Data Bound List y
sus controles Data, aun es lenta (en formularios grandes), y peor aun, es terriblemente
complicado dar mantenimiento (por ejemplo alguna modificación en la estructura o relaciones
de la base de datos es un lío). Visual Basic 6.0 entrega una herramienta realmente eficaz, las
clases de reconocimientos de datos. En este artículo expongo la manera de aplicar la
tecnología ADO para una solución optima a este problema.
Perspectiva actual
Visual Basic alcanzó una gran evolución en el tratamiento de los datos desde Visual Basic 5.0
hasta el 6.0, especialmente marcada por el advenimiento de ADO. Sin embargo no todo para
hay, Visual Basic 6.0 trae un arsenal impresionante de facilidades para programar contra datos
a un nivel muy elevado. Por ejemplo los objetos DataEnvironment permiten crear una solución
sencilla casi de inmediato y con mucha flexibilidad. No obstante los DataEnvironment, o alguno
de los asistentes de Visual Basic no resuelven el viejo problema de las tablas relacionadas y
claves externas. Si bien un novato, o especialista en manejar asistentes, podría decepcionarse
al llegar a este punto.
La solución para un formulario de datos es relativamente sencilla. Puede encontrar una
descripción en la librería MSDN bajo el título: «Vinculación de dos tablas mediante los controles
DataCombo y DataList». Retomaré este escrito e innovaré en lo siguiente: (1) Dar un origen de
datos a las listas con Clases de Reconocimiento de Datos y, (2) Daré una solución potente
para una grilla de datos (esta es la parte complicada). Primero que todo retomaré la definición
del problema tal y como la describen en la documentación de Visual Basic.
Descripción del problema de las listas relacionadas
Esto es textual de la librería MSDN bajo el título: «Vinculación de dos tablas mediante los
controles DataCombo y DataList»:
Tablas relacionales y valores "poco explícitos"
En una base de datos relacional, la información que se utiliza repetidamente no se almacena
en su totalidad en múltiples lugares, sino que el grueso de la información se almacena en un
conjunto de registros compuesto por muchos campos; entre estos campos está el campo Id.
que identifica de manera individualizada a cada registro. Por ejemplo, la base de datos Biblio,
que se suministra con Visual Basic almacena los nombres de varias empresas editoriales en
una tabla llamada "Publishers". La tabla contiene muchos campos, como dirección, ciudad,
código postal y número de teléfono. Pero para hacerlo más sencillo, considere los campos
Name y PubID como los dos campos esenciales de la tabla. El campo Name almacena el
1
Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS
Tablas relacionadas y valores poco explícitos/Harvey Triana
Mayo/1999
nombre de un editor, mientras que el campo PubID almacena un valor comparativamente "poco
explícitos", como un número de código. Pero este valor "poco explícitos" es más importante ya
que identifica individualmente al editor, y sirve de vínculo para todo el conjunto de registros. Y
es ese valor el que está almacenado en múltiples conjuntos de registros de una segunda tabla.
La segunda tabla se llama "Titles", y cada conjunto de registros contiene información, como
título, año de publicación e ISBN. Incluido entre los campos, hay uno llamado "PubID". Este
campo tiene exactamente el mismo nombre que su campo correspondiente en la tabla
Publishers ya que almacena el valor que vincula el título a un editor determinado.
Este eficaz esquema presenta un pequeño problema: Dada una aplicación para bases de datos
que permita a los usuarios insertar nuevos títulos, el usuario debe, de algún modo, introducir
valores enteros que identifiquen al editor (Publisher). Esto está bien si el usuario ha
memorizado el Id. único de cada editor, pero para la gente sería más fácil ver el nombre del
editor y hacer que la aplicación almacene el valor asociado en la base de datos. Los controles
DataList y DataCombo resuelven este problema fácilmente.
En la gráfica anterior vemos los dos orígenes y los tres campos. Aquí, la tabla Titles (Libros) es
el origen a actualizar, la tabla Publishers (Editoriales) es el origen que suministra los valores
explícitos para actualizar. El campo PubID de la tabla Titles es el campo a actualizar a partir de
la lista y comúnmente se denomina Clave Externa. El campo PubID de la tabla Publishers es la
clave en la relación. El tercer campo, viene a ser cualquier campo, o combinación, de la tabla
Publishers y es el que se mostrará al usuario, por ejemplo es claro que mostraríamos el campo
Name (nombre de la Editorial).
NOTA. Existe una relación en estas dos tablas. Un registro de Publishers tiene varios en Titles,
en otras palabras, una Editorial representa a varios Libros. En general, esta relación no es un
requisito obligado para implementar listas relacionadas. Un objeto Relation de Access (tal y
como se muestra en la gráfica) puede afectar en cierto modo la construcción de consultas de
varias tablas. En general la consulta resultante debe tener como origen principal de datos la
tabla que editaremos y no la tabla de la lista.
Los controles DataCombo y DataList tienen la capacidad de acceso a dos tablas diferentes y
vincular datos de la primera tabla a un campo de la segunda. Esto se lleva a cabo mediante
dos orígenes de datos, ya sea un control de datos ADO, un entorno de datos, o una clase de
reconocimiento de datos.
El problema con un control DataCombo se solucionaría así desde la ventana diseño:
Propiedad
DataSource
DataField
RowSource
BoundColumn
ListField
Valor
Origen a la Tabla Titles (un ADODC o un objeto de un DataEnvironment)
PubID
Origen a la Tabla Publishers (un ADODC o un objeto de un DataEnvironment)
PubID
Name
2
Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS
Tablas relacionadas y valores poco explícitos/Harvey Triana
Mayo/1999
Si empleamos una clase de reconocimiento de datos como origen de datos para el DataCombo
en vez de un control de datos ADO, usaremos una cantidad menor de recursos y una lectura
más rápida. Sin embargo esto se hace con código.
Finalmente, el formulario del ejemplo expuesto anteriormente puede lucir así:
Cuando el usuario selecciona un ítem de la lista Publishers, se actualiza el campo PubID de la
tabla Titles (clave externa). - Cabe mencionar que esto es solo un ejemplo, en una aplicación
real mostraríamos todos los campos (exceptuando claves) de la tabla Titles, y cada lista
relacionada que requiera.
Este diseño esta bien para una gestión sencilla. Ahora, la solución empleando una clase de
reconocimiento de datos facilitará esta implementación de manera sorprendente. Además, será
reutilizable para cada formulario que vaya a crear. El objetivo es poder crear tantas listas como
se requieran en una línea de código por cada lista, y usar el mínimo posible de recursos.
Además, y esto es de gran importancia, podemos facilitar un componente eficaz para que haga
parte de la capa intermedia en soluciones para el Web.
Solución para un formulario usando clases de reconocimientos
de datos
Primero que todo, no necesitará escribir una Clase para cada Lista que vaya a emplear. Basta
una clase y una colección que gestione las instancias.
Crearemos una Jerarquía de Clases en donde disponemos una clase padre:
RowSourceCollection y las subclases RowSource. Puede que Ud. sea un experto, pero
guiare la solución para cualquier nivel de programador.
1. Un proyecto EXE Estándar (posteriormente podemos aislar las partes y crear un
componente DLL).
Seleccionar Referencias del menú proyecto para agregar una referencia a la Biblioteca
Microsoft ActiveX Data Objetos 2.0 (no emplee la versión 2.1 si va utilizar el control
ADODC en la solución, ya que hay algunas incompatibilidades). Agregamos referencias a
los controles Microsoft DataList Control 6.0 y Microsoft ADO Data Control (OLEDB).
2. Una Jerarquía de Clases. Empezamos por el menú Complementos, opción Utilidad
Generador de Clases (si no tiene registrado el complemento, ubíquelo en Administrador de
Complementos). Botón: Agregar Nueva Colección, Nombre: RowSourceCollection,
Luego, usamos el Frame: Colección de nueva clase, Nombre de Nueva Clase:
RowSource. Finalmente Aceptar y Salir. Acepte actualizar el Proyecto. La siguiente
imagen muestra como se debe ver una jerarquía de clases (resaltado con una línea azul):
3
Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS
Tablas relacionadas y valores poco explícitos/Harvey Triana
Mayo/1999
3. Bien, ya esta creada la Jerarquía de clase. Ahora adicionaremos el código. Antes, vamos al
módulo de clase: RowSource para fijar esta propiedad: DataSourceBehavior:
vbDataSource (esto convierte la clase en origen de datos). Reemplace todo el código de
este módulo por el siguiente:
'//ROWSOURCE
'//Harvey T., 1999
'//Clase origen de datos para listas enlazadas
Option Explicit
Private rs As ADODB.Recordset
Private Sub Class_GetDataMember(DataMember As String, Data As Object)
Set Data = rs
End Sub
Public Sub Settings( _
sActiveConnection As String, _
sSource As String, _
sDataMember As String _
)
DataMembers.Add sDataMember
Set rs = New ADODB.Recordset
With rs
.ActiveConnection = sActiveConnection
.Source = sSource
.CursorType = adOpenForwardOnly
.CursorLocation = adUseClient
.Open
End With
End Sub
Public Property Get RecordCount() As Long
RecordCount = rs.RecordCount
End Property
Private Sub Class_Terminate()
Set rs = Nothing
End Sub
4
Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS
Tablas relacionadas y valores poco explícitos/Harvey Triana
Mayo/1999
Módulo de clase: RowSourceCollection. Reemplace todo el código por el siguiente:
'//ROWSOURCECOLLECTION
'//Gestiona las instancias de objetos RowSource
'//Harvey T., 1999
Option Explicit
Private m_Col As Collection
Public Function Add( _
ctlList As Control, _
sActiveConnection As String, _
sBoundColumn As String, _
sListField As String, _
sRowTable As String _
) As RowSource
Dim rws As RowSource
Dim SQL As String
On Error GoTo SubErr
'//Crea la consulta mínima de la lista
SQL = "SELECT [" & sBoundColumn & "],[" & sListField & "] " & _
"FROM [" & sRowTable & "] " & _
"ORDER BY [" & sListField & "];"
Set rws = New RowSource
'//sDataMember será sBoundColumn
rws.Settings sActiveConnection, SQL, sBoundColumn
'//sDataMember es la clave del ítem
m_Col.Add rws, sBoundColumn
With ctlList
.RowMember = sBoundColumn
.BoundColumn = sBoundColumn
.ListField = sListField
.Tag = CStr(rws.RecordCount)
Set .RowSource = rws
End With
Set Add = rws
Set rws = Nothing
Exit Function
SubErr:
MsgBox "Cannot create RowSource object for " & sBoundColumn & _
vbCrLf & vbCrLf & Err.Description
End Function
Public Property Get Item(vntIndexKey As Variant) As RowSource
Set Item = m_Col(vntIndexKey)
End Property
5
Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS
Tablas relacionadas y valores poco explícitos/Harvey Triana
Mayo/1999
Public Property Get Count() As Long
Count = m_Col.Count
End Property
Public Sub Remove(vntIndexKey As Variant)
m_Col.Remove vntIndexKey
End Sub
Public Property Get NewEnum() As IUnknown
Set NewEnum = m_Col.[_NewEnum]
End Property
Private Sub Class_Initialize()
Set m_Col = New Collection
End Sub
Private Sub Class_Terminate()
Set m_Col = Nothing
End Sub
4. Dibujamos y configuramos el formulario. El formulario de la primara figura de este articulo
sirve de guía. Para usar el ADODC le sugiero los siguientes pasos (palabras entre
corchetes cuadrados significan botones):
Clic-Derecho sobre el control ADODC
Propiedades
Usar Cadena de Conexión
[Generar]
OLE DB Provider(s) = MS Jet 3.51OLE DB Provider;
(NOTA. borre manualmente la línea: Persist Security Info=False; Esta línea me ha dado
problemas con las consultas, más no con los Queries. - Debe tratarse de algún Bug)
[Next]
Select database name: [...], buscar la ubicación de Biblio.MDB
[Test Connection]
[Aceptar]
Ficha: Origen de Datos
adCommandText
SQL:
SELECT * FROM Titles
[Aceptar].
Ventana Propiedades, señalar el control ADODC:
Nombre=adcTitles
Align=2-vbAlignBottom
Caption=Titles
Mode=3-adModeReadWrite
Ya está configurado el control de datos ADO. Ahora basta colocar los controles que
muestra la gráfica y dar sus propiedades de datos (similar a lo corriente con el control Data
de DAO). TextBox: Name=txt, Index=0, DataSource=adcTitles, DataField=Title. Control
DataCombo: Name=acb, Index=0, DataSource=adcTitles, DataField=PubID, Style=2dbcDropdownList.
La propiedad Estilo del DataCombo se fija a lista de sólo lectura (dbcDropdownList), dado
que la lista Publisher se muestra como una vista de la clave externa en nombres explicitos
y no es editable. Si se permite editar la lista se producen errores o se modifica la tabla de
referencia afectando los datos ya ingresados en la misma. Si desea que se editen o
6
Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS
Tablas relacionadas y valores poco explícitos/Harvey Triana
Mayo/1999
agreguen ítems a la lista lo haremos desde un comando externo a un formulario destinado
para esto. El caso lo mostraré más adelante.
5. Por último copiamos el código del formulario. Pegue esta sección:
'//LISTAS ENLAZADAS
'//Ejemplo del Articulo:
'//Tablas Relacionadas y Valores Poco Explícitos
'//FormADOPickList
'//Harvey T., 1999
Option Explicit
Private rsc As RowSourceCollection
Private Sub Form_Load()
Dim s As String
'//Instancia del objeto RowSourceCollection
Set rsc = New RowSourceCollection
'//Adicionando una Lista
s = adcTitles.Recordset.ActiveConnection
Call rsc.Add(acb(0), s, "PubID", "Name", "Publishers")
End Sub
Private Sub Form_Unload(Cancel As Integer)
Set rsc = Nothing
End Sub
Private Sub adcTitles_MoveComplete(ByVal adReason As ADODB.EventReasonEnum,
ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal
pRecordset As ADODB.Recordset)
'//Muestra Record i de n
With adcTitles.Recordset
If Not (.EOF Or .BOF) Then
adcTitles.Caption = "Title " & .AbsolutePosition & " of " &
.RecordCount
End If
End With
End Sub
Como puede observar, el código que gestiona la(s) lista es muy poco. El evento MoveComplete
se escribió para mostrar la posición del registro y el número de registros en la vista de datos.
Alcances de la Solución
Este simple ejemplo muestra una solución muy flexible al problema. Puede anexar tantas listas
como lo requiera el formulario, simplemente agregando ítems a objeto RowSourceCollection de
la siguiente línea:
rsc.Add NombreDeDataCombo, ActivateConnnetion, ClaveExterna, CampoEnLista, OrigenDeLista
7
Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS
Tablas relacionadas y valores poco explícitos/Harvey Triana
Mayo/1999
El ejemplo también enmarca un camino a soluciones más complejas, por ejemplo generación
de formularios de datos en tiempo de ejecución. En este caso el problema principal es obtener
los datos para los parámetros del método Add de objeto RowSourceCollection. En particular,
Access suministra una Ficha "Búsqueda" cuando diseñamos la estructura de una tabla. En la
ficha búsqueda se fijan unos parámetros que hacen la implementación de listas relacionas
automática en cualquier vista de datos con Access. Hacer esto con Visual Basic es viable dado
lo expuesto en este articulo, solo tendríamos que leer esas propiedades del la interfaz
requerida suministrada por el Proveedor. Sin embargo, las propiedades de objetos Field
obtenida con ADO para BDs Access no suministran la misma interfaz Field de Access, y esto si
que es un problema. Se podría intentar algo obteniendo Esquemas con ADO, pero esto
complica lo que era una solución sencilla. Yo opte por generar una tabla virtual que contiene la
información de todas las propiedades de listas enlazadas. Esta técnica me permite
universalizar la solución, sin importar el proveedor de datos (en estos momentos la aplica una
solución contra Oracle y otra contra Access). Esta extensión de la solución se aleja de los
propósitos de este articulo.
Solución para un DataGrid usando clases de reconocimientos
de datos
Para completar la aplicabilidad de la solución solo falta ponerla a trabajar para un grilla de
datos. La siguiente gráfica muestra la solución en producción:
Esta imagen muestra la aplicabilidad usando una Base de Datos que no es Biblio. Los botones
de la barra tienen las siguiente funciones: (1) Actualizar la celda con el ítem seleccionado de la
lista, (2) Editar la lista, y (3) Actualizar la lista desde su origen (Refresh).
El principal problema al utilizar una grilla de datos como DataGrid, en contraste con formularios
simples, es que se debe mostrar el campo explícito de la lista relacionada en una columna,
mientras que debemos ocultar la clave externa. Para mostrar las columnas con los valores
explícitos de la lista se debe construir una Consulta que contenga las tablas involucradas, una
consulta que usa la cláusula JOINT. Por supuesto, esto reviste ciertos conocimientos de SQL, y
la consulta suele variar; Por ejemplo cuando se usan relaciones con objetos Relation de
Access. La consulta para el ejemplo Titles/Publishers de Biblio es la siguiente:
SELECT Titles.Title, Titles.PubID, Publishers.Name
FROM Titles LEFT JOIN Publishers ON Titles.PubID = Publishers.PubID;
La cláusula LEFT JOIN impone a la tabla Titles como origen de datos. En general, para
construir este tipo de consultas es conveniente usar las QBE (Query By Example) de Access o
de cualquier DBMS. Visual Studio 6.0 también trae un constructor de consultas aparte.
8
Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS
Tablas relacionadas y valores poco explícitos/Harvey Triana
Mayo/1999
Por favor, mire la imagen anterior. La lista no es una lista convencional colgada a la celda de la
grilla sino un formulario. La razón por la solución Lista-Formulario tiene notables ventajas:
• Generalmente se desea editar la lista para arreglar o agregar ítems. Se dispone un
comando para que un usuario con los permisos pertinentes pueda hacerlo.
• Es deseable un comando para actualizar la lista. Esto es importante cuando se trabaja en
Red. Las listas por rendimiento se conservan en memoria estática, es decir, se usa un
cursor estático de solo lectura para leerla y desplegarla. Así, si otro usuario hace
modificaciones a la lista, esta se puede actualizar sin tener que cerrar la instancia de carga
de datos.
• Permite una mayor visibilidad y navegación por los ítems. Además, es más estética
(apreciación subjetiva)
• Puedo agregar más comandos al formulario de lista. Todos los formularios de datos se
verán beneficiados de los cambios. Esta solución empaqueta su código en un componente.
• Los controles necesarios para gestionar la lista son independientes del formulario que
contiene la grilla.
• Puedo usar la lista en otro contexto que no sea una grilla de datos.
• En teoría, se podrían hacer reutilizables las instancias de las listas, es decir los usuarios A,
B y C ven la misma lista. Para esto requiere un componente fuera de proceso, clases
basadas en conectores, cursores del lado del servidor, y una buena dosis de código. Esto
sería deseable en una solución para el Web (el ejemplo que suministro no considera esto).
Al aislar la implementación de listas, deja en libertad al programador de hacer muchas
variantes sin afectar la interfaz del usuario. En virtud de esto, en la solución aplicada a grillas
no use un DataList, sino un ListBox estándar. El control ListBox estándar es más ligero que
DataList y, de acuerdo a la capacidad programada, hará el mismo trabajo. El componente
también optimiza para que la cargue de la lista a petición, es decir la primera vez que un
usuario da clic en el botón de la celda, la lista se llena y despliega, subsecuentes llamadas solo
despliegan la lista. Esto permite una carga más ágil de formulario. También gestione la
reutilización del objeto Connection de ADO al pasarlo por referencia.
El código de ejemplo también incluye la clase DataGridFormat. En esta clase empaqueta
código necesario para dar algo de formato a la grilla. En realidad el control DataGrid (y su
antecesor DBGrid) son pobres en presentación y requieren de mucho código para dar una
interfaz mejorada al usuario.
Existen casos en donde la lista no actualiza una clave externa, es decir, la lista solo facilita al
usuario la elección de datos. En este caso, pase el parámetro BoundColumn de
RowSourceCollection.Add como sarta vacía (vbNullString).
El código de listas enlazadas para la grilla no lo expondré en este articulo, pues va más allá de
la simple implementación de listas y es algo extenso de explicar. Sin embargo los principios
son los mismos aplicados a partir de la técnica que expuse. En la descarga de archivos
encontrará el código aplicado al ejemplo Titles / Publishers.
En realidad estos módulos no representan una solución cien por cien completa. Es un buen
avance y cumple con lo expuesto en este articulo. A veces tenemos que adaptar los
componentes a los requerimientos de un aplicativo empresarial lo que los hará complejos y
poco didácticos. Por esta razón es conveniente simplificar para que otros se puedan beneficiar
de las ideas.
Sobre el ejemplo: Abrir el grupo de proyectos grpDataGridBrowser.vbg. Luego abrir el módulo
del formulario frmBiblioSample.frm, para especificar la trayectoria de la base de datos
Biblio.mdb en su PC, en la línea:
cnn.Open "Provider=Microsoft.Jet.OLEDB.3.51;" + _
"Data Source=SuTrayectoria/Biblio.mdb"
9
Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS
Descargar