Apunte de Visual C++
Por: Demian Panello demianpanello@yahoo.com.ar
Capítulo XIV
Indice rápido del capítulo 14:
En principio se podría decir que ADO es el sucesor de DAO y RDO. Es la nueva interfaz orientada a objetos para el acceso a bases de datos. Como ADO encapsula la funcionalidad, (aunque no toda, por cierto), de DAO y RDO, se está convirtiendo en un estándar en la programación de bases de datos. No obstante el mismo Microsoft aconseja el uso de DAO para aplicaciones con bases de datos locales y recomienda a aquellos que usan RDO para conexiones con bases de datos de SQL Server y de Oracle, que utilicen ADO.
ADO funciona a través de un servidor OLE DB, o sea, es una interfaz entre el servidor OLE DB de los datos, (ejemplo Access, SQL Server, Oracle, etc.), y la aplicación.
ADO es un componente COM, (Component Object Model), que encapsula diversos objetos útiles al momento de manipular grandes bases de datos. (Se recomienda la lectura de artículos de MSDN sobre la arquitectura COM.).
Dificultades al usar ADO con C++:
En Visual Basic o en Java es muy transparente el uso de ADO y hasta diría simple. En cambio en Visual C++ no existe una clase que encapsule la funcionalidad de la librería de ADO, (por lo menos hasta donde yo sé, aún no he visto el Visual C++ .NET), lo que implica que la aplicación se debe "comunicar" directamente con el componente COM, (aunque puede usar la clase que escribió Carlos Antollini cuyos fuentes y documentación puede encontrar en la sección de artículos).
Pero salvado ese obstáculo, aún se encuentran contratiempos al momento de manipular los datos que al igual que DAO son devueltos por la funciones como valores de tipo VARIANT, (COleVariant o _variant_t). Además las cadenas que esperan ciertas funciones deben de tipo _bstr_t.
ADO en una aplicación Dialog Based:
Bien, vamos a hacer una aplicación Dialog Based sencilla que nos permita ver como se usa ADO en Visual C++, (lo más importante, la conexión a la base y creación de un recordset). Para esto vamos a usar una base de datos de Access(*) llamada "base_empleados.mdb" que consta de una tabla "Empleados" con los campos "Apellido", "Nombre", "Salario" e "ID"..
Cree una aplicación Dialog Based de nombre "ado_emp", (en realidad puede poner el nombre que quiera) y diseñe el diálogo más o menos como se ve en la siguiente imagen:

(*)la base puede ser creada con cualquier versión de Access ya que todas soportan ADO, no pasa lo mismo con DAO, que a apartir de Access 2000 no es reconocido, (salvo que se guarde la base de datos como una versión anterior).
Especifique las siguientes propiedades y cree variables asociadas a los controles de acuerdo a lo indicado en la siguiente tabla:
| Control | ID | Propiedades | Variable asociada |
|---|---|---|---|
| CListBox | IDC_LISTAPE | por defecto | CListBox m_lstApe |
| CEdit | IDC_NOMBRE | Read Only | CString m_Nombre |
| CEdit | IDC_SALARIO | Read Only | CString m_Salario |
| CEdit | IDC_ID | Read Only | CString m_ID |
| CButton | IDC_INFO | por defecto | ninguna. |
| CButton | IDC_SALIR | por defecto | ninguna. |
La idea es que cuando se cargue el diálogo, (OnInitDialog()), deberemos llenar la lista con los apellidos de la tabla, luego al seleccionar un apellido de la lista mostrar los restantes campos en los cuadros de edición, (muy parecido a lo hecho en el capítulo anterior con DAO).
Importando la librería de ADO:
Para trabajar con ADO hay que importar el archivo DLL correspondiente: msado15.dll con la directiva #import, (en realidad existen tres formas de acceder a ADO, ver Knowledge Base de MSDN Q174565). Un buen lugar para ubicar esta sentencia es en el archivo de cabecera del diálogo,ado_empDlg.h, allí luego de la línea #endif escriba:
#import "c:\Archivos de programa\Archivos comunes\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
El path puede variar si su versión de Windows está en inglés, (ni que decir si esta en islandés o mandarín... en fin : ).
En esta sentencia, además de indicar la importación de funciones de esa librería, estamos renombrando la propiedad EOF por adoEOF ya que de lo contrario el compilador nos arrojaría errores.
Es necesario inicializar OLE antes de establecer la conexión, (por que se trata de objetos COM), para eso usaremos la función CoInitialise() pasándole como parámetro NULL. Escriba en el constructor del diálogo:
CoInitialize(NULL);
Con ADO en lugar de abrir directamente la base de datos, se realiza primero una conexión, para eso agregue una variable al diálogo, (en la clase del diálogo), de tipo _ConnectionPtr.
_ConnectionPtr pAdoCone;
Este es un objeto de tipo Connection y nos permitirá conectar nuestra base de datos con la aplicación.
Agregue al diálogo una función que retorne int de nombre Conectar y escriba allí:
int CAdo_empDlg::Conectar()
{
//Objeto _bstr_t con la cadena para la conexión.
_bstr_t bstrCone(L"Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=base_empleados.mdb;"); (1)
try
{
//Se crea una instancia para la
conexión
pAdoCone.CreateInstance (__uuidof(Connection));
(2)
//se establece la
conexión
pAdoCone->Open (bstrCone ,_bstr_t (
"" ), _bstr_t ( "" ), adModeUnknown ); (3)
return 1;
}
catch (_com_error& ce) (4)
{
//si ocurrió una
excepción, se llama a la función que mostrará el mensaje de error
ver_com_error(ce); (5)
return 0;
}
}
En (1) se declarar una cadena de tipo _bstr_t, la esperada por las funciones de ADO, cuyo fin es especificar información como Provider que en nuestro ejemplo es Microsoft.Jet.OLEDB.4.0 ya que pretendemos usar una base de Access, (si fuera una base de datos de SQL Server deberíamos escribir Provider=SQLOLEDB), y Data Source que hace mención a la fuente de datos, nuestro archivo.
Luego, dentro de una estructura try-catch, (altamente recomendada), en (2) creamos una instancia de la conexión, (quienes ya han usado ADO en Visual Basic o ASP esta línea equivale a Set pAdoCone = New Connection) y luego abrimos la conexión en (3) con la función Open() cuyo formato es:
Open(BSTR ConnectionString, BSTR UserID, BSTR Password, long Options)
ConnectioString: La cadena para la conexión que armamos en (1).
UserID y Password: El ID del usuario y el password. En nuestro ejemplo ejemplo pasamos cadenas nulas siempre convirtiendola al tipo esperado, _bstr_t.
Options: Una constante que especifica un modo de conexión:
| Constante | Descripción |
| adModeUnknown | Predeterminada. Indica que los permisos no se han establecido aún o que no se pueden determinar. |
| adModeRead | Indica que son permisos de sólo lectura. |
| adModeWrite | Indica que son permisos de sólo escritura. |
| adModeReadWrite | Indica que son permisos de lectura/escritura. |
| adModeShareDenyRead | Impide que otros abran una conexión con permisos de lectura. |
| adModeShareDenyWrite | Impide que otros abran una conexión con permisos de escritura. |
| adModeShareExclusive | Impide que otros abran una conexión. |
| adModeShareDenyNone | Impide que otros abran una conexión con cualquier tipo de permiso. |
Ahora, bien, de no existir ningún error, (excepción), la función, (nuestra función Conectar), retorna 1. Si ocurre una excepción el flujo del programa salta a catch (4) donde lo que se verifica es un error de COM, _com_error. Los datos de la excepción se almacenarán en la variable ce de tipo _com_error, la cual manipulamos en una función que deberemos crear llamada ver_com_error. (5)
Entonces agregue al diálogo la función ver_com_error(_com_error &e) con retorno void y escriba en ella:
void CAdo_empDlg::ver_com_error(_com_error &e)
{
CString msgErr; //aquí se almacenará el mensaje
de error
_bstr_t bstrOrigen= e.Source(); //origen del error
_bstr_t bstrDescri=e.Description(); //texto del error
msgErr.Format ("ADO - COM Error\n\tCódigo = %08lx\n\tOrigen =
%s\n\tDescripción = %s\n",
e.Error(),(LPCSTR)bstrOrigen, (LPCSTR)bstrDescri);
AfxMessageBox( msgErr, MB_OK | MB_ICONERROR );
}
El objeto _com_error e trae información sobre la excepción ocurrida. La función Source() devuelve una cadena que indica el origen del error y Description() una breve descripción del mismo.
Se formatea un objeto CString con estos datos más el código de error, función Error(), que luego se muestra con AfxMessageBox().
Ahora, coloque en OnInitDialog() la llamada a Conectar() de la siguiente forma:
//Llamo a la función conectar
if (!Conectar())
{
AfxMessageBox("No se pudo realizar la conexión.");
EndDialog(0);
}
El siguiente paso es escribir una función que cargue la lista de apellidos con todos los apellidos de la tabla, (en este ejemplo se supone que no se repite ninguno). Entonces agregue otra función al diálogo, que retorne int, llamada CargaLista() y escriba:
int CAdo_empDlg::CargaLista()
{
CString msgErr;
try{
//Se crea una instancia del
recordset
_RecordsetPtr Rs(__uuidof(Recordset));
(1) (ver nota)
//se le asocia la conexión
Rs->PutRefActiveConnection(pAdoCone); (2)
//armo una cadena de tipo
_bstr_t con la sentencia SQL necesaria
_bstr_t bstrSql(L"Select apellido
from Empleados Order By apellido"); (3)
//abro el recordset
Rs->Open(bstrSql,vtMissing,adOpenForwardOnly,adLockReadOnly,adCmdText); (4)
CString strApe;
while(!Rs->adoEOF)
//mientras no sea
fin de archivo...
{
//obtengo el valor del
campo apellido
strApe =
(char*) (_bstr_t) Rs->Fields->GetItem("Apellido")->Value; (5)
//lo agrego a la lista
m_lstApe.AddString (strApe);
//paso al siguiente
registro
Rs->MoveNext();
}
//cierro y libero el
recordset
Rs->Close();
Rs.Release();
return 1;
}
catch (_com_error& ce)
{
ver_com_error(ce);
return 0;
}
}
Como debe ser, las sentencias de ADO deben estar controladas en una estructura try-catch(_com_error), y entonces podemos en (1) crear una instancia del objetos recordset, _RecordsetPtr, (en VB equivale a Set Rs = new Recordset) y en (2) le indicamos que pertenece a nuestra conexión pAdoCone.
En (3) armamos la cadena SQL que tomará los datos requeridos, o sea todos los apellidos ordenados alfabéticamente y en (4) abrimos el recordset.
Luego, mientras no sea fin de archivo, tomamos en (5) el contenido del campo Apellido convirtiéndolo, primero en _bstr_t y después en char*. Siempre que un campo sea de tipo cadena debe aplicar estas dos conversiones.
En caso de encontrar un error en ejecución, el flujo "salta" a la sección catch, la cual llama a la función ver_com_error() que se encarga de mostrar un mensaje aclaratorio.
Nota: En (1) puede ver la palabra clave _uuidof() que se encarga de devolver el GUID, (Globally Unique IDentifier), de una expresión, en este caso el objeto Recordset. En pocas palabras, el compilador le asocia un GUID a una clase declarada o definida con el atributo uuid.
__declspec( uuid(ComObjectGUID) ) declarator
El atributo uuid recibe una cadena como parámetro, la cual se trata de un GUID. Por ejemplo:
struct __declspec(uuid("00000000-0000-0000-c000-000000000046"))
IUnknown;
struct __declspec(uuid("{00020400-0000-0000-c000-000000000046}"))
IDispatch;
Esto se aplica en particular a los objetos COM para que el sistema pueda suministrar las definiciones de interfaces como IUnknown. (ver tema sobre objetos COM en MSDN).
Si prueba el programa, verá como se carga la lista con los apellidos. Nos falta escribir código para que cuando se seleccione un apellido de la lista, se muestren los restantes campos en los cuadros de edición. Para ésto agregue el mensaje OnSelChangeListApe(), (recuerde que ésto se hace por medio del Class Wizard).
En este mensaje escriba:
void CAdo_empDlg::OnSelchangeListape()
{
CString apeSel; //Acá
almaceno el nombre seleccionado
//cadena sql sin completar. Falta concatenarle el
apellido seleccionado entre ' '
char strSql[255] = "Select * from
Empleados where apellido= ' ";
//tomo el apellido seleccionado
m_lstApe.GetText(m_lstApe.GetCurSel(),apeSel);
//le coloco una comilla al final
apeSel.Insert (apeSel.GetLength (), "'"); //entre comillas dobles hay una '
//termino de armar la cadena SQL
strcat(strSql, apeSel);
try{
//declaro el objeto
recordset
_RecordsetPtr Rs(__uuidof(Recordset));
//lo asocio a la
conexión activa
Rs->PutRefActiveConnection(pAdoCone);
_bstr_t bstrSql(strSql); //ahora la cadena SQL es de tipo _bstr_t
//abro el recordset
Rs->Open(bstrSql,vtMissing,adOpenForwardOnly,adLockReadOnly,adCmdText);
//tomo el nombre
m_Nombre = (char*)
(_bstr_t) Rs->Fields->GetItem("Nombre")->Value;
//v es una variable variant,
(podría haber sido un objeto COleVariant),
//para el contenido de los campos.
_variant_t v;
v=Rs->Fields->GetItem("Salario")->Value; //obtengo el salario
m_Salario.Format ("%.2f",v.fltVal);
//lo coloco en el edit
v=Rs->Fields->GetItem("ID")->Value;
//obtengo el ID
m_ID.Format("%i", v.intVal);
//lo
coloco en el edit
//actualizo los controles
UpdateData(FALSE);
Rs->Close();
Rs.Release();
}
catch (_com_error& ce)
{
ver_com_error(ce); //llamo
a la función que se encarga de mostrar el error
}
}
Bueno, este código está exhaustivamente comentado, línea X línea, así que sería redundante explicar todo otra vez, simplemente es importante remarcar la diferencia que hay al obtener el contenido de un campo de tipo cadena y uno numérico. Para los numéricos se usa una variable de tipo _variant_t, o un objeto COleVariant como en el capítulo XIII, (también lo puede hacer para las cadenas), y luego se obtiene el valor por medio de la propiedad que identifica el tipo en concreto, por ejemplo fltVal si es float o intVal si es entero, etc.
Por último escribiremos el código que permita, al pulsar el botón que dice "Ver connection string", mostrar un mensaje con el contenido por defecto de la cadena de conexión usada para conectar la base. Hay más parámetros que los que especificamos al momento de conectar la base, con este botón podremos ver el contenido por defecto de los restantes. Escriba entonces:
void CAdo_empDlg::OnInfo()
{
//Muestro un mensaje con la cadena completa de la conexión
MessageBox(pAdoCone->GetConnectionString() ,"Información de la conexión",
MB_ICONINFORMATION);
}
Simplemente se hace uso de la función GetConnectionString() del objeto Connection.

Resumiendo:
ADO es un conjunto de objetos escritos según la arquitectura COM almacenados en una librería. Para poder usar ADO hay que importar dicha librería:
#import "c:\Archivos de programa\Archivos comunes\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
En ADO se crean conexiones a bases de datos o fuentes de datos ODBC. Por esto hay que crear un objeto de tipo _ConnectionPtr que luego es utilizado para abrir la conexión con la función Open() del mismo objeto, que recibe 3 parámetros: La cadena de conexión, el ID del usuario y opciones de conexión.
Una vez creada la conexión, se puede en cualquier momento obtener información de las tablas almacenadas en la base, por medio de un objeto Recordset o Command, (este último no ha sido visto en este capítulo). Es necesario crear un objeto de tipo _RecordsetPtr y luego usar la función Open() del mismo para obtener los datos.
Todos los campos son devueltos por funciones del objeto RecordsetPtr como tipo _variant_t.
Siempre hay que escribir las sentencias de ADO entre estructuras TRY-CATCH que controlen la excepción _com_error.
Descargar archivos fuente del ejemplo: ado_emp.zip. (77 Kb).