Демонстрационная программа (Исходные тексты) - 5 KB
Если вы не используете MFC для доступа к СУБД или вы хотите узнать, как работают классы CDatabase и CRecordset из этой библиотеки, то эта статья для вас.
Если объяснить просто, то для большинства разработчиков, ODBC это стандарт, поддерживая который можно создавать приложения способные работать со многими СУБД, при условии, что эти СУБД, в свою очередь, тоже поддерживают этот стандарт (поставляя драйвер, скрывающий от программиста подробности вызова native API). При этом, со стороны клиента совершенно не обязательно знать с какой СУБД он работает в данный момент: Oracle, MSSQL, Access. Если клиент использует ODBC, то он не замечает особой разницы.
Подробное описание ODBC API вы сможете найти в библиотеке MSDN в разделе Platform SDK Documentation -> Data Services -> Microsoft Data Access Components (MDAC) SDK -> Microsoft Open Database Connectivity (ODBC).
В данной статье мы рассмотрим, как с помощью ODBC создавать простые приложения способные выполнять запросы к базам данных и получать данные.
Мы разберём всю последовательность действий, которые необходимо выполнить любому приложению работающему с СУБД посредством ODBC.
Сначала в общих чертах.
Теперь подробнее.
Прежде чем начать работать, нам необходимо загрузить Driver Manager (Менеджер драйвера). Для этого вызовем функцию SQLAllocHandle, которая даст нам описатель (Handle) окружения. Необходимо сохранить его:
SQLHENV henv = NULL; // Alloc env handle if (::SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv) == SQL_ERROR) { DumpError(_T("AllocHandle on ENV failed.")); goto EXIT; } |
Затем нам необходимо зарегистрировать версию ODBC, с которой мы собираемся работать. Делается это с помощью функции SQLSetEnvAttr с флагом SQL_ATTR_APP_ODBC_VER. Эта функция, как видно из названия, изменяет атрибуты окружения Менеджера драйвера.
В нашем случае сообщаем, что работаем с 3-й версией ODBC (SQL_OV_ODBC3):
::SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
(void*) SQL_OV_ODBC3, SQL_IS_INTEGER); |
Далее переходим непосредственно к установлению соединения. Для этого мы должны инициализировать структуру, в которой Менеджер драйвера хранит всю информацию о соединении, и получить описатель этой структуры (Handle). Делается это с помощью всё той же функции SQLAllocHandle, но теперь в качестве флага мы указываем не SQL_HANDLE_ENV, а SQL_HANDLE_DBC:
SQLHDBC hdbc = NULL; if ( ::SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc) == SQL_ERROR) { DumpError(_T("AllocHandle on DBC failed.")); goto EXIT; } |
Теперь всё готово к установлению соединения с источником данных. Да, да, я не оговорился именно с источником данных (Data Source), хотя ODBC позволяет работать с СУБД напрямую, делать этого не рекомендуется, поэтому мы не будем рассматривать эту возможность в данной статье.
Итак, устанавливаем связь с помощью функции SQLConnect:
if ( ::SQLConnect(hdbc, (SQLTCHAR*)szDsn, SQL_NTS, (SQLTCHAR*)szUser, _tcslen(szUser), (SQLTCHAR*)szPass, _tcslen(szPass) ) == SQL_ERROR ) { DumpError( _T("Connect to ODBC failed.") ); goto EXIT; } |
Эта функция принимает 7 аргументов:
Ну, что ж теперь мы имеем соединение с СУБД и неплохо было бы воспользоваться этим обстоятельством для собственной выгоды :-))) - начинаем работать с базой данных.
Перед тем как выполнять запрос к СУБД нам необходимо инициализировать структуру, в которой хранится вся информация о текущем запросе, делается это с помощью всё того же метода SQLAllocHandle, с флагом SQL_HANDLE_STMT.
// Get a statement handle if (::SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt) == SQL_ERROR) { DumpError( _T("AllocHandle on STMT failed.") ); goto EXIT; } |
Запросы к базе данных можно выполнять двумя способами.
Прямой запрос с помощью SQLExecDirect, когда строка запроса непосредственно передаётся функции со всеми необходимыми параметрами.
Запрос сначала подготавливается с помощью SQLPrepare, затем если необходимо специальные символы в строке запроса, в ODBC это знак «?», можно связать с какими либо переменными, это делается с помощью SQLBindParameter, затем запрос отправляется в СУБД методом SQLExecute.
Поскольку с первым способом всё понятно и комментариев не требуется, то рассмотрим подробно лишь второй способ.
SQLREAL Price; SQLUINTEGER PartID; SQLINTEGER PartIDInd = 0, PriceInd = 0; // Подготавливаем запрос, обратите внимание на знаки «?» вместо значений. SQLPrepare(hstmt, "UPDATE Parts SET Price = ? WHERE PartID = ?", SQL_NTS); // Связываем 1-й и 2-й параметры с переменными. SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_FLOAT, SQL_REAL, 7, 0, &Price, 0, &PriceInd); SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 10, 0, &PartID, 0, &PartIDInd); // Запрос готов. // Инициализируем связанные переменные значениями и выполняем запрос. while (GetPrice(&PartID, &Price)) { SQLExecute(hstmt); } |
Как видите, здесь тоже нет ничего сложного.
Чтобы получить данные из СУБД, нам необходимо просто подготовить переменные, в которые мы будем записывать получаемые значения, и связать эти переменные с соответствующими столбцами результата запроса. Делается это с помощью метода SQLBindCol.
SQLRETURN ret = ::SQLBindCol( hstmt, nCol, FldBindType, ColData.DataPtr, BuffLen, &ColData.StrLen_or_Ind ); |
Коротко опишем эту функцию. Она принимает на вход 6 параметров:
Всё понятно если мы знаем, сколько столбцов в результате запроса и тип каждого из них, а если нет, если заранее мы не знаем ни количество столбцов, ни их тип? На этот случай ODBC API имеет один метод, чтобы узнать количество столбцов в результате запроса
ret = SQLNumResultCols( hstmt, &nCols ); |
И два метода, что бы получить информацию о каждом столбце SQLDescribeCol и SQLColAttribute обе эти функции функционально почти идентичны, но SQLColAttribute возвращает расширенное описание, основанное на стандарте ANSI SQL-92 и DBMS, а SQLDescribeCol информацию основанную на стандарте ANSI-89 SQL.
После того как мы связали наши переменные с каждым из столбцов, всё, что осталось нам сделать это вызывать метод SQLFetch, пока он не вернёт нам значение равное SQL_NO_DATA говорящее нам, что была достигнута последняя строка в результате запроса.
После каждого вызова SQLFetch, наши переменные, связанные со столбцами, содержат значения полей текущей строки в результате запроса.
Ну что же мы получили данные, теперь нам обязательно необходимо закрыть курсор, который открывается автоматически при выполнении запроса к СУБД (SQLExecDirect или SQLExecute), делаем это методом SQLCloseCursor
// Close result cursor
::SQLCloseCursor( hstmt );
|
Далее мы возвращаемся к пункту номер 2 или завершаем работу, для чего закрываем все описатели в порядке обратном их открытию, не забываем закрыть соединения с базой данных, всё это выглядит следующим образом.
if (hstmt != NULL) { SQLFreeHandle(SQL_HANDLE_STMT, hstmt); } if (hdbc != NULL) { SQLDisconnect(hdbc); SQLFreeHandle(SQL_HANDLE_DBC, hdbc); } // Здесь можно перейти к шагу номер 2 if (henv != NULL) { SQLFreeHandle(SQL_HANDLE_ENV, henv); } |
Вот так вкратце работают приложения на базе ODBC API. Возможности ODBC API намного шире, чем показано в данной статье, за дополнительной информацией обращайтесь в MSDN.