Практикум прикладного программирования на MFC и C++ в среде VS.NET

Поддержка баз данных с помощью API ODBC

Разбить на страницы
Показывать лекцию целиком
Файлы к данной лабораторной работе, Вы можете скачать здесь.

Некоторые определения

  • DB ( Data Base ) - база данных, представляющая собой совокупность взаимосвязанных таблиц, каждая из которых, как правило, хранится в отдельном файле. Имя базы данных обычно совпадает с именем каталога, где хранятся данные
  • ODBC ( Open Database Connectivity ) - открытое соединение с базами данных
  • DBMS ( Database Management System ) - система управления базами данных
  • SDK ( Software Development Kit ) - комплект разработки программного обеспечения
  • API ( Application Programming Interface ) - интерфейс прикладных программ. Функции API ODBC обеспечивают взаимодействие при помощи синтаксиса SQL с любыми форматами данных, для которых существует соответствующий драйвер ODBC
  • SQL ( Structured Query Language ) - язык структурированных запросов
  • ODBC Driver - драйвер взаимодействия с данными определенной структуры. Среда разработки Visual C++ .NET предоставляет готовые к употреблению драйвера ODBC, предназначенные для работы с такими СУБД, как
  • SQL Server
  • Microsoft Access
  • Microsoft FoxPro
  • Microsoft Excel
  • dBASE
  • Paradox
  • Текстовые файлы
  • ODBC Driver Manager - диспетчер драйверов ODBC (файл ODBC32.DLL )
  • ODBC Cursor Library - библиотека курсоров ODBC (файл ODBCCR32.DLL )
  • Data Source - источник данных, означает логическое представление в виде таблицы конкретного файла с данными
  • DSN ( Data Source Name ) - имя (алиас, псевдоним) источника данных, зарегистрированное диспетчером драйверов, под которым ODBC будет обеспечивать подключение к данным нужного драйвера и в котором будет указан путь размещения таблиц базы данных
  • Result Set - результирующий набор данных, полученный после выполнения SQL -запроса к базе данных
  • Connection - подключение к базе данных
  • ODBC Data Source Administrator - специальная утилита (вспомогательная, служебная программа) корпорации Microsoft для регистрации источника данных
  • Для обеспечения определенного уровня программной абстракции ODBC обеспечивает взаимодействие с конкретным форматом данных через драйвер ODBC, поставляемый разработчиком соответствующего формата DB. Чтобы ODBC поддерживало соответствующий формат данных, разработан перечень требований, который должен обеспечить разработчик драйвера (поставщик соответствующих данных). Этот перечень требований называется уровнем согласования.

    Драйвер может поддерживать и большее число функций, но базовый уровень согласования должен быть выдержан, если разработчик драйвера хочет, чтобы его данные поддерживались приложениями на основе ODBC.

    Когда приложение использует функции базового уровня согласования, то в проверке возможностей драйвера нет необходимости. Но если приложение базы данных хочет обратиться к функции ODBC, не принадлежащей базовому уровню согласования, то оно должно предварительно удостовериться, что драйвер эту функцию поддерживает. Для выяснения возможности драйвера поддерживать какую-то функцию в ODBC существует специальная тестовая функция SQLGetFunctions(). Аналогичные требования согласования существуют и для синтаксиса SQL.

    Все функции ODBC можно разделить на логические группы, соответствующие порядку, в котором их применяют при создании приложений:

  • Настройка ODBC
  • Подключение к источнику данных
  • Получение информации о возможностях драйвера ODBC
  • Подготовка к выполнению SQL -запросов
  • SQL -запросы на получение выборки данных
  • Получение данных
  • Отключение от источника данных
  • Настройка ODBC

    Настройка ODBC включает в себя действия, которые нужно выполнить для нормальной работы приложения. Прежде всего нужно запустить системную утилиту ODBC Data Source Administrator. Она запускается из командной строки Пуск/Выполнить операционной системы командой odbcad32

    В результате появится окно администратора

    Окно администратора имеет несколько вкладок, в том числе соответствующие типу DSN, поддерживаемому ODBC

  • User DSN - пользовательский источник данных. Является локальным для того компьютера, на котором он был создан, и может применяться только создавшим его пользователем
  • System DSN - системный источник данных. Принадлежит тому компьютеру, на котором создан. Любой обладающий достаточными правами пользователь может обращаться к этому источнику данных
  • File DSN - файловый источник данных. Обеспечивает доступ к конкретному файлу базы данных
  • Каждая вкладка делает свою запись в системном реестре, например

    Чтобы создать DSN для источника данных, поддерживаемого драйвером ODBC, следует указать всю информацию, необходимую для его работы. Большинство драйверов ODBC требуют, как минимум, имени физического файла с данными и путем доступа к файлу.

    Иными словами, с помощью утилиты ODBC Data Source Administrator мы устанавливаем ассоциативную связь между файлом с данными конкретной структуры и соответствующим драйвером, понимающим такой формат данных. Этой связи мы присваиваем имя ( DSN ), которое затем должны использовать в своем приложении для работы с этими данными, но уже с помощью стандартных функций ODBC. Таким образом достигается отделение (абстракция) функций обработки данных от конкретной структуры данных. Кроме этого, в DSN содержится информация местонахождения файлов-таблиц с данными. Перенеся приложение и данные в другое место или компьютер, достаточно установить настройки соответствующего DSN и приложение будет нормально работать!!!

    Подключение к источнику данных

    После настройки DSN с помощью утилиты ODBC Data Source Administrator теперь вместо конкретного файла с данными у нас есть именованная абстракция данные-драйвер, зарегистрированная в операционной системе и поэтому готовая к поддержке ее механизмом ODBC. Все готово для того, чтобы к базе данных можно было обратиться с помощью функций API ODBC как к абстрактным данным, которые теперь можно назвать данными ODBC.

    Первым этапом программного доступа к данным ODBC является подключение к источнику данных. Для этого механизм ODBC требует выполнить последовательность действий, часть из которых является подготовительной:

  • Прежде всего следует инициализировать сам механизм ODBC и получить уникальный дескриптор среды ( unique environment handle ) henv. Этот дескриптор уникально идентифицирует в операционной системе поток, который будет использовать механизм ODBC. Следующий фрагмент кода резервирует в системе поток для ODBC
    SQLHENV henv;
    SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_ENV,
                                  SQL_NULL_HANDLE,
                                  henv);
  • Получив дескриптор среды, следует установить версию ODBC, с которой совместимо приложение
    SQLSetEnvAttr(henv,
                  SQL_ATTR_ODBC_VERSION,
                  (SQLPOINTER) SQL_OV_ODBC3,
                  SQL_IS_INTEGER);
  • первый параметр указывает дескриптор потока
  • второй указывает необходимость регистрирования версии
  • третий устанавливает, что приложение будет поддерживать ODBC версии 3.0
  • Прежде, чем приложение сможет подключиться к источнику данных, оно должно получить от ODBC дескриптор подключения к базе данных ( data connection handle ) hdbc
    SQLHDBC hdbc;
    SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_DBC,
                                  henv,
                                  hdbc);
  • Получив дескриптор hdbc, можно, наконец, подключиться к источнику данных, использовав любую из трех функций ODBC:
  • SQLConnect() - самый простой способ подключения к источнику данных. Ей достаточно передать DSN, идентификатор пользователя и пароль для подключения к источнику данных ODBC
  • SQLDriverConnect() - альтернативный вариант для драйверов ODBC. Она требует передачи более подробной информации, чем функция SQLConnect()
  • SQLBrowseConnect() - это средство подключения к источнику данных, обладающее наибольшими возможностями. При помощи одного или нескольких обращений к этой функции можно запросить и получить всю информацию, необходимую для подключения к источнику данных. Для обеспечения такого подхода функция возвращает значение SQL_NEEDS_DATA до тех пор, пока приложение не представит всю требуемую информацию, и только после этого она возвратит значение SQL_SUCCESS
  • Вот первый, наиболее простой, вариант использования функции подключения

    SQLRETURN rc = SQLConnect(hdbc, // Дескриптор подключения
              (SQLCHAR*) "MyBase", SQL_NTS, // DSN
              (SQLCHAR*) "", SQL_NTS, // Идентификатор пользователя
              (SQLCHAR*) "", SQL_NTS); // Пароль для подключения
    bool bIsConnected = TRUE; // Подняли флаг, что соединение существует

    Аргумент SQL_NTS сообщает ODBC о том, что предыдущий переданный аргумент представляет собой строку с завершающим нулевым символом.

    Получение информации о возможностях драйвера ODBC

    Одной из наиболее существенных особенностей ODBC является то, что он позволяет запросить информацию о возможностях драйвера ODBC. Для этого используются API ODBC -функции SQLGetFunctions(), SQLGetInfo(), SQLGetTypeInfo(). Рассмотрим их...

  • SQLGetFunctions() - позволяет запросить драйвер ODBC о том, поддерживает ли он определенные функции или группы функций. В качестве аргументов необходимо передать дескриптор hdbc и числовое значение, идентифицирующее определенную функцию или группу функций API ODBC. Эти значения определены в файле заголовка sqlext.h, который устанавливается вместе с Microsoft SQL Server, SDK ODBC или Visual Studio .NET. Приведенный ниже фрагмент кода выясняет, поддерживает ли драйвер, ассоциированный с указанным DSN, API ODBC -функции SQLTables() и SQLColumns()
    SQLUSMALLINT bSQLTablesFunctionExists; // Объявили логическую величину
    SQLUSMALLINT bSQLColumnsFunctionExists; // Объявили логическую величину
    SQLGetFunctions(hdbc,
                    SQL_API_SQLTABLES,
                    bSQLTablesFunctionExists);
    SQLGetFunctions(hdbc,
                    SQL_API_SQLCOLUMNS,
                    bSQLColumnsFunctionExists);
    if(bSQLTablesFunctionExists
        bSQLColumnsFunctionExists){
       // Обе функции существуют
    }
  • Если проверяемый драйвер поддерживает базовый уровень согласования, то необходимости в приведенной проверке функциональности драйвера нет. Если только мы не используем самопальный или экзотический формат данных, а придерживаемся структуры данных наиболее распространенных типов, то можно быть уверенным, что разработчики этих форматов обеспечили базовый уровень согласования.

  • SQLGetInfo() - возвращает информацию о драйвере ODBC и его возможностях. Например, с ее помощью можно выяснить версию драйвера ODBC, его название, а также уровни согласования API и SQL
  • SQLGetTypeInfo() - возвращает информацию о том, какие типы данных поддерживаются применяемым источником данных. Драйвер возвращает информацию в формате результирующего набора данных ( result set )
  • Подготовка к выполнению SQL-запросов

    Последнее, что приложение должно сделать после подключения к источнику данных, но прежде, чем оно будет способно осуществлять запросы SQL, - это получить дескриптор оператора ( statement handle ) или hstmt. Чтобы получить дескриптор hstmt, достаточно выполнить такой код

    SQLSTMT hstmt;
    SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_STMT,
                                  hdbc,
                                  hstmt);

    Как только приложение получит дескриптор hstmt, оно сможет передавать источнику данных операторы SQL.

    SQL-запросы на получение выборки данных

    Существуют два разных способа организации выполнения источником данных запроса SQL.

    Способ 1

    LPCSTR szSQL = "INSERT INTO MyTable "
                   "VALUES('UserID', 'USER NAME', 0)";
    SQLRETURN rc;         
    rc = ::SQLExecDirect(hstmt, (unsigned char*)szSQL, SQL_NTS);

    Данная функция добавит в таблицу MyTable базы данных MyBase новую строку данных со значениями полей ("UserID", "USER NAME", 0)

    Способ 2

    Здесь подразумевается предварительная подготовка SQL -запроса при помощи функции SQLPrepare() и последующее его выполнение при помощи функции SQLExecute().

    LPCSTR szSQL = "INSERT INTO MyTable "
                   "VALUES('UserID', 'USER NAME', 0)";
    SQLRETURN rc;      
    rc = ::SQLPrepare(hstmt, (unsigned char*)szSQL, SQL_NTS);   
    if(rc != SQL_SUCCESS){
       // Плохо! Сообщить пользователю или принять решение! 
       // Дальше не идти!
    }  
                
    rc = ::SQLExecute(hstmt);   
    if(rc != SQL_SUCCESS){
       // Плохо! Сообщить пользователю или принять решение! 
       // Дальше не идти!
    }

    Второй способ иногда может быть эффективней потому, что выражение SQL будет откомпилировано один раз, а затем может многократно использоваться для выполнения одного и того же запроса.

    Получение данных

    При выполнении SQL -запроса на получение данных приложению возвращается результирующий набор данных ( result set ), из которого приложение должно выбирать данные и передавать их в переменные для обработки и представления пользователю. Для решения этой задачи предусмотрен ряд API ODBC -функций, таких как

  • SQLNumResultCols() - определяет количество и тип полей возвращенных данных в результирующем наборе
  • SQLGetData() - извлекает значение определенного поля
  • SQLBindCol() - ассоциирует переменные приложения со столбцами базы данных
  • SQLFetch() - передает данные из набора в переменные и передвигает курсор результирующего набора данных на следующую запись
  • Эти функции позволяют разработчику создавать приложения баз данных, не заботясь заранее о том, сколько полей и какого типа будет возвращать источник данных в составе результирующего набора данных.

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

    Отключение от источника данных

    Как только подключение к источнику данных станет ненужным, от него необходимо отключиться при помощи функции SQLDisconnect(), а также освободить все дескрипторы, которые были для этого созданы. Освобождение дескрипторов должно выполняться в порядке, обратном их созданию. Соответствующий код может выглядеть так

    if(henv){
      if(hdbc){
        if(bIsConnected){
          if(hstmt){
            ::SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
          }
          ::SQLDisconnect(hdbc);
          bIsConnected = FALSE;
        }
        ::SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
        hdbc = NULL;
      }
      ::SQLFreeHandle(SQL_HANDLE_ENV, henv);
      henv = NULL;
    }

    Настройки мастера создания приложения для автоматической поддержки ODBC

    Правильная настройка мастера создания приложений с поддержкой ODBC позволяет сгенерировать значительное количество кода автоматически. Кратко рассмотрим некоторые параметры мастера.

    Нужно создать новый проект на основе MFC

    Установить тип документа SDI или MDI

    Вкладка Database Support

    None (Отсутствует) - мастер не будет создавать никакого кода, имеющего отношение к базам данных.

    Header files only (Только файлы заголовка) - сделает доступным переключатель, позволяющий выбрать применяемую приложением технологию доступа к базе данных. Выбор параметра ODBC приведет к включению мастером в состав проекта заголовка AFXDB.H и добавит в проект все необходимые подключаемые баблиотеки. Выбор параметра OLE DB приведет к подключению таких файлов заголовка, как ATLBASE.H, AFXOLEDB.H, ATLPLUS.H.

    Database view without file support (Представление базы данных без файловой поддержки) - приведет к включению в состав приложения соответствующих заголовочных файлов, подключаемых библиотек, представления записей и класса набора записей. Через кнопку Data Source (Источник данных) можно выбрать ODBC DSN или OLEDB Provider, а также используемую таблицу или представление. Параметры Dynaset (Динамический набор) или Snapshot (Моментальная выборка) позволяют выбирать тип логического курсора для результирующего набора данных. Параметр Bind all columns (Связать все поля) - включает поддержку механизма обмена данными RFX ( Record Field Exchange - обмен данными с полями записи) между переменными представления и полями базы. Это аналог DDX для баз данных.

    Database view with file support (Представление базы данных с файловой поддержкой) - обеспечивает способность полученному коду сохранять документ.

    Создание проекта API ODBC

    Если выбрать тип диалогового приложения, то мастер делает все настройки поддержки ODBC недоступными. Однако можно включить эти поддержки в приложение вручную, что мы и сделаем ниже, выбрав тип диалогового приложения для лабораторной работы.

    Создание DSN ODBC

  • Скопируйте из каталога Pictures данной лабораторной работы файл MyTable.dbf в свой каталог
  • Запустите системную утилиту ODBC Data Source Administrator. Она запускается из командной строки Пуск/Выполнить операционной системы командой odbcad32
  • Установите вкладку User DSN и нажмите кнопку Add
  • Выберите драйвер как показано на рисунке
  • Нажмите кнопку Готово и в появившемся окне

    заполните имя источника данных своим именем, например, MyBase. Установите версию dBase IV

  • Нажмите кнопку Выбор каталога и через диалоговое окно выберите каталог, где будут размещены файлы с таблицами данных
  • В нашем случае используется одна таблица с данными MyTable.dbf, которую можно найти в каталоге размещения этого описания.

  • Предполагая, что Вы уже скопировали или еще скопируете файл MyTable.dbf в свой каталог, установите в качестве размещения базы данных свой каталог
  • Окно выбора каталога размещения базы данных с примерными настройками показано на рисунке.

  • Нажимайте кнопку OK вплоть до закрытия окна администратора
  • Создание заготовки проекта диалогового приложения

  • Создайте новый проект на основе
  • Настройте вкладки мастера так
  • Нажмите кнопку Finish и мастер сгенерирует диалоговое приложение
  • Добавление в проект поддержки ODBC

    Оболочка Studio .NET при создании диалогового приложения не поддерживает установки мастера на обеспечение ODBC. Поэтому поддержку ODBC необходимо включить вручную. Чтобы приложение могло воспользоваться SDK ODBC, в него необходимо включить соответствующие файлы заголовка и подключаемые библиотеки. Существуют два способа решения этой задачи

    Способ 1.

    В сгенерированный мастером файл API_ODBCDlg.cpp ( в начало ) или в файл stdafx.h ( в конец ) неоходимо включить заголовочные файлы

    #include <sql.h> 
    #include <sqlext.h>
    #include <sqltypes.h>

    В проект приложения необходимо добавить библиотеку импорта ODBC (odbc32.lib) . Для этого в меню Project нужно выбрать пункт меню Add Existing Item (Добавить существующий элемент) и найти необходимую библиотеку импорта odbc32.lib. В конфигурации Visual Studio .NET этот файл находится в каталоге Lib, как показано на рисунке

    Если выбрать этот файл, то он автоматически будет добавлен в проект и появится во вкладке Solution Explorer данного проекта

    Способ 2.

    В сгенерированный мастером файл API_ODBCDlg.cpp ( в начало ) или в файл stdafx.h ( в конец ) необходимо включить заголовочный файл

    #include <afxdb.h>

    Этот файл подключает необходимые файлы заголовка Windows и содержит директиву #pragma comment, что обеспечивает включение необходимых библиотек импорта ODBC в состав приложения.

  • Включите в конец файла stdafx.h директиву, приведенную во втором способе так
    Включение директивы поддержки ODBC в файл stdafx.h
    // stdafx.h : include file for standard system include files,
    // or project specific include files that are used frequently,
    // but are changed infrequently
      
    #pragma once
    .....................................................
      
    #include <afxdb.h>
  • Конструирование главного диалогового окна приложения

  • Раскройте вкладку Resource View и русифицируйте ресурсы
  • IDD_ABOUTBOX
  • Откройте ресурс IDD_API_ODBC_DIALOG в режиме дизайна, удалите все элементы управления и добавьте следующие элементы управления из панели Toolbox
  • List Box - 1 шт.
  • Static Text - 3 шт.
  • Edit Control - 3 шт.
  • Включите панель инструментов
  • Измените идентификаторы списка и полей ввода и создайте для этих элементов управления переменные-члены Ассоциированные переменные для элементов управления ID Категория Тип Имя Доступ IDC_LISTBOX Control CListBox m_ListBox public IDC_NUMEDIT Value int m_NumEdit public IDC_NAMEEDIT Value CString m_NameEdit public IDC_YEAREDIT Value int m_YearEdit public
  • Настройки мастера приведены на рисунках

    Мастер внесет добавления в объявление класса

    Добавления в файл API_ODBCDlg.h
    // CAPI_ODBCDlg dialog
    class CAPI_ODBCDlg : public CDialog
    {
    ..................................................
    public:
      // Переменная управления списком
      CListBox m_ListBox;
      // Переменная поля ввода № п/п
      int m_NumEdit;
      // Переменная поля ввода ФИО
      CString m_NameEdit;
      // Переменная поля ввода Год
      int m_YearEdit;
    };

    Мастер внесет добавления в определение класса

    Добавления в файл API_ODBCDlg.cpp
    // CAPI_ODBCDlg dialog
      
    CAPI_ODBCDlg::CAPI_ODBCDlg(CWnd* pParent /*=NULL*/)
      : CDialog(CAPI_ODBCDlg::IDD, pParent)
      , m_NumEdit(0)
      , m_NameEdit(_T(""))
      , m_YearEdit(0)
    {
      m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    }
      
    void CAPI_ODBCDlg::DoDataExchange(CDataExchange* pDX)
    {
      CDialog::DoDataExchange(pDX);
      DDX_Control(pDX, IDC_LISTBOX, m_ListBox);
      DDX_Text(pDX, IDC_NUMEDIT, m_NumEdit);
      DDX_Text(pDX, IDC_NAMEEDIT, m_NameEdit);
      DDX_Text(pDX, IDC_YEAREDIT, m_YearEdit);
    }
      
    BEGIN_MESSAGE_MAP(CAPI_ODBCDlg, CDialog)
      ON_WM_SYSCOMMAND()
      ON_WM_PAINT()
      ON_WM_QUERYDRAGICON()
      //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
      
    ........................................................

    Код инициализации ODBC и взаимодействие с базой данных

    Разработаем код, который получает данные из базы данных и заполняет ими список. Код упакуем в функцию FillDbToListBox().

  • Добавьте в класс CAPI_ODBCDlg объявление функции
    Добавление в файл API_ODBCDlg.h
    class CAPI_ODBCDlg : public CDialog
    {
    ................................................
    public:
      // Переменная управления списком
      CListBox m_ListBox;
      // Переменная поля ввода № п/п
      int m_NumEdit;
      // Переменная поля ввода ФИО
      CString m_NameEdit;
      // Переменная поля ввода Год
      int m_YearEdit;
      // Функция заполнения списка данными DB
      void FillDbToListBox(void);
    };
    Добавление в файл API_ODBCDlg.cpp
    
    // Функция заполнения списка данными DB
    void CAPI_ODBCDlg::FillDbToListBox(void)
    {
    }
  • Добавьте в начало файла API_ODBCDlg.cpp следующее определение структуры
    Добавление структуры в файл API_ODBCDlg.cpp
    // API_ODBCDlg.cpp : implementation file
    //
      
    #include "stdafx.h"
    #include "API_ODBC.h"
    #include "API_ODBCDlg.h"
    #include ".\api_odbcdlg.h"
      
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
      
    struct RECORD
    {
      int recNumEdit;// Переменная поля ввода № п/п
      CString recNameEdit;// Переменная поля ввода ФИО
      int recYearEdit;// Переменная поля ввода Год
    };
    .......................................................
  • Добавьте после определения структуры RECORD следующую директиву препроцессора
    Добавление директивы препроцессора в файл API_ODBCDlg.cpp
    ......................................................
    struct RECORD
    {
      int recNumEdit;// Переменная поля ввода № п/п
      CString recNameEdit;// Переменная поля ввода ФИО
      int recYearEdit;// Переменная поля ввода Год
    };
    
    #define LEN_BUF 31 // Длина буфера
    .......................................................
  • Найдите функцию OnInitDialog(), сгенерированную мастером, и непосредственно перед оператором return добавьте вызов функции FillDbToListBox(). Эта функция заполнит список на последнем этапе инициализации диалогового окна
    Добавление вызова в файл API_ODBCDlg.cpp
    BOOL CAPI_ODBCDlg::OnInitDialog()
    {
      CDialog::OnInitDialog();
    .............................................................
      
      // TODO: Add extra initialization here
      FillDbToListBox();
      
      return TRUE;  // return TRUE  unless you set the focus to a control
    }
  • Наполните функцию FillDbToListBox() следующим кодом, реализующим то, о чем было сказано выше
    Функция FillDbToListBox() в файле API_ODBCDlg.cpp
    // Функция заполнения списка данными DB
    void CAPI_ODBCDlg::FillDbToListBox(void)
    {
    // Объявления дескрипторов и вспомогательных переменных
      SQLHENV henv = NULL;
      SQLHDBC hdbc = NULL;
      SQLHSTMT hstmt = NULL;
      bool bIsConnected = false;
      SQLRETURN rc;
      
    // Инициализация механизма ODBC среды
      rc = SQLAllocHandle(SQL_HANDLE_ENV,
                          SQL_NULL_HANDLE,
                          henv);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу инициализировать ODBC",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Выбор версии ODBC-3
      rc = SQLSetEnvAttr(henv,
            SQL_ATTR_ODBC_VERSION,
            (SQLPOINTER) SQL_OV_ODBC3,
            SQL_IS_INTEGER);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу установить версию ODBC",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Получить от ODBC дескриптор подключения к базе данных
      rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, hdbc);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу получить дескриптор подключения к БД",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Подключить приложение к источнику данных
      rc = SQLConnect(hdbc, // Дескриптор подключения
              (SQLCHAR*) "MyBase", SQL_NTS, // DSN
              (SQLCHAR*) "", SQL_NTS, // Идентификатор пользователя
              (SQLCHAR*) "", SQL_NTS); // Пароль для подключения
      if(rc != SQL_SUCCESS  rc != SQL_SUCCESS_WITH_INFO){
        MessageBox("Не могу подключиться к источнику данных",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      bIsConnected = true; // Подняли флаг, что соединение существует
      
    // Получить дескриптор оператора (statement handle)
    // для возможности передачи SQL-запросов 
      rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, hstmt);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу получить дескриптор оператора",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Подготовить и выполнить SQL-запрос (имя таблицы можно без расширения)
      LPCSTR szSQL = "SELECT number, name, birth FROM MyTable.dbf";
      rc = SQLExecDirect(hstmt, (unsigned char*)szSQL, SQL_NTS);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу выполнить SQL-запрос к БД",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Связывание полей с переменными приложения
      long int cb; // Вспомогательная типа SDWORD
      int iNumber; 
      char strName[LEN_BUF];
      int iBirth; 
      SQLBindCol(hstmt, 1, SQL_INTEGER, iNumber, 0, cb);// Field1
      SQLBindCol(hstmt, 2, SQL_CHAR, strName, LEN_BUF, cb);// Field2
      SQLBindCol(hstmt, 3, SQL_INTEGER, iBirth, 0, cb);// Field3
      
    // Заполнение списка
      rc = SQLFetch(hstmt);
      while(rc == SQL_SUCCESS){
        RECORD* pRecord = new RECORD;
        pRecord->recNumEdit = iNumber;
        pRecord->recNameEdit = strName;
        pRecord->recYearEdit = iBirth;
        CString strTerm;
        strTerm.Format("%d    %s    %d", iNumber, strName, iBirth);
        int iIndex = m_ListBox.AddString(strTerm);// Добавить в список
        // Сохранить указатель на неименованный экземпляр структуры в списке
        m_ListBox.SetItemData(iIndex, (DWORD_PTR)pRecord);
                          
        rc = SQLFetch(hstmt); // Дать следующую запись
      }
        
    // Освободить все ресурсы ODBC
      if(henv){
        if(hdbc){
          if(bIsConnected){
            if(hstmt){
              SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
            }
            SQLDisconnect(hdbc);
            bIsConnected = false;
          }
          SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
          hdbc = NULL;
        }
        SQLFreeHandle(SQL_HANDLE_ENV, henv);
        henv = NULL;
      }
    }
  • После завершения работы приложения необходимо вернуть системе память, выделенную динамически под хранение экземпляров структуры RECORD.

  • В классе CAPI_ODBCDlg переопределите виртуальную функцию DestroyWindow() базового класса CDialog

    и разместить в ней следующий код

    Код функции DestroyWindow() в файл API_ODBCDlg.cpp
    BOOL CAPI_ODBCDlg::DestroyWindow()
    {
      RECORD* pRecord;
      int nCount = m_ListBox.GetCount();
      for(int i = 0; i < nCount; i++){
        pRecord = (RECORD*)m_ListBox.GetItemData(i);
        if(pRecord)
          delete pRecord;
      }
      
      return CDialog::DestroyWindow();
    }
  • Установим связь между полями ввода Edit Control и списком List Box, добавив обработчик в класс CAPI_ODBCDlg. Для этого:

  • В панели
  • Добавьте обработчик для сообщения LBN_SELCHANGE, который заполните таким кодом
    Код обработчика для списка в файле API_ODBCDlg.cpp
    void CAPI_ODBCDlg::OnLbnSelchangeListbox()
    {
      int iIndex = m_ListBox.GetCurSel();
      if(iIndex == LB_ERR) return;
      
      RECORD* pRecord = 
                   (RECORD*)m_ListBox.GetItemData(iIndex);
      if(pRecord){
        m_NumEdit = pRecord->recNumEdit;
        m_NameEdit = pRecord->recNameEdit;
        m_YearEdit = pRecord->recYearEdit;
      
        UpdateData(FALSE);
      }
    }
  • Добавьте в функцию OnInitDialog() класса CAPI_ODBCDlg после вызова функции FillDbToListBox() заполнение полей ввода значениями первой записи
    Инициализация полей ввода в файле API_ODBCDlg.cpp
    BOOL CAPI_ODBCDlg::OnInitDialog()
    {
    ...............................................
      // TODO: Add extra initialization here
      FillDbToListBox();
      m_ListBox.SetCurSel(0);
      OnLbnSelchangeListbox();
      
      return TRUE;  // return TRUE  unless you set the focus to a control
    }
  • Оформите About со своими фамилиями и номером лабораторной работы
  • Постройте приложение и проверьте его работу. Результат должен быть примерно таким
  • Страницы:
    Файлы к данной лабораторной работе, Вы можете скачать здесь.

    Некоторые определения

  • DB ( Data Base ) - база данных, представляющая собой совокупность взаимосвязанных таблиц, каждая из которых, как правило, хранится в отдельном файле. Имя базы данных обычно совпадает с именем каталога, где хранятся данные
  • ODBC ( Open Database Connectivity ) - открытое соединение с базами данных
  • DBMS ( Database Management System ) - система управления базами данных
  • SDK ( Software Development Kit ) - комплект разработки программного обеспечения
  • API ( Application Programming Interface ) - интерфейс прикладных программ. Функции API ODBC обеспечивают взаимодействие при помощи синтаксиса SQL с любыми форматами данных, для которых существует соответствующий драйвер ODBC
  • SQL ( Structured Query Language ) - язык структурированных запросов
  • ODBC Driver - драйвер взаимодействия с данными определенной структуры. Среда разработки Visual C++ .NET предоставляет готовые к употреблению драйвера ODBC, предназначенные для работы с такими СУБД, как
  • SQL Server
  • Microsoft Access
  • Microsoft FoxPro
  • Microsoft Excel
  • dBASE
  • Paradox
  • Текстовые файлы
  • ODBC Driver Manager - диспетчер драйверов ODBC (файл ODBC32.DLL )
  • ODBC Cursor Library - библиотека курсоров ODBC (файл ODBCCR32.DLL )
  • Data Source - источник данных, означает логическое представление в виде таблицы конкретного файла с данными
  • DSN ( Data Source Name ) - имя (алиас, псевдоним) источника данных, зарегистрированное диспетчером драйверов, под которым ODBC будет обеспечивать подключение к данным нужного драйвера и в котором будет указан путь размещения таблиц базы данных
  • Result Set - результирующий набор данных, полученный после выполнения SQL -запроса к базе данных
  • Connection - подключение к базе данных
  • ODBC Data Source Administrator - специальная утилита (вспомогательная, служебная программа) корпорации Microsoft для регистрации источника данных
  • Для обеспечения определенного уровня программной абстракции ODBC обеспечивает взаимодействие с конкретным форматом данных через драйвер ODBC, поставляемый разработчиком соответствующего формата DB. Чтобы ODBC поддерживало соответствующий формат данных, разработан перечень требований, который должен обеспечить разработчик драйвера (поставщик соответствующих данных). Этот перечень требований называется уровнем согласования.

    Драйвер может поддерживать и большее число функций, но базовый уровень согласования должен быть выдержан, если разработчик драйвера хочет, чтобы его данные поддерживались приложениями на основе ODBC.

    Когда приложение использует функции базового уровня согласования, то в проверке возможностей драйвера нет необходимости. Но если приложение базы данных хочет обратиться к функции ODBC, не принадлежащей базовому уровню согласования, то оно должно предварительно удостовериться, что драйвер эту функцию поддерживает. Для выяснения возможности драйвера поддерживать какую-то функцию в ODBC существует специальная тестовая функция SQLGetFunctions(). Аналогичные требования согласования существуют и для синтаксиса SQL.

    Все функции ODBC можно разделить на логические группы, соответствующие порядку, в котором их применяют при создании приложений:

  • Настройка ODBC
  • Подключение к источнику данных
  • Получение информации о возможностях драйвера ODBC
  • Подготовка к выполнению SQL -запросов
  • SQL -запросы на получение выборки данных
  • Получение данных
  • Отключение от источника данных
  • Настройка ODBC

    Настройка ODBC включает в себя действия, которые нужно выполнить для нормальной работы приложения. Прежде всего нужно запустить системную утилиту ODBC Data Source Administrator. Она запускается из командной строки Пуск/Выполнить операционной системы командой odbcad32

    В результате появится окно администратора

    Окно администратора имеет несколько вкладок, в том числе соответствующие типу DSN, поддерживаемому ODBC

  • User DSN - пользовательский источник данных. Является локальным для того компьютера, на котором он был создан, и может применяться только создавшим его пользователем
  • System DSN - системный источник данных. Принадлежит тому компьютеру, на котором создан. Любой обладающий достаточными правами пользователь может обращаться к этому источнику данных
  • File DSN - файловый источник данных. Обеспечивает доступ к конкретному файлу базы данных
  • Каждая вкладка делает свою запись в системном реестре, например

    Чтобы создать DSN для источника данных, поддерживаемого драйвером ODBC, следует указать всю информацию, необходимую для его работы. Большинство драйверов ODBC требуют, как минимум, имени физического файла с данными и путем доступа к файлу.

    Иными словами, с помощью утилиты ODBC Data Source Administrator мы устанавливаем ассоциативную связь между файлом с данными конкретной структуры и соответствующим драйвером, понимающим такой формат данных. Этой связи мы присваиваем имя ( DSN ), которое затем должны использовать в своем приложении для работы с этими данными, но уже с помощью стандартных функций ODBC. Таким образом достигается отделение (абстракция) функций обработки данных от конкретной структуры данных. Кроме этого, в DSN содержится информация местонахождения файлов-таблиц с данными. Перенеся приложение и данные в другое место или компьютер, достаточно установить настройки соответствующего DSN и приложение будет нормально работать!!!

    Подключение к источнику данных

    После настройки DSN с помощью утилиты ODBC Data Source Administrator теперь вместо конкретного файла с данными у нас есть именованная абстракция данные-драйвер, зарегистрированная в операционной системе и поэтому готовая к поддержке ее механизмом ODBC. Все готово для того, чтобы к базе данных можно было обратиться с помощью функций API ODBC как к абстрактным данным, которые теперь можно назвать данными ODBC.

    Первым этапом программного доступа к данным ODBC является подключение к источнику данных. Для этого механизм ODBC требует выполнить последовательность действий, часть из которых является подготовительной:

  • Прежде всего следует инициализировать сам механизм ODBC и получить уникальный дескриптор среды ( unique environment handle ) henv. Этот дескриптор уникально идентифицирует в операционной системе поток, который будет использовать механизм ODBC. Следующий фрагмент кода резервирует в системе поток для ODBC
    SQLHENV henv;
    SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_ENV,
                                  SQL_NULL_HANDLE,
                                  henv);
  • Получив дескриптор среды, следует установить версию ODBC, с которой совместимо приложение
    SQLSetEnvAttr(henv,
                  SQL_ATTR_ODBC_VERSION,
                  (SQLPOINTER) SQL_OV_ODBC3,
                  SQL_IS_INTEGER);
  • первый параметр указывает дескриптор потока
  • второй указывает необходимость регистрирования версии
  • третий устанавливает, что приложение будет поддерживать ODBC версии 3.0
  • Прежде, чем приложение сможет подключиться к источнику данных, оно должно получить от ODBC дескриптор подключения к базе данных ( data connection handle ) hdbc
    SQLHDBC hdbc;
    SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_DBC,
                                  henv,
                                  hdbc);
  • Получив дескриптор hdbc, можно, наконец, подключиться к источнику данных, использовав любую из трех функций ODBC:
  • SQLConnect() - самый простой способ подключения к источнику данных. Ей достаточно передать DSN, идентификатор пользователя и пароль для подключения к источнику данных ODBC
  • SQLDriverConnect() - альтернативный вариант для драйверов ODBC. Она требует передачи более подробной информации, чем функция SQLConnect()
  • SQLBrowseConnect() - это средство подключения к источнику данных, обладающее наибольшими возможностями. При помощи одного или нескольких обращений к этой функции можно запросить и получить всю информацию, необходимую для подключения к источнику данных. Для обеспечения такого подхода функция возвращает значение SQL_NEEDS_DATA до тех пор, пока приложение не представит всю требуемую информацию, и только после этого она возвратит значение SQL_SUCCESS
  • Вот первый, наиболее простой, вариант использования функции подключения

    SQLRETURN rc = SQLConnect(hdbc, // Дескриптор подключения
              (SQLCHAR*) "MyBase", SQL_NTS, // DSN
              (SQLCHAR*) "", SQL_NTS, // Идентификатор пользователя
              (SQLCHAR*) "", SQL_NTS); // Пароль для подключения
    bool bIsConnected = TRUE; // Подняли флаг, что соединение существует

    Аргумент SQL_NTS сообщает ODBC о том, что предыдущий переданный аргумент представляет собой строку с завершающим нулевым символом.

    Получение информации о возможностях драйвера ODBC

    Одной из наиболее существенных особенностей ODBC является то, что он позволяет запросить информацию о возможностях драйвера ODBC. Для этого используются API ODBC -функции SQLGetFunctions(), SQLGetInfo(), SQLGetTypeInfo(). Рассмотрим их...

  • SQLGetFunctions() - позволяет запросить драйвер ODBC о том, поддерживает ли он определенные функции или группы функций. В качестве аргументов необходимо передать дескриптор hdbc и числовое значение, идентифицирующее определенную функцию или группу функций API ODBC. Эти значения определены в файле заголовка sqlext.h, который устанавливается вместе с Microsoft SQL Server, SDK ODBC или Visual Studio .NET. Приведенный ниже фрагмент кода выясняет, поддерживает ли драйвер, ассоциированный с указанным DSN, API ODBC -функции SQLTables() и SQLColumns()
    SQLUSMALLINT bSQLTablesFunctionExists; // Объявили логическую величину
    SQLUSMALLINT bSQLColumnsFunctionExists; // Объявили логическую величину
    SQLGetFunctions(hdbc,
                    SQL_API_SQLTABLES,
                    bSQLTablesFunctionExists);
    SQLGetFunctions(hdbc,
                    SQL_API_SQLCOLUMNS,
                    bSQLColumnsFunctionExists);
    if(bSQLTablesFunctionExists
        bSQLColumnsFunctionExists){
       // Обе функции существуют
    }
  • Если проверяемый драйвер поддерживает базовый уровень согласования, то необходимости в приведенной проверке функциональности драйвера нет. Если только мы не используем самопальный или экзотический формат данных, а придерживаемся структуры данных наиболее распространенных типов, то можно быть уверенным, что разработчики этих форматов обеспечили базовый уровень согласования.

  • SQLGetInfo() - возвращает информацию о драйвере ODBC и его возможностях. Например, с ее помощью можно выяснить версию драйвера ODBC, его название, а также уровни согласования API и SQL
  • SQLGetTypeInfo() - возвращает информацию о том, какие типы данных поддерживаются применяемым источником данных. Драйвер возвращает информацию в формате результирующего набора данных ( result set )
  • Подготовка к выполнению SQL-запросов

    Последнее, что приложение должно сделать после подключения к источнику данных, но прежде, чем оно будет способно осуществлять запросы SQL, - это получить дескриптор оператора ( statement handle ) или hstmt. Чтобы получить дескриптор hstmt, достаточно выполнить такой код

    SQLSTMT hstmt;
    SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_STMT,
                                  hdbc,
                                  hstmt);

    Как только приложение получит дескриптор hstmt, оно сможет передавать источнику данных операторы SQL.

    SQL-запросы на получение выборки данных

    Существуют два разных способа организации выполнения источником данных запроса SQL.

    Способ 1

    LPCSTR szSQL = "INSERT INTO MyTable "
                   "VALUES('UserID', 'USER NAME', 0)";
    SQLRETURN rc;         
    rc = ::SQLExecDirect(hstmt, (unsigned char*)szSQL, SQL_NTS);

    Данная функция добавит в таблицу MyTable базы данных MyBase новую строку данных со значениями полей ("UserID", "USER NAME", 0)

    Способ 2

    Здесь подразумевается предварительная подготовка SQL -запроса при помощи функции SQLPrepare() и последующее его выполнение при помощи функции SQLExecute().

    LPCSTR szSQL = "INSERT INTO MyTable "
                   "VALUES('UserID', 'USER NAME', 0)";
    SQLRETURN rc;      
    rc = ::SQLPrepare(hstmt, (unsigned char*)szSQL, SQL_NTS);   
    if(rc != SQL_SUCCESS){
       // Плохо! Сообщить пользователю или принять решение! 
       // Дальше не идти!
    }  
                
    rc = ::SQLExecute(hstmt);   
    if(rc != SQL_SUCCESS){
       // Плохо! Сообщить пользователю или принять решение! 
       // Дальше не идти!
    }

    Второй способ иногда может быть эффективней потому, что выражение SQL будет откомпилировано один раз, а затем может многократно использоваться для выполнения одного и того же запроса.

    Получение данных

    При выполнении SQL -запроса на получение данных приложению возвращается результирующий набор данных ( result set ), из которого приложение должно выбирать данные и передавать их в переменные для обработки и представления пользователю. Для решения этой задачи предусмотрен ряд API ODBC -функций, таких как

  • SQLNumResultCols() - определяет количество и тип полей возвращенных данных в результирующем наборе
  • SQLGetData() - извлекает значение определенного поля
  • SQLBindCol() - ассоциирует переменные приложения со столбцами базы данных
  • SQLFetch() - передает данные из набора в переменные и передвигает курсор результирующего набора данных на следующую запись
  • Эти функции позволяют разработчику создавать приложения баз данных, не заботясь заранее о том, сколько полей и какого типа будет возвращать источник данных в составе результирующего набора данных.

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

    Отключение от источника данных

    Как только подключение к источнику данных станет ненужным, от него необходимо отключиться при помощи функции SQLDisconnect(), а также освободить все дескрипторы, которые были для этого созданы. Освобождение дескрипторов должно выполняться в порядке, обратном их созданию. Соответствующий код может выглядеть так

    if(henv){
      if(hdbc){
        if(bIsConnected){
          if(hstmt){
            ::SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
          }
          ::SQLDisconnect(hdbc);
          bIsConnected = FALSE;
        }
        ::SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
        hdbc = NULL;
      }
      ::SQLFreeHandle(SQL_HANDLE_ENV, henv);
      henv = NULL;
    }

    Настройки мастера создания приложения для автоматической поддержки ODBC

    Правильная настройка мастера создания приложений с поддержкой ODBC позволяет сгенерировать значительное количество кода автоматически. Кратко рассмотрим некоторые параметры мастера.

    Нужно создать новый проект на основе MFC

    Установить тип документа SDI или MDI

    Вкладка Database Support

    None (Отсутствует) - мастер не будет создавать никакого кода, имеющего отношение к базам данных.

    Header files only (Только файлы заголовка) - сделает доступным переключатель, позволяющий выбрать применяемую приложением технологию доступа к базе данных. Выбор параметра ODBC приведет к включению мастером в состав проекта заголовка AFXDB.H и добавит в проект все необходимые подключаемые баблиотеки. Выбор параметра OLE DB приведет к подключению таких файлов заголовка, как ATLBASE.H, AFXOLEDB.H, ATLPLUS.H.

    Database view without file support (Представление базы данных без файловой поддержки) - приведет к включению в состав приложения соответствующих заголовочных файлов, подключаемых библиотек, представления записей и класса набора записей. Через кнопку Data Source (Источник данных) можно выбрать ODBC DSN или OLEDB Provider, а также используемую таблицу или представление. Параметры Dynaset (Динамический набор) или Snapshot (Моментальная выборка) позволяют выбирать тип логического курсора для результирующего набора данных. Параметр Bind all columns (Связать все поля) - включает поддержку механизма обмена данными RFX ( Record Field Exchange - обмен данными с полями записи) между переменными представления и полями базы. Это аналог DDX для баз данных.

    Database view with file support (Представление базы данных с файловой поддержкой) - обеспечивает способность полученному коду сохранять документ.

    Создание проекта API ODBC

    Если выбрать тип диалогового приложения, то мастер делает все настройки поддержки ODBC недоступными. Однако можно включить эти поддержки в приложение вручную, что мы и сделаем ниже, выбрав тип диалогового приложения для лабораторной работы.

    Создание DSN ODBC

  • Скопируйте из каталога Pictures данной лабораторной работы файл MyTable.dbf в свой каталог
  • Запустите системную утилиту ODBC Data Source Administrator. Она запускается из командной строки Пуск/Выполнить операционной системы командой odbcad32
  • Установите вкладку User DSN и нажмите кнопку Add
  • Выберите драйвер как показано на рисунке
  • Нажмите кнопку Готово и в появившемся окне

    заполните имя источника данных своим именем, например, MyBase. Установите версию dBase IV

  • Нажмите кнопку Выбор каталога и через диалоговое окно выберите каталог, где будут размещены файлы с таблицами данных
  • В нашем случае используется одна таблица с данными MyTable.dbf, которую можно найти в каталоге размещения этого описания.

  • Предполагая, что Вы уже скопировали или еще скопируете файл MyTable.dbf в свой каталог, установите в качестве размещения базы данных свой каталог
  • Окно выбора каталога размещения базы данных с примерными настройками показано на рисунке.

  • Нажимайте кнопку OK вплоть до закрытия окна администратора
  • Создание заготовки проекта диалогового приложения

  • Создайте новый проект на основе
  • Настройте вкладки мастера так
  • Нажмите кнопку Finish и мастер сгенерирует диалоговое приложение
  • Добавление в проект поддержки ODBC

    Оболочка Studio .NET при создании диалогового приложения не поддерживает установки мастера на обеспечение ODBC. Поэтому поддержку ODBC необходимо включить вручную. Чтобы приложение могло воспользоваться SDK ODBC, в него необходимо включить соответствующие файлы заголовка и подключаемые библиотеки. Существуют два способа решения этой задачи

    Способ 1.

    В сгенерированный мастером файл API_ODBCDlg.cpp ( в начало ) или в файл stdafx.h ( в конец ) неоходимо включить заголовочные файлы

    #include <sql.h> 
    #include <sqlext.h>
    #include <sqltypes.h>

    В проект приложения необходимо добавить библиотеку импорта ODBC (odbc32.lib) . Для этого в меню Project нужно выбрать пункт меню Add Existing Item (Добавить существующий элемент) и найти необходимую библиотеку импорта odbc32.lib. В конфигурации Visual Studio .NET этот файл находится в каталоге Lib, как показано на рисунке

    Если выбрать этот файл, то он автоматически будет добавлен в проект и появится во вкладке Solution Explorer данного проекта

    Способ 2.

    В сгенерированный мастером файл API_ODBCDlg.cpp ( в начало ) или в файл stdafx.h ( в конец ) необходимо включить заголовочный файл

    #include <afxdb.h>

    Этот файл подключает необходимые файлы заголовка Windows и содержит директиву #pragma comment, что обеспечивает включение необходимых библиотек импорта ODBC в состав приложения.

  • Включите в конец файла stdafx.h директиву, приведенную во втором способе так
    Включение директивы поддержки ODBC в файл stdafx.h
    // stdafx.h : include file for standard system include files,
    // or project specific include files that are used frequently,
    // but are changed infrequently
      
    #pragma once
    .....................................................
      
    #include <afxdb.h>
  • Конструирование главного диалогового окна приложения

  • Раскройте вкладку Resource View и русифицируйте ресурсы
  • IDD_ABOUTBOX
  • Откройте ресурс IDD_API_ODBC_DIALOG в режиме дизайна, удалите все элементы управления и добавьте следующие элементы управления из панели Toolbox
  • List Box - 1 шт.
  • Static Text - 3 шт.
  • Edit Control - 3 шт.
  • Включите панель инструментов
  • Измените идентификаторы списка и полей ввода и создайте для этих элементов управления переменные-члены Ассоциированные переменные для элементов управления ID Категория Тип Имя Доступ IDC_LISTBOX Control CListBox m_ListBox public IDC_NUMEDIT Value int m_NumEdit public IDC_NAMEEDIT Value CString m_NameEdit public IDC_YEAREDIT Value int m_YearEdit public
  • Настройки мастера приведены на рисунках

    Мастер внесет добавления в объявление класса

    Добавления в файл API_ODBCDlg.h
    // CAPI_ODBCDlg dialog
    class CAPI_ODBCDlg : public CDialog
    {
    ..................................................
    public:
      // Переменная управления списком
      CListBox m_ListBox;
      // Переменная поля ввода № п/п
      int m_NumEdit;
      // Переменная поля ввода ФИО
      CString m_NameEdit;
      // Переменная поля ввода Год
      int m_YearEdit;
    };

    Мастер внесет добавления в определение класса

    Добавления в файл API_ODBCDlg.cpp
    // CAPI_ODBCDlg dialog
      
    CAPI_ODBCDlg::CAPI_ODBCDlg(CWnd* pParent /*=NULL*/)
      : CDialog(CAPI_ODBCDlg::IDD, pParent)
      , m_NumEdit(0)
      , m_NameEdit(_T(""))
      , m_YearEdit(0)
    {
      m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    }
      
    void CAPI_ODBCDlg::DoDataExchange(CDataExchange* pDX)
    {
      CDialog::DoDataExchange(pDX);
      DDX_Control(pDX, IDC_LISTBOX, m_ListBox);
      DDX_Text(pDX, IDC_NUMEDIT, m_NumEdit);
      DDX_Text(pDX, IDC_NAMEEDIT, m_NameEdit);
      DDX_Text(pDX, IDC_YEAREDIT, m_YearEdit);
    }
      
    BEGIN_MESSAGE_MAP(CAPI_ODBCDlg, CDialog)
      ON_WM_SYSCOMMAND()
      ON_WM_PAINT()
      ON_WM_QUERYDRAGICON()
      //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
      
    ........................................................

    Код инициализации ODBC и взаимодействие с базой данных

    Разработаем код, который получает данные из базы данных и заполняет ими список. Код упакуем в функцию FillDbToListBox().

  • Добавьте в класс CAPI_ODBCDlg объявление функции
    Добавление в файл API_ODBCDlg.h
    class CAPI_ODBCDlg : public CDialog
    {
    ................................................
    public:
      // Переменная управления списком
      CListBox m_ListBox;
      // Переменная поля ввода № п/п
      int m_NumEdit;
      // Переменная поля ввода ФИО
      CString m_NameEdit;
      // Переменная поля ввода Год
      int m_YearEdit;
      // Функция заполнения списка данными DB
      void FillDbToListBox(void);
    };
    Добавление в файл API_ODBCDlg.cpp
    
    // Функция заполнения списка данными DB
    void CAPI_ODBCDlg::FillDbToListBox(void)
    {
    }
  • Добавьте в начало файла API_ODBCDlg.cpp следующее определение структуры
    Добавление структуры в файл API_ODBCDlg.cpp
    // API_ODBCDlg.cpp : implementation file
    //
      
    #include "stdafx.h"
    #include "API_ODBC.h"
    #include "API_ODBCDlg.h"
    #include ".\api_odbcdlg.h"
      
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
      
    struct RECORD
    {
      int recNumEdit;// Переменная поля ввода № п/п
      CString recNameEdit;// Переменная поля ввода ФИО
      int recYearEdit;// Переменная поля ввода Год
    };
    .......................................................
  • Добавьте после определения структуры RECORD следующую директиву препроцессора
    Добавление директивы препроцессора в файл API_ODBCDlg.cpp
    ......................................................
    struct RECORD
    {
      int recNumEdit;// Переменная поля ввода № п/п
      CString recNameEdit;// Переменная поля ввода ФИО
      int recYearEdit;// Переменная поля ввода Год
    };
    
    #define LEN_BUF 31 // Длина буфера
    .......................................................
  • Найдите функцию OnInitDialog(), сгенерированную мастером, и непосредственно перед оператором return добавьте вызов функции FillDbToListBox(). Эта функция заполнит список на последнем этапе инициализации диалогового окна
    Добавление вызова в файл API_ODBCDlg.cpp
    BOOL CAPI_ODBCDlg::OnInitDialog()
    {
      CDialog::OnInitDialog();
    .............................................................
      
      // TODO: Add extra initialization here
      FillDbToListBox();
      
      return TRUE;  // return TRUE  unless you set the focus to a control
    }
  • Наполните функцию FillDbToListBox() следующим кодом, реализующим то, о чем было сказано выше
    Функция FillDbToListBox() в файле API_ODBCDlg.cpp
    // Функция заполнения списка данными DB
    void CAPI_ODBCDlg::FillDbToListBox(void)
    {
    // Объявления дескрипторов и вспомогательных переменных
      SQLHENV henv = NULL;
      SQLHDBC hdbc = NULL;
      SQLHSTMT hstmt = NULL;
      bool bIsConnected = false;
      SQLRETURN rc;
      
    // Инициализация механизма ODBC среды
      rc = SQLAllocHandle(SQL_HANDLE_ENV,
                          SQL_NULL_HANDLE,
                          henv);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу инициализировать ODBC",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Выбор версии ODBC-3
      rc = SQLSetEnvAttr(henv,
            SQL_ATTR_ODBC_VERSION,
            (SQLPOINTER) SQL_OV_ODBC3,
            SQL_IS_INTEGER);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу установить версию ODBC",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Получить от ODBC дескриптор подключения к базе данных
      rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, hdbc);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу получить дескриптор подключения к БД",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Подключить приложение к источнику данных
      rc = SQLConnect(hdbc, // Дескриптор подключения
              (SQLCHAR*) "MyBase", SQL_NTS, // DSN
              (SQLCHAR*) "", SQL_NTS, // Идентификатор пользователя
              (SQLCHAR*) "", SQL_NTS); // Пароль для подключения
      if(rc != SQL_SUCCESS  rc != SQL_SUCCESS_WITH_INFO){
        MessageBox("Не могу подключиться к источнику данных",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      bIsConnected = true; // Подняли флаг, что соединение существует
      
    // Получить дескриптор оператора (statement handle)
    // для возможности передачи SQL-запросов 
      rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, hstmt);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу получить дескриптор оператора",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Подготовить и выполнить SQL-запрос (имя таблицы можно без расширения)
      LPCSTR szSQL = "SELECT number, name, birth FROM MyTable.dbf";
      rc = SQLExecDirect(hstmt, (unsigned char*)szSQL, SQL_NTS);
      if(rc != SQL_SUCCESS){
        MessageBox("Не могу выполнить SQL-запрос к БД",
               "", MB_OK | MB_ICONSTOP);
        return;
      }
      
    // Связывание полей с переменными приложения
      long int cb; // Вспомогательная типа SDWORD
      int iNumber; 
      char strName[LEN_BUF];
      int iBirth; 
      SQLBindCol(hstmt, 1, SQL_INTEGER, iNumber, 0, cb);// Field1
      SQLBindCol(hstmt, 2, SQL_CHAR, strName, LEN_BUF, cb);// Field2
      SQLBindCol(hstmt, 3, SQL_INTEGER, iBirth, 0, cb);// Field3
      
    // Заполнение списка
      rc = SQLFetch(hstmt);
      while(rc == SQL_SUCCESS){
        RECORD* pRecord = new RECORD;
        pRecord->recNumEdit = iNumber;
        pRecord->recNameEdit = strName;
        pRecord->recYearEdit = iBirth;
        CString strTerm;
        strTerm.Format("%d    %s    %d", iNumber, strName, iBirth);
        int iIndex = m_ListBox.AddString(strTerm);// Добавить в список
        // Сохранить указатель на неименованный экземпляр структуры в списке
        m_ListBox.SetItemData(iIndex, (DWORD_PTR)pRecord);
                          
        rc = SQLFetch(hstmt); // Дать следующую запись
      }
        
    // Освободить все ресурсы ODBC
      if(henv){
        if(hdbc){
          if(bIsConnected){
            if(hstmt){
              SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
            }
            SQLDisconnect(hdbc);
            bIsConnected = false;
          }
          SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
          hdbc = NULL;
        }
        SQLFreeHandle(SQL_HANDLE_ENV, henv);
        henv = NULL;
      }
    }
  • После завершения работы приложения необходимо вернуть системе память, выделенную динамически под хранение экземпляров структуры RECORD.

  • В классе CAPI_ODBCDlg переопределите виртуальную функцию DestroyWindow() базового класса CDialog

    и разместить в ней следующий код

    Код функции DestroyWindow() в файл API_ODBCDlg.cpp
    BOOL CAPI_ODBCDlg::DestroyWindow()
    {
      RECORD* pRecord;
      int nCount = m_ListBox.GetCount();
      for(int i = 0; i < nCount; i++){
        pRecord = (RECORD*)m_ListBox.GetItemData(i);
        if(pRecord)
          delete pRecord;
      }
      
      return CDialog::DestroyWindow();
    }
  • Установим связь между полями ввода Edit Control и списком List Box, добавив обработчик в класс CAPI_ODBCDlg. Для этого:

  • В панели
  • Добавьте обработчик для сообщения LBN_SELCHANGE, который заполните таким кодом
    Код обработчика для списка в файле API_ODBCDlg.cpp
    void CAPI_ODBCDlg::OnLbnSelchangeListbox()
    {
      int iIndex = m_ListBox.GetCurSel();
      if(iIndex == LB_ERR) return;
      
      RECORD* pRecord = 
                   (RECORD*)m_ListBox.GetItemData(iIndex);
      if(pRecord){
        m_NumEdit = pRecord->recNumEdit;
        m_NameEdit = pRecord->recNameEdit;
        m_YearEdit = pRecord->recYearEdit;
      
        UpdateData(FALSE);
      }
    }
  • Добавьте в функцию OnInitDialog() класса CAPI_ODBCDlg после вызова функции FillDbToListBox() заполнение полей ввода значениями первой записи
    Инициализация полей ввода в файле API_ODBCDlg.cpp
    BOOL CAPI_ODBCDlg::OnInitDialog()
    {
    ...............................................
      // TODO: Add extra initialization here
      FillDbToListBox();
      m_ListBox.SetCurSel(0);
      OnLbnSelchangeListbox();
      
      return TRUE;  // return TRUE  unless you set the focus to a control
    }
  • Оформите About со своими фамилиями и номером лабораторной работы
  • Постройте приложение и проверьте его работу. Результат должен быть примерно таким
  • Вернуться к учебному плану