ADO a Alto Nivel


ADO No Siempre es flexible, En especial cuando queremos tareas en un solo paso. No obstante, Un Componente de Capa Media puede hacer a ADO realmente simple. Ideas que evolucionan en ADO.NET

Por Harvey Triana

El acceso a datos a alto nivel fue una innovación de Visual Basic 3, y quizás fue eso lo que desde 1993 hizo popular este lenguaje: sencillez y eficacia. Todo empezó con los Objetos de Acceso a Datos, DAO, que son unos componentes de software diseñados y optimizados para bases de datos de Microsoft Access. Fue tan buena idea, que DAO pronto fue capacitado para poder desarrollar en entornos Cliente-Servidor (ODBCDirect), aunque no con el desempeño deseado, pero era algo. De aquí que se escribiera algo especializado para entornos Cliente-Servidor, que se denomino RDO, el cual tubo una corta vida debido a los avances tecnológicos detrás del software. Entonces la tecnología de componentes de Microsoft, basada en el modelo COM, necesitaba algo totalmente afín a su naturaleza para acceder a datos, entonces se escribió lo que hoy conocemos como ADO. La universalidad de ADO le da sus meritos. No obstante, ADO no es siempre flexible, en especial cuando queremos tareas en un solo paso. No obstante, un componente de capa media puede hacer a ADO realmente simple. Ideas que evolucionan en ADO.NET.

Clases y Más Clases

Hace ya varios años que Visual Basic dejo de ser un lenguaje simple, y un desarrollador Visual Basic que no sepa escribir clases vera muy sub-utilizada su capacidad. Los componentes de capa media se escriben en clases, y solo en clases. Los objetos que se escriben con Visual Basic son magia, puedes hacer tareas realmente complejas tan solo con hacer referencia a una DLL y usarla.

Simplificando ADO

Yo he simplificado mucho mi escritura de software de acceso a datos al hacer funciones reutilizables que encapsulan operaciones de ADO. Naturalmente estas funciones van a parar en un componente ActiveX que bautice ADOFunctions. Me explico, si escribimos una función como la siguiente:

Public Function StaticRecordset( _
       QueryName As String, _
       ParamArray Param() As Variant _
     ) As ADODB.Recordset

    Dim rs  As ADODB.Recordset
    Dim cmd As ADODB.Command
    Dim pmt As ADODB.Parameter
    Dim i   As Long

    On Error GoTo ErrHandler

    Set cmd = New ADODB.Command
    Set rs = New ADODB.Recordset

    With cmd
        .ActiveConnection = m_ConnectionString
        .CommandType = adCmdTable
        .CommandText = "[" & QueryName & "]"
         For Each pmt In .Parameters
             pmt.Value = Param(i)
             i = i + 1
         Next
    End With

    With rs
        .CursorLocation = adUseClient
        .Open cmd, , adOpenStatic
        Set cmd.ActiveConnection = Nothing
        Set cmd = Nothing
        Set .ActiveConnection = Nothing
    End With

    Set StaticRecordset = rs
    Exit Function

ErrHandler:
    Set StaticRecordset = Nothing
    Call ErrHandlerAction(Err)
End Function

No tendremos que volver a escribir el tedioso conjunto de instrucciones para abrir un Recordset de tipo Static, con (o sin) parámetros. ¿No es maravilloso?. Es decir si yo tengo un Query (un SQL creado en la base de datos) en la base de datos con el nombre sqlProveedores que usa un ID como parámetro, yo simplemente creo el Recordset en una sola línea ejecutable, p.e:

Dim rs As ADODB.Recordset  
Set rs = StaticRecordset(“sqlProveedores”, 12345)

Nótese que el parámetro Param() es un array opcional, con lo que extiende la función StaticRecordset a un numero variable de parámetros, incluyendo ausencia de ellos. 

Esta función también encapsula el manejo de errores, con lo que podríamos escribir:

Dim rs As ADODB.Recordset  
Set rs = StaticRecordset(“sqlProveedores”, 12345)  
If Not rs Is Nothing Then  
...  
End If

Es decir, no tengo que escribir una y otra vez On Error Goto. Simplemente sé que si StaticRecordset retornó Nothing, el Recordset fallo en su creación.

A estas alturas se estará preguntando: ¿Qué paso con la cadena de conexión?. La cadena de conexión simplemente entra como una propiedad del componente. Nótese que es una propiedad obligada, que se debe asignar inmediatamente después de crear el componente. Así, solo escribió una vez la cadena de conexión, es una clásico ejemplo de lo que debiera ser un parámetro de un procedimiento Constructor. Es una propiedad de la clase tal como escribimos cualquier propiedad:

‘//En Declaraciones:
Private m_ConnectionString As String  

Public Property Get ConnectionString() As String 
   
ConnectionString = m_ConnectionString  
End Property

Public Property Let ConnectionString(v As String) 
   
m_ConnectionString = v  
End Property

Como Visual Basic 4 a 6 no tiene capacidad de constructores, entonces escribimos un método que será llamado después de crear el componente. Lo que tiene los mismo resultados (mientras el programador siga las reglas). Por ejemplo:

Dim af As CADOFunctions  
Set af = New CADOFunctions  
Af.ConnectionString = sMiConexion ‘//Asumase como Contructor  
...

Nótese también que el código que maneja los errores dentro del componente es uno solo: llamamos al procedimiento ErrHandlerAction(Err). Esto es una nueva capa de simplificación.

Así como la función StaticRecordset, podemos escribir muchas funciones mas de acuerdo a nuestras necesidades. Presentaré otros dos ejemplos:

Public Function OpenSQLForwardOnly(sql As String) As ADODB.Recordset
    Dim rs As ADODB.Recordset

    On Error GoTo ErrHandler
    Set rs = New ADODB.Recordset
    rs.Open sql, m_ConnectionString, adOpenForwardOnly
    Set OpenSQLForwardOnly = rs
    Exit Function

ErrHandler:
    Set OpenSQLForwardOnly = Nothing
    Call ErrHandlerAction(Err)
End Function

La función OpenSQLForwardOnly requiere un SQL como parámetro. Así las llamadas serán de este tipo:

Dim rs As ADODB.Recordset  
Set rs = OpenSQLForwardOnly (“SELECT * FROM Proveedores WHERE ID=” & ID)  
If Not rs Is Nothing Then  
...  
End If

La siguiente función retorna un Recordset editable con parámetro de entrada un SQL:

Public Function GetEditRecordset(sql As String) As ADODB.Recordset
    Dim rs  As ADODB.Recordset

    On Error GoTo ErrHandler
    Set rs = New ADODB.Recordset
    rs.Open sql, m_ConnectionString, adOpenKeyset, adLockOptimistic
    Set GetEditRecordset = rs
    Exit Function

ErrHandler:
    Set GetEditRecordset = Nothing
    Call ErrHandlerAction(Err)
End Function

Un componentes como ADOFunctions también simplifica la escritura de componentes de capa media, ya que es general. Es decir cualquier aplicación con cualquier base de datos puede servirse del componente.

Valga aclara que ADOFunctions, no necesariamente tiene que ser un componente de capa media, es mas aun no tiene que ser un componente. Puede agregar la clase a su proyecto y utilizarla tal cual.

Uso del XML para manejar Recordsets Desconectados

Los Recordset desconectados se usan en aplicaciones Cliente-Servidor, tal como el Web. Entiéndase un Recordset desconectado aquel que no tiene referencias a la base de datos. Es decir, existe como un objeto que encapsula datos y su definición, y que no sabe nada de la base de datos que lo suministro. De aquí que el XML es una forma realmente eficaz de transmitir datos. Desde la versión 2.1 de ADO, podemos Salvar y Cargar conjuntos de datos en formato XML, que fueron estructurados por un Recordset fr ADO. El objeto Recordset entrega el siguiente par de propiedades para manejar datos en formato XML:

rs.Open XMLFile
y
rs.Save XMLFile, adPersistXML

Cuando archivamos un Recordset con Save, y luego lo recuperamos con Open, tanto la definición de datos como los datos son recuperados como por arte de magia, es decir se crea un Recordset en una línea, sin saber nada de la base de datos. Ya supondrá lo bueno que esto para el Web. Seguiré ampliando mi componente ADOFunctions con la siguiente función:

Public Function GetXMLRecordset( _
    ByVal XMLFile As String, _
    ByVal SQL As String) As ADODB.Recordset

    Dim rs As ADODB.Recordset

    On Error GoTo ErrHandler

    Set rs = New ADODB.Recordset

    XMLFile = App.Path & "\" & XMLFile
    If Len(Dir(XMLFile)) Then
       rs.Open XMLFile
    Else
       '//Create XML file
       rs.Open SQL, m_ConnectionString, adOpenStatic
       If Not rs Is Nothing Then
          rs.Save XMLFile, adPersistXML
       End If
    End If
    Set GetXMLRecordset = rs
    Exit Function

ErrHandler:
    Set GetXMLRecordset = Nothing
    Call ErrHandlerAction(Err)
End Function

La función GetXMLRecordset crea el archivo XML la primera vez que se invoca, luego lo recupera sin conectarse a la base de datos, lo que por supuesto es bastante ágil. Sin embargo, no sobre estime la funcionalidad de GetXMLRecordset, pues es útil solo para Recordsets que posiblemente no cambiarán con el tiempo (por ejemplo listas de selección constantes o Picklists), de otra manera, si los datos cambian seria necesario actualizar el archivo XML. Esto ultimo se puede hacer al agregar un parámetro que obligue a actualizar cuando sea pertinente (un Refresh).

Los métodos Open y Save pueden ser tremendamente utilices en programación para el Web. El modelo seria  el siguiente. Un formulario en una pagina  HTML que al ser enviado (SUBMIT), ejecutaría un Script que crea un Recordset (a partir de un archivo XML), y lo envía a servidor. Es decir sin conexión a la base de datos desde el cliente. Esto por supuesto es una de tantas técnicas (mas o menos son ideas que dieron origen a  RDS).

  Ideas Que Evolucionan En ADO.NET

ADO.NET es aquel producto de la reingeniería de ADO, diseñado para la plataforma .NET. Por supuesto es una evolución fuerte, y más si consideramos que ADO.NET esta basado en XML, lo que quiere decir “estoy hecho para Internet”.

No es mi intención profundizar en el tema, pues no corresponde a los propósitos de este articulo. No obstante, cabe mencionar algunas ideas evolucionan en ADO.NET, afines con este articulo. Por ejemplo me sorprende que ADO.NET suministra un método DataReader, el cual viene a ser el espejo de la función OpenSQLForwardOnly. El equivalente o pariente del Recordset es DataSet, un poderoso objeto que no solo maneja conjuntos de registros sino tambien relaciones. Es decir, se profundiza más en la funcionalidad de los objetos.

De otra parte, el tratamiento que da ADO.NET a los datos es siempre “desconectado”. Las actualizaciones, agregaciones y eliminaciones se hacen por tratamientos inteligentes de los objetos (DataSet). Esto se puede programar en ADO. ¿Cómo? – Hariamos un Open – Save a archivos XML, el reto de programacion lo presenta el actualizar la base de datos, - Aunque no es tan complicado como suena (si nos movemos en un conjunto de registros simple), solo eliminamos los datos del SQL y los reemplazamos por los nuevos en una Transaccion. Pero francamente, todo va a ser más facil con ADO.NET.