DOSUG CZ– розовая кнопка на сайте!
Logo

Изучаем работу с ADO (ActiveX Data Objects)

Для работы с данными ваше приложение может использовать различные технологии. Это Open DataBase Connectivity (ODBC), Data Access Objects (DAO), Remote Data Objects (RDO), ActiveX Data Objects (ADO) и Object Linking and Embedding DataBase (OLE DB). На выбор конкретной технологии больше всего влияния оказывают такие факторы, как функциональность, легкость программирования, удобство сопровождения и производительность. Иногда бывает важно получить доступ к данным "аккуратно", другой раз на первом месте стоит скорость работы с данными. Вопросы удобства и простоты развертывания и сопровождения, думаю, очевидны. Также нужно принимать во внимание возможность работы с одними и теми же данными нескольких пользователей - система должна поддерживать несколько подключений к базе данных без заметного снижения произоводительности. Вкратце охарактеризуем самые распространенные технологии доступа к данным:

Open DataBase Connectivity (ODBC): это API для доступа к реляционным базам данных. ODBC используется для подключения к БД, выполнения операторов SQL и получения результирующих наборов данных. ODBC позволяет приложениям "общаться" с различными системами управления базами данных (СУБД), используя один и тот же программный код.

Data Access Object (DAO): технология доступа к данным, разработанная для использования преимущественно с базами данных Jet, например, Microsoft Access или БД ISAM, такими как dBase, FoxPro и т.д.

Remote Data Objects (RDO): тонкая надстройка над ODBC, обеспечивающая соединение с БД, создание результирующих наборов данных, курсоров и выполнения хранимых процедур. Технология RDO была создана для работы с такими СУБД, как, например, SQL Server и Oracle.

OLE DB: сегодня приложения работают на различных платформах - настольных, мэйнфреймах или в интернете. Данные, с которыми приходится работать этим приложениям, могут находится в различных хранилищах данных - электронные таблицы, персональные базы данных и т.д. Такие технологии, как ODBC, DAO или RDO неспособны извлекать данные из любых источников. Для решения этой проблемы компания Microsoft разработала универсальную стратегию доступа к данным - Universal Data Access Strategy (UDA). Эта стратегия основана на COM. OLE DB - набор COM-интерфейсов, разработанных на основе концепций UDA, реализующих различные сервисы реальных хранилищ данных. Технология предназначена для доступа к реляционным и нереляционным источникам данных, включая мэйнфреймовые ISAM базы данных, системы электронной почты, текстовые файлы и т.д. - все зависит от местоположения данных.

Архитектура OLE DB состоит из потребителей данных (Data Consumers), провайдеров данных (Data Providers) и серверных компонент (Server Components). Потребитель - программный компонент, использующий интерфейс OLE DB. Среды разработки, такие как Power Builder, Delphi и языки программирования типа Visual Basic, Visual C++ - примеры потребителей.

Провайдер - программный компонент, реализующий интерфейс OLE DB. Провайдер может быть быть как провайдером данных (Data Provider) так и провайдером сервисов (Service Provider). Провайдер данных - программный компонент, представляющий свои данные в табличной форме, называемой набором строк (rowsets). СУБД, базы данных - примеры провайдеров данных. Этот тип провайдера является владельцем данных. Провайдер сервисов - компонент, который реализует сервисы ("обработчики"), такие, например, как обработчик (процессор) запросов. Провайдеры сервисов не владеют данными.

ActiveX Data Objects (ADO): технология DAO оптимизирован для работы с базами данных Microsoft Access. RDO создано для доступа к СУБД типа SQL Server или Oracle с использованием ODBC. ActiveX Data Objects (ADO) - наследник технологий DAO и RDO. ADO объединяет все лучшее из DAO и RDO. ADO использует технологию OLE DB для доступа к данным из любого источника.

OLE DB - очень мощный интерфейс для доступа и манипуляции с данными, однако программирование напрямую через OLE DB очень сложно. Вот поэтому и появилось ADO. ADO реализует высокоуровневый, объектно-ориентированный интерфейс к OLE DB.

Объектная модель ADO основана на трех типах объектов - подключение (Connection), команда (Command) и набор данных (Recordset). Объект Connection хранит информацию о подключении к источнику данных, такую как имя, местоположение данных, имя пользователя и пароль, имя провайдера OLE DB и т.д. Объект типа Command используется для выполнения операторов SQL, хранимых процедур и т.д. Объект Recordset хранит результаты выполнения запросов. Далее приводятся типичные алгоритмы работы с этими тремя видами объектов:

Для подключения к источнику данных нужно:

  • Объявить указатель на объект типа Connection

    ConnectionPtr p ;

  • Создать объект Connection

    p.CreateInstance ( __uuiof ( Connection ) ) ;

  • Открыть источник данных

    p->Open ( data provider, data source name, user name, password ) ;

  • По окончании работы с данными закрыть соединение

    p->Close( ) ;

Для создания объекта типа Command нужно:

  • Объявить указатель на объект типа Command

    _CommandPtr p1 ;

  • Создать объект типа Command

    p1.CreateInstance ( __uuidof ( Command ) ) ;

  • Сформулировать текст запроса

    p1->CommandText = " …. Actual command …"

  • Указать тип команды

    p1->CommandType = adCmdText ;

    CommandType - свойство, которое может принимать предопределенные значения, такие как AdCmdTable, AdCmdStoredProc, AdCmdUnknown.

  • Выполнить команду

    p1 -> Execute( ) ;

Для использования объекта типа Recordset нужно:

  • Объявить указатель на объект типа Recordset

    _RecordsetPtr p2 ;

  • Создаем объект Recordset

    p2.CeateInstance ( __uuidof ( recordset ) ) ;

  • Сформулировать запрос

    p2 -> CommandText = "… Actual command …." ;

  • Выполнить запрос и получить результирующий набор данных

    p2->Execute( ) ;

  • Закрыть Recordset
    p2->Close( ) ;

Объект Recordset содержит реализацию методов, позволяющих пользователю обновлять данные в базе данных и перемещать указатель текущей записи по результирующему набору. Также этот объект содержит поля, хранящие число записей в наборе, тип курсора, тип блокировки и т.д.

В этой статье приводится детальное описание создания программы для манипуляций (добавления, изменения, удаления) со строками таблицы БД Microsoft Access через ADO. Таблица состоит из трех полей, названных Account, Name и Balance (код счета, имя владельца и сумма денег на счете).

Процесс создания компонент в ATL состоит из трех этапов:

(a) Создание модуля:

          Для создания модуля в Developer Studio существует мастер ATL COM AppWizard.

  • Выберите пункт меню File -> New.
  • В качестве проекта выберите ‘ATL COM AppWizard’. Введите имя проекта - ‘AdoServer’ и нажмите ‘OK’.
  • Выберите тип модуля ‘Dynamic Link Library’, нажмите ‘Finish’.

          (b) Добавление компонента к модулю

Для добавления компонента воспользуемся мастером ‘ATL Object Wizard’. Для достижения цели придерживаемся следующих шагов:

  • Выберите пункт меню ‘Insert | New ATL Object’. На экране появится мастер ‘ATL Object Wizard’
  • Выберите категорию объекта - ‘Simple Object’ и нажмите ‘Next’.
  • На экране появится диалог ‘ATL Object Wizard Properties’.
  • В качестве краткого имени (Short Name) наберите ‘Customer’. Все остальные поля заполнятся автоматически. Нажмите OK.

          (c) Добавление методов компонента

  • Компонент, созданный только что созданный нами, не содержит никакой функциональности. Для придания объекту функциональности определим пять методов, AddRecord( ), UpdateRecord( ), DeleteRecord( ), Getrsetbyid( ) и Getrsetbysort( ). Перед этим добавим две функции в заголовочный файл ‘Customer.h’:

HRESULT FinalConstruct( )
{
    HRESULT hr ;
    CoInitialize ( NULL ) ;
    hr = m_pconnection.CreateInstance( __uuidof ( Connection ) ) ;
    hr =m_pconnection->Open(_T("Provider=  Microsoft.Jet.OLEDB.3.51; Data        
                                                Source=d:\\table1.mdb"),"","",adOpenUnspecified ) ;
    return S_OK ;
}

HRESULT FinalRelease( )
{
    CoUninitialize( ) ;
    HRESULT hr = m_pconnection->Close( ) ;
}

Функция FinalConstruct( ) вызывается перед созданием объекта проектируемого нами компонента. В этой функции происходит инициализация библиотеки COM, создается объект типа Connection и открывается источник данных. В функции FinalRelease( ) мы делаем обратное: библиотека COM деинициализируется и закрывается соединение с источником данных. Для работы этих двух функций в класс CCustomer, в раздел private необходимо добавить переменную m_pconnection типа _ConnectionPtr.

  • Переключитесь на закладку определения класса (Class View tab). Выберите интерфейс ICustomer и нажмите правую кнопку мыши. Из появившегося меню выберите ‘Add Method’.
  • В появившемся диалоге ‘Add Method to Interface’ укажите имя метода ‘AddRecord’ и заполните поле ввода параметров следующей строкой:

[in] int id, [in] BSTR name, [in] int balance

  • Нажмите ‘OK’.
  • Подобным образом добавьте остальные методы в файл IDL.

HRESULT DeleteRecord ( [in] int id ) ;
HRESULT UpdateRecord ( [in] int id, [in] BSTR name, [in] int balance ) ;
HRESULT Getrsetbyid ( [in] int accno, [out,retval] IDispatch ** p ) ;
HRESULT Getrsetbysort ( [in] int no, [out,retval] IDispatch ** p );

  • Добавление метода AddRecord( ) генерирует описание следующей функции в файле ‘Customer.cpp’:

STDMETHODIMP CCustomer::AddRecord ( int id, BSTR name, int balance)
{
       // TODO: Add your implementation code here
       return S_OK ;
}

  • Добавьте следующий фрагмент кода в метод AddRecord( ):

_RecordsetPtr recset ;
HRESULT hr ;
CString query ;
query.Format ( "SELECT * FROM bank WHERE id IS NULL" ) ;
CComVariant vNull ;
vNull.vt = VT_ERROR ;
vNull.scode = DISP_E_PARAMNOTFOUND ;
hr = recset.CreateInstance ( __uuidof ( Recordset ) ) ;
if ( SUCCEEDED ( hr ) )
{
    recset -> PutRefActiveConnection ( m_pconnection ) ;
    hr = recset -> Open ( query.operator LPCTSTR( ), vNull,
    adOpenForwardOnly, adLockOptimistic, adCmdText );
    if ( SUCCEEDED ( hr ) )
    {
        COleSafeArray fieldlist ;
        fieldlist.CreateOneDim ( VT_VARIANT, 3 ) ;
        long arrayindex[3] = { 0, 1, 2 } ;
        CComVariant f1 ( "id" ) ;
        CComVariant f2 ( "Name" ) ;
        CComVariant f3 ( "Balance" ) ;
        fieldlist.PutElement ( &arrayindex[0], &f1 ) ;
        fieldlist.PutElement ( &arrayindex[1], &f2 ) ;
        fieldlist.PutElement ( &arrayindex[2], &f3 ) ;
        COleSafeArray valuelist ;
        valuelist.CreateOneDim ( VT_VARIANT, 3 ) ;
        CComVariant v1 ( id ) ;
        CComVariant v2 ( name ) ;
        CComVariant v3 ( balance ) ;
        valuelist.PutElement ( &arrayindex[0], &v1 ) ;
        valuelist.PutElement ( &arrayindex[1], &v2 ) ;
        valuelist.PutElement ( &arrayindex[2], &v3 ) ;
        recset -> AddNew ( fieldlist, valuelist ) ;
        recset -> Close( ) ;
    }
}

Здесь мы создаем объект Recordset, подключаем его к уже созданному объекту Connection (это делается вызовом функции _RecordSet::PutRefActiveConnection( )) и добавляем запись вызовом функции AddNew( ). Список полей и список значений этих полей, передаваемые в метод AddNew( ), описаны как массивы значений типа CComVariant.

  • Метод DeleteRecord( ) реализуется следующим образом

STDMETHODIMP CCustomer::DeleteRecord ( int id )
{
    AFX_MANAGE_STATE ( AfxGetStaticModuleState( ) )
    _RecordsetPtr recset ;
    HRESULT hr ;
    CString query ;

    query.Format ( "SELECT * FROM bank WHERE id = %d",id ) ;
    CComVariant vNull ;
    vNull.vt = VT_ERROR ;
    vNull.scode = DISP_E_PARAMNOTFOUND ;
    hr = recset.CreateInstance ( _uuidof ( Recordset ) ) ;
    if ( SUCCEEDED ( hr ) )
    {
        recset->PutRefActiveConnection ( m_pconnection ) ;
        hr = recset -> Open ( query.operator LPCTSTR( ), vNull,
        adOpenForwardOnly, adLockOptimistic, adCmdText );
        if ( !recset -> GetadoEOF( ) )
        {
            recset->Delete ( adAffectCurrent ) ;
            recset->Close( ) ;
        }
    }
    return S_OK ;
}

Функция аналогична AddRecord( ), за исключением строки запроса и того, что вместо метода AddNew( ) мы вызываем метод _RecordSet::Delete( ).

  • Теперь добавим реализацию метода UpdateRecord( ), содержащего следующий код:

STDMETHODIMP CCustomer::UpdateRecord ( int id, BSTR name, int balance )
{
    AFX_MANAGE_STATE ( AfxGetStaticModuleState( ) )
    _RecordsetPtr recset ;
    HRESULT hr ;
    CString query ;

    query.Format ( "SELECT * FROM bank WHERE id = %d", id ) ;
    CComVariant vNull ;
    vNull.vt = VT_ERROR ;
    vNull.scode = DISP_E_PARAMNOTFOUND ;
    hr = recset.CreateInstance ( __uuidof ( Recordset ) ) ;
    if ( SUCCEEDED ( hr ) )
    {
        recset -> PutRefActiveConnection ( m_pconnection ) ;
        hr = recset -> Open ( query.operator LPCTSTR( ), vNull,
        adOpenForwardOnly, adLockOptimistic, adCmdText );
      if ( ! recset -> GetadoEOF( ) )
      {
            CComVariant f1 ( name ) ;
            CComVariant f2 ( balance ) ;
            recset -> PutCollect ( L"Name", &f1 ) ;
            recset -> PutCollect ( L"Balance",&f2 ) ;
           recset -> Update ( vNull, vNull ) ;
            recset -> Close( ) ;
        }
    }
    return S_OK ;
}

  • Теперь нам нужен метод, который будет искать в таблице указанную запись. Вот как он может выглядеть:

STDMETHODIMP Ccustomer :: Getrsetbyid ( int accno,IDispatch **p )
{
    AFX_MANAGE_STATE ( AfxGetStaticModuleState( ) )
    _RecordsetPtr recset ;
   CComVariant v ( 0L ) ;
   CString query ;

    query.Format ( "SELECT * FROM bank where id = %d",accno );
    recset = m_pconnection -> Execute ( query.operator LPCTSTR( ), &v,  
                                               adOptionUnspecified ) ;
    *p = ( IDispatch * ) recset ;
    recset -> AddRef( ) ;
    return S_OK ;
}

Так как мы не меняем извлеченную запись, запрос выполняется несколько по-другому, чем в предыдущих случаях. Мы просто получаем набор записей вызовом метода _Connection::Execute( ). Заметим, что в данном случае необходимо вызвать метод AddRef( ), т.к. мы знаем, что есть клиент, использующий полученный набор записей. Если бы мы этого не сделали, набор записей был бы уничтожен еще до его использования клиентским приложением. Итак, мы подошли к реализации последнего метода, позволяющего отсортировать набор записей, получаемый из источника данных. Вот исходный код этого метода:


STDMETHODIMP CCustomer::Getrsetbysort ( int no, IDispatch **p )
{
    AFX_MANAGE_STATE ( AfxGetStaticModuleState( ) )
    _RecordsetPtr recset ;
    CString query ;
    CComVariant v ( 0L ) ;

    if ( no == 0 )
        query.Format ( "SELECT * FROM bank order by id" ) ;
    else
        query.Format ( "SELECT * FROM bank order by Name" ) ;
    recset = m_pconnection -> Execute (
    query.operator LPCTSTR( ), &v, adOptionUnspecified ) ;
    *p = ( Idispatch * ) recset ;
    recset->AddRef( ) ;
    return S_OK;
}

Эта функция подобна Getrsetbyid( ), работа которой была была объяснена чуть выше. Здесь мы получаем различным образом отстортированный нобор записей в зависимости от переданного в функцию идентификатора.

  • В файл ‘stdafx.h’ необходимо добавить следующие операторы:

#include <comdef.h>
#import "C:\Program Files\CommonFiles\System\ado\msado15.dll"
no_namespace rename ( "EOF", "adoEOF" )

Теперь полученный компонент можно откомпилировать. Посмотрим, как использовать компонент на клиентской стороне. В настоящий момент мы знаем, как строятся компоненты ADO, получающие записи из источника данных (т.е. "серверные" компоненты). Напишем реализацию клиента, использующего методы, которые мы только что описали.

Для того, чтобы написать COM-клиента с использованием MFC, нужно проделать следующее:

  • Создать исполняемое диалоговое приложение с помощью AppWizard.
  • Добавить элементы управления, например, как показано на рисунке 1.

 
Рисунок 1.

В дополнение к набору элементов, показанных на рисунке 1 добавьте элемент просмотра списка (list view). Этот список будет заполняться, если пользователь нажмет кнопку ‘List’.

  • Используя ClassWizard добавьте три перменные m_id, m_name и m_bal типа integer, string и integer соответственно - они будут содержать значения полей ввода.
  • Аналогично создайте переменные m_list и m_rad типа CListCtrl и CButton для элемента списка и радиокнопки.
  • Добавьте переменные m_hide, m_commit и m_search, все типа CButton, для кнопок ‘Hide’, ‘Commit’ и ‘Search’ соответственно.
  • Также на закладке Class view создайте следующие переменные в заголовочный файл ‘AdoClientDlg.h’:

int m_oper ;
CEdit *name, *acc, *bal ;

  • Импортируйте библиотеку типов (Type Library) сервера в клиентское приложение. Это делается занесением следующих строк в файл ‘StdAfx.h’.

# include <comdef.h>
# include "atlbase.h"
# import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename ( "EOF", "adoEOF" )
# import "..\AdoServer\AdoServer.tlb" using namespace ADOSERVERLib ;

  • Добавьте указатель на интерфейс cust типа ICustomer * в файл ‘CAdoClientDlg.h’.
  • В функцию инициализации диалога OnInitDialog( ) добавьте следующие код:

BOOL CAdoClientDlg::OnInitDialog( )
{
    // AppWizard generated code
    // TODO: Add extra initialization here
    CoInitialize ( NULL ) ;
    CLSID clsid ;
    HRESULT hr ;
    hr = CLSIDFromProgID( OLESTR ("AdoServer.Customer" ), &clsid ) ;
    hr = CoCreateInstance ( clsid, NULL, CLSCTX_ALL, __uuidof ( ICustomer ), (void **) &cust ) ;
    acc = ( CEdit * ) GetDlgItem ( IDC_EDIT1 ) ;
    name = ( CEdit * ) GetDlgItem ( IDC_EDIT2 ) ;
    bal = ( CEdit* ) GetDlgItem ( IDC_EDIT3) ;

    // insert columns in the list view control
    m_list.InsertColumn ( 0,"ID",LVCFMT_LEFT,100 ) ;
    m_list.InsertColumn ( 1,"Name",LVCFMT_LEFT,100 ) ;
    m_list.InsertColumn ( 2,"Balance",LVCFMT_LEFT,100 ) ;

    // Don’t show the list view control to begin with
    SetWindowPos ( &wndTop, 50,50,400,235,SWP_SHOWWINDOW ) ;
}

  • Создайте обработчики для шести кнопок (Add, Delete, Update, Commit, List и Hide):

void CAdoClientDlg::OnAdd( )
{
    acc -> EnableWindow ( TRUE ) ;
    name -> EnableWindow ( TRUE ) ;
    bal -> EnableWindow ( TRUE ) ;
    m_commit.EnableWindow ( TRUE ) ;
    m_oper = 1 ;
}

void CAdoClientDlg::OnDelete( )
{
    acc -> EnableWindow ( TRUE ) ;
    m_search.EnableWindow ( TRUE ) ;
    m_oper = 3 ;
}

void CAdoClientDlg::OnUpdate( )
{
    acc -> EnableWindow ( TRUE ) ;
    m_search.EnableWindow ( TRUE ) ;
    m_oper = 2 ;
}

void CAdoClientDlg::OnList( )
{
    SetWindowPos ( &wndTop, 50, 50, 400, 390,
    SWP_SHOWWINDOW ) ;
    m_hide.ShowWindow ( SW_SHOW ) ;
    m_list.DeleteAllItems( ) ;
    UpdateData ( TRUE ) ;
    CComVariant custaccno ;
    CComVariant custname ;
    CComVariant custbal ;
    CString name,acc,bal ;
    int i = 0 ;
    _RecordsetPtr recset ;

    recset = ( _RecordsetPtr ) cust -> Getrsetbysort ( m_rad1 ) ;
    while ( !recset -> adoEOF )
    {
        custaccno = recset -> GetCollect ( L"id" ) ;
        acc.Format ( "%d", custaccno.iVal ) ;
        m_list.InsertItem ( i, acc, i ) ;
        custname = recset -> GetCollect ( L"Name" ) ;
        name = custname.bstrVal ;
        m_list.SetItemText ( i, 1, name ) ;
        custbal = recset -> GetCollect ( L"Balance" ) ;
        bal.Format ( "%d", custbal.iVal ) ;
        m_list.SetItemText ( i, 2, bal ) ;
        recset -> MoveNext( ) ;
        i++ ;
    }
    recset->Close( ) ;
}

void CAdoClientDlg::OnCommit( )
{
    UpdateData ( TRUE ) ;
    switch ( m_oper )
    {
        case 1:
            cust -> AddRecord ( m_id, _bstr_t ( m_name ), m_bal ) ;
            break ;
        case 2:
            cust -> UpdateRecord ( m_id, _bstr_t ( m_name ), m_bal ) ;
            break ;
        case 3:
            cust -> DeleteRecord ( m_id ) ;
            break ;
    }
    m_id = 0 ;
    m_name = "" ;
    m_bal = 0 ;
    acc -> EnableWindow ( FALSE ) ;
    name -> EnableWindow ( FALSE ) ;
    bal -> EnableWindow ( FALSE ) ;
    m_commit.EnableWindow ( FALSE ) ;
    m_search.EnableWindow ( FALSE ) ;
    UpdateData ( FALSE ) ;
}

void CAdoClientDlg::OnSearch( )
{
    UpdateData(TRUE) ;
    CComVariant custaccno ;
    CComVariant custname ;
    CComVariant custbal ;
    _RecordsetPtr recset ;

    recset = ( _RecordsetPtr ) cust -> Getrsetbyid ( m_id ) ;
    if ( ! recset -> adoEOF )
    {
        custaccno = recset -> GetCollect ( L"id" ) ;
        m_id = custaccno.iVal ;
        custname = recset -> GetCollect ( L"Name" ) ;
        m_name = custname.bstrVal ;
        custbal = recset -> GetCollect ( L"Balance" ) ;
        m_bal = custbal.iVal ;
        recset -> Close( ) ;
        UpdateData ( FALSE ) ;
        name -> EnableWindow ( TRUE ) ;
        bal -> EnableWindow ( TRUE ) ;
        m_commit.EnableWindow ( TRUE ) ;
    }
    else
        MessageBox ( "Record not found" ) ;
}

void CAdoClientDlg::Onhide( )
{
    SetWindowPos(&wndTop,50,50,400,230,SWP_SHOWWINDOW) ;
    m_hide.ShowWindow(SW_HIDE) ;
}

  • Деинициализируйте библиотеку COM, вызвав CoUninitialize( ) в конце функции InitInstance( ):

BOOL CAdoClientApp::InitInstance( )
{
    // wizard generate code
    CoUninitialize( ) ;
    return FALSE;
}

На этом заканчивается написание клиентской части приложения. Теперь можно откомпилировать и запустить на выполнение клиента и проверить на практике, как происходит его взаимодействие с серверным компонентом ADO.

Удачи в программировании...

главная - о проекте - контакты - реклама на сайте
 
LBN100 Elite

SoftStudio.Ru - студия разработки программ
LBN100 Elite