|
::Главная страница :: С++/Си :: Статьи |
Доступ
к БД с использованием ODBC
Часть 1
Открытый интерфейс доступа к базам данных (Open Database Connectivity, ODBC) – это программный интерфейс, который позволяет приложению обращаться к различным СУБД, используя структурированный язык запросов SQL. Применяя ODBC, разработчики могут писать программы, независимые от архитектуры конкретной СУБД. Такие программы будут работать с любой реляционной базой данных (как существующей в данный момент, так и той, которая, возможно, появится в будущем), для которой написан ODBC-драйвер.
НЕМНОГО ТЕОРИИ
Структура ODBC
Архитектура ODBC имеет четыре основных компонента: пользовательское приложение,
менеджер драйверов ODBC, драйвер, источник данных. Менеджер драйверов написан
в виде DLL, которая загружается пользовательским приложением и перенаправляет
вызовы функций ODBC API нужному драйверу. Драйвер, в свою очередь, выполняет
основную работу по выполнению запросов.
Типичная схема взаимодействия приложения с базой данных состоит из трёх шагов:
ODBC API и классы MFC
MFC предоставляет набор классов, облегчающих работу с ODBC API. Два из них мы
рассмотрим подробно – это CDatabase и CRecordset. Хотя эти два класса позволяют
выполнять все основные операции по выборке и модификации данных, иногда их возможностей
оказывается недостаточно. В этом случае приходится вызывать функции ODBC API
напрямую (все эти функции имеют префикс SQL).
Источники данных
Источник данных (data source) – это по сути логическое имя базы данных, которое
используется для обращения к ней средствами ODBC. Эта абстракция оказывается
достаточно удобной: если база данных, используемая программой, будет скопирована
в другой каталог или перенесена на другой компьютер, нужно просто скорректировать
атрибуты источника данных, не внося никаких изменений в саму программу. Однако,
ODBC позволяет работать с базой данных и напрямую, то есть без использования
источников данных.
БАЗОВЫЕ ВОЗМОЖНОСТИ ODBC
Обработка ошибок
Прежде чем мы приступим к работе с ODBC, нужно научиться обрабатывать ошибки,
которые могут возникнуть в процессе выполнения программы. Очень часто ответ
на вопрос "почему программа не работает?" находится под рукой; нужно
только знать, куда посмотреть.
При использовании ODBC API программист должен был анализировать возвращаемые значения функций, обращаясь за более подробной информацией об ошибке к функции SQLError. MFC скрывает от нас детали этого процесса. В случае возникновения ошибки она возбуждает исключение, которое и должна перехватить наша программа. Обработчик исключения получает указатель на структуру CDBException, которая содержит всю необходимую информацию. Так, поле m_strError содержит описание ошибки в понятной для человека форме, а в m_strStateNativeOrigin записывается пятибуквенный код состояния ODBC, который удобно анализировать в программе, а также некоторая дополнительная информация. Типичный пример кода обработки исключений выглядит так:
try { // Работаем с БД } catch(CDBException *pException) { AfxMessageBox(pException->m_strError); pException->Delete(); // Удаляем структуру из памяти! }
Замечу, что обработку исключений следует использовать только для восстановления программы после ошибки. Если всё, что нам требуется – это просмотреть значения полей структуры CDBException, можно сделать это и без перехвата исключений. В режиме отладки Visual C++ сам выводит содержимое структуры CDBException на вкладку Debug в окно Output; так что если ваша программа "рухнула", следует первым делом посмотреть на эту вкладку.
Соединение с базой данных
Сначала поговорим о том, как в системе регистрируются источники данных. Это
можно сделать программно или с помощью специальной утилиты – администратора
источников данных ODBC. Эта утилита входит в состав Windows и вызывается через
панель управления (пункт "Источники данных ODBC (32)"). Программную
регистрацию мы рассмотрим во второй части, а пока можно воспользоваться услугами
администратора.
Соединение с базой данных в MFC представляется объектом класса CDatabase. Чтобы установить соединение, необходимо воспользоваться функцией CDatabase::OpenEx (функция Open устарела; к тому же пользоваться ею менее удобно). Например:
CDatabase db; db.OpenEx("DSN=db;UID=sa;PWD=", 0);
Первый параметр функции OpenEx – это строка подключения, в которой пары "параметр=значение" разделяются точкой с запятой. Имена параметров нечувствительны к регистру. В стандартный набор параметров строки соединения входят: DSN (data source name – имя источника данных), UID (имя пользователя), PWD (пароль) и DRIVER (драйвер ODBC). Большинство драйверов распознаёт ряд дополнительных параметров.
Обратите внимание, что строка соединения не должна начинаться с префикса "ODBC;" (этот префикс нужно было добавлять при использовании функции CDatabase::Open).
Второй параметр функции OpenEx – набор битовых флагов, объединённых логическим "ИЛИ". Вот некоторые из них:
CDatabase::openReadOnly – открыть БД в режиме "только для чтения".
CDatabase::noOdbcDialog – никогда не выводить диалог, запрашивающий дополнительную информацию о соединении.
CDatabase::forceOdbcDialog – всегда выводить диалог, запрашивающий информацию о соединении. Этот режим уместен, если во время написания программы не известно, с какой именно базой данных она будет работать.
По умолчанию (если второй параметр OpenEx не задан или равен 0) база данных открывается в режиме "чтение и запись", а диалог появляется только в случае, когда в строке соединения отсутствуют необходимые параметры (например, имя источника данных).
Выборка данных из таблицы
Работая с ODBC, программа получает данные, извлекаемые из БД, в виде множества
записей (recordset). Каждая запись содержит набор полей. В любой заданный момент
времени программа может работать только с одной записью (она называется текущей).
Используя функции ODBC, можно перемещаться от одной записи к другой. Для удобства
каждое поле в результирующем множестве обычно связывается с переменной, которую
программа использует для чтения и модификации значения соответствующего поля.
Тем не менее, связывать поля с переменными в общем случае необязательно.
В MFC для работы с множеством записей предназначен класс CRecordset. Как правило, этот класс не используется в программе напрямую. Вместо этого от него порождают новые классы, переменные-члены которых и связываются с полями множества записей. Само связывание происходит в виртуальной функции CRecordset::DoFieldExchange, которая переопределяется в производном классе; эта же функция осуществляет обмен данных между полями записи и переменными класса. Множество записей создаётся функцией CRecordset::Open, а перемещение от одной записи к другой осуществляется посредством функций Move, MoveNext, MovePrev и т. п.
Пример
Рассмотрим небольшой пример. Допустим, в базе данных содержится таблица tPeople
с полями Name (типа строка из 50 символов) и DateOfBirth (типа дата/время),
и мы хотим напечатать на экране её содержимое. Сперва создаём новый класс, порождённый
от CRecordset:
class CPeople : public CRecordset { public: CPeople(CDatabase *pDatabase = NULL) : CRecordset(pDatabase) { m_nFields = 2; }; CString m_Name; CTime m_DateOfBirth; void DoFieldExchange(CFieldExchange *pFX); };
Для каждого поля в таблице tPeople мы объявили переменную соответствующего типа. Так, поле Name хранится как строка, поэтому m_Name имеет тип CString. DateOfBirth – это дата, поэтому m_DateOfBirth – переменная типа CTime. Обратите внимание, что в переменную m_nFields (CPeople наследует её от CRecordset) необходимо записать количество полей таблицы (это значение необходимо MFC, чтобы правильно построить запрос). Теперь реализуем функцию DoFieldExchange, в которой происходит связывание полей таблицы с переменными нашего класса CPeople.
void CPeople::DoFieldExchange(CFieldExchange *pFX) { pFX->SetFieldType(CFieldExchange::outputColumn); RFX_Text(pFX, "Name", m_Name, 50); RFX_Date(pFX, "DateOfBirth", m_DateOfBirth); }
Вызов SetFieldType с параметром CFieldExchange::outputColumn должен всегда предшествовать операциям связывания. Сам механизм связывания напоминает механизм обмена данными с элементами управления диалога. Тут и там используется набор специальных макросов, которые в зависимости от контекста (который определяется объектом класса CFieldExchange) выполняют различные действия - в нашем случае формируют элементы запроса, связывают переменные с полями множества записей и осуществляют обмен данными между ними.
Каждый из макросов, используемых в DoFieldExchange, имеет префикс "RFX_". Существует несколько версий этих макросов – по одному на каждый основной тип. Наборы параметров у них несколько отличаются, но первые три параметра совпадают у всех макросов: указатель на объект класса CFieldExchange (нужно просто передать указатель, полученный от MFC), имя поля во множестве записей и ссылка на переменную, которая будет с этим полем связана.
Итак, создание класса CPeople закончено, и можно использовать его для доступа к таблице tPeople. Вот фрагмент, который печатает на экране её содержимое.
… CDatabase Db; Db.OpenEx("DSN=db;UID=sa;PWD="); CPeople Rs(&Db); Rs.Open(CRecordset::dynaset, "tPeople"); while(!Rs.IsEOF()) { printf("%s\t%s\n", Rs.m_Name, Rs.m_DateOfBirth.Format("%d of %B %Y")); Rs.MoveNext(); } …
Здесь нужно обратить внимание на несколько моментов. Во-первых, как мы помним, прежде чем работать с базой данных, необходимо установить соединение с ней. Это делается с использованием уже знакомой нам функцией CDatabase::OpenEx. Во-вторых, указатель на соединение нужно передать конструктору класса CPeople, чтобы данные извлекались именно из нужной нам базы данных.
Теперь рассмотрим параметры функции CRecordset::Open. Первый параметр задаёт тип результирующего множества записей. Можно задавать следующие типы:
CRecordset::forwardOnly - множество записей, доступное только для чтения и по которому можно перемещаться только вперёд.
CRecordset::snapshot – множество записей, по которому можно перемещаться в любом направлении. Изменения, внесённые в БД после создания такого множества, в нём не отражаются.
CRecordset::dynaset – похоже на предыдущее, но любые изменения записи в БД будут видны после повторной выборки этой записи. Новые записи, добавленные в БД после создания такого множества, в нём не отражаются.
CRecordset::dynamic – самое ресурсоёмкое множество. Любые изменения, внесённые в БД после его открытия, будут в нём отражены. Не поддерживается многими драйверами.
Если драйвер не поддерживает запрошенный тип множества записей, MFC возбудит исключение.
Второй параметр функции CRecordset::Open используется для передачи имени таблицы или запроса, на основе которого будет построено множество записей. MFC сама определит, что именно ей передали. У функции CRecordset::Open есть также третий параметр – который во многих случаях можно не указывать. За его описанием можно обратиться к документации.
После того как множество записей создано, пользоваться им достаточно просто. Для обращения к полям текущий записи мы используем переменные-члены класса CPeople, а к следующей записи перемещаемся при помощи функции CRecordset::MoveNext. Когда все записи исчерпаны, функция CRecordset::IsEOF возвращает TRUE, и цикл прерывается.
Модификация данных в таблице
С помощью методов класса CRecordset можно изменять записи в таблице и добавлять
новые записи. Прежде чем изменять запись, следует убедиться, что открытое множество
записей допускает такую операцию, с помощью функции CRecordset::CanUpdate. Сама
модификация начинается вызовом функции CRecordset::Edit и завершается вызовом
функции CRecordset::Update; между этими двумя вызовами следует изменить значения
переменных, связанных с полями множества записей. Например:
if(Rs.CanUpdate()) { Rs.Edit(); Rs.m_Name = "Vasya Pupkin"; Rs.m_DateOfBirth = CTime(2000, 1, 1, 0, 0, 0); Rs.Update(); }
Аналогичным образом можно добавлять новые записи, но вместо Edit используется AddNew. Убедиться в том, что множество записей поддерживает добавление, можно с помощью функции CRecordset::CanAppend. Например:
if(Rs.CanAppend()) { Rs.AddNew(); Rs.m_Name = "Vasya Pupkin"; Rs.m_DateOfBirth = CTime(2000, 1, 1, 0, 0, 0); Rs.Update(); }
И последнее замечание. Чтобы обновить множество записей после внесения изменений в БД, нужно вызвать функцию CRecordset::Requery.
Разрыв соединения
Это самый простой, но совершенно необходимый этап. Закончив работу с источником
данных, программа должна разорвать с ним соединение вызовом CDatabase::Close.
Перед этим необходимо также закрыть все наборы записей, используя функцию CRecordset::Close.
Ни одна из этих функций не принимает никаких параметров.
Тематические
ссылки
|
Ваша ссылка | Ваша ссылка |
Обмен кнопками, ведение статистики, реклама. |
|||