Глава 20.
Библиотека MFC и базы данных
Соединяй приятное с полезным.
Козьма Прутков
Прежде чем начинать создавать приложения для работы с базами данных нужно познакомиться с некоторыми основными понятиями данной темы. Одним из них является понятие ODBC — Open Database Connectivity (Открытый интерфейс для подключения к базам данных), обозначающее наиболее универсальную на настоящий момент технологию, посредством которой можно получить доступ к базам данных.
ODBC является одной из самых старых технологий для работы с базами данных, которые выпустила фирма Microsoft. Одной из основных причин разработки ODBC была необходимость предоставления программистам простого способа доступа к содержимому баз данных с минимальной ориентацией на какой-либо конкретный язык. Среди платформ программирования, на которые фирма Microsoft изначально ориентировала на работу с ODBC, находится и Visual C++.
Основные принципы ODBC стандартны для Windows — для выполнения работы используются соответствующие драйверы, содержащиеся в DLL (Dynamic Link Library, Библиотека динамической компоновки). Драйверы, которые могут оказаться необходимыми для работы, можно получить непосредственно от поставщика базы данных — чаще всего прямо в пакете программ. В частности, Visual C++ поставляется со следующими входящими в пакет драйверами:
Список драйверов ODBC, инсталлированных на вашем компьютере, можно увидеть на вкладке Drivers (Драйверы) диалогового окна ODBC Data Source Administrator (рис. 20.1).
Примечание
Для вывода на экран диалогового окна ODBC Data Source Administrator просто дважды щелкните левой кнопкой мыши по значку ODBC в Control Panel (панель управления).
Чтобы получить доступ к базе данных с использованием ODBC, необходимо создать источник данных. Но мы пойдем по несколько некорректному, но упрощенному пути. Для создания примера мы не будем создавать ни источник данных, ни саму базу данных, а воспользуемся уже готовым продуктом. А именно, возьмем поставляемую с Microsoft Access базу данных Northwind и созданный для нее источник данных.
После этого небольшого отступления переходим непосредственно к созданию каркаса приложения, "умеющего" работать с базами данных.
Рис. 20.1. Список инсталлированных драйверов ODBC
Прежде всего надо создать (или выбрать) саму базу данных. Однако, если пользователю, который знать не знает баз данных, предоставить просто удобный для программирования интерфейс, хорошего получится мало. Вывод отсюда такой — необходимо создание специализированных приложений, основная цель которых, помимо оптимальности выполнения запросов к системе управления базами данных, заключается в удобном интерфейсе, не требующем никаких специальных знаний о базах данных. И здесь неоценимую помощь может оказать именно Visual C++, с помощью которого такое приложение создать достаточно просто.
Первым шагом на этом пути является выбор в качестве проекта MFC AppWizard, первое окно которого представлено на рис. 20.2.
Рис. 20.2. Первое окно мастера MFC AppWizard
Начнем с создания самого простого приложения для работы с базой данных. Поэтому выберите переключатель Single document и нажмите кнопку Next. В результате мы перейдем к самому интересному для нас окну мастера MFC AppWizard — Step 2 of 6, показанному на рис. 20.3 — именно здесь мы будем определять параметры базы данных.
Первое, что сразу бросается в глаза — это группа переключателей What database support would you like to include? (Какую поддержку базы данных вы хотите включить в проект) и кнопка Data Source... (Источник данных). Сначала рассмотрим поддержку. Здесь возможны четыре варианта:
Выберите переключатель Database view with file support.
Теперь необходимо указать источник данных, с которым мы будем работать. Для этого нажмите кнопку Data Source..., после чего откроется диалоговое окно Database Options (Параметры базы данных), показанное на рис. 20.4.
Обратите внимание, что здесь можно выбирать не только источник данных, но и тип доступа. Установите переключатель на ODBC и в комбинированном списке выберите источник данных (MS Access 97 Database). Теперь требуется выбрать один из переключателей Recordset type (Тип набора записей). Набор записей (Recordset) — это, говоря несколько упрощенно, выборка из таблицы (или запроса) базы данных. Существуют три вида набора записей:
Рис. 20.3. Диалоговое окно MFC AppWizard — Step 2 of 6
Рис. 20.4. Диалоговое окно Database Options
Для нашего примера установите переключатель Snapshot и нажмите кнопку ОК. После регистрации соединения с базой данных вы увидите блок диалога Select Database Tables (Выберите таблицы базы данных), аналогичный представленному на рис. 20.5, где необходимо выделить те таблицы или запросы (queries), которые требуется включить в набор записей. В нашем простейшем случае это пока одна таблица Products.
Рис. 20.5. Диалоговое окно Select Database Tables
После нажатия на кнопку ОК вы вернетесь к диалоговому окну MFC AppWizard — Step 2 of 6, где, правда, изменилась надпись под кнопкой Data Source..., которая теперь сообщает о выбранных таблицах. На данном этапе конфигурирование приложения для использования базы данных Northwind завершается.
Три следующие окна мастера для нас неинтересны, поэтому просто четыре раза нажмите кнопку Next. В результате на экране появится диалоговое окно MFC AppWizard — Step 6 of 6, аналогичное показанному на рис. 20.6.
В отображенном списке выделите элемент CDBView, чтобы обратить внимание на его базовый класс — CRecordView, с которым мы подробно познакомимся чуть ниже. Нажмите кнопку Finish, и Visual C++ отобразит диалоговое окно New Project Information.
Примечание
Безусловно, мастер AppWizard выполняет огромную часть работы по созданию приложения. Но наполнять его содержимым в любом случае придется вам самим. Если сейчас скомпилировать (или как рекомендует называть этот процесс фирма Microsoft) построить приложения, то никаких данных из базы данных вы не увидите (рис. 20.7).
Рис. 20.6. Диалоговое окно MFC AppWizard — Step 6 of 6
Рис. 20.7. Приложение, созданное мастером AppWizard, не предоставляет пользователю никакой информации о содержимом базы данных
А теперь перейдем к непосредственному рассмотрению классов библиотеки MFC, предназначенных для работы с базами данных с использованием механизма ODBC.
Эти классы, взаимодействуя с другими классами приложения, обеспечивают простой доступ к различным базам данных, использующим драйверы ODBC. Приложениям, которые работают с такими базами данных, следует иметь в своем составе, по крайней мере, два класса — CDatabase и CRecordset. С них мы и начнем.
Объекты этого класса используются для соединения с базами данных, посредством которого можно манипулировать источником данных (рис. 20.8). Чтобы у вас сразу же сложилось представление о заложенных в этот класс возможностях, приведу список категорий, на которые можно условно разделить все его компоненты и методы:
Рис. 20.8. Место класса CDatabase в иерархии библиотеки MFC
CDatabase::CDatabase ()
Служит для создания объекта CDatabase:
class CPublDoc : public CDocument
{
public:
// Объявляем объект CDatabase в документе
CDatabase m_dbPubl;
..
};
После того как объект создан, необходимо установить соединение с определенным источником данных, для чего следует вызвать одну из приведенных ниже функций.
virtual BOOL CDatabase::Open {
LPCTSTR IpszDSN,
BOOL bExclusive = FALSE,
BOOL bReadOnly = FALSE,
LPCTSTR IpszConnect = "ODBC;",
BOOL bUseCursorLib = TRUE),
Параметр IpszDSN определяет имя источника данных, которое должно быть зарегистрировано с помощью программы ODBC Administrator. Это значение должно быть равно NULL, если DSN (Data Source Name, Имя источника данных) определено в строке IpszConnect, или может быть равно NULL, если необходимо предоставить пользователю блок диалога для выбора источника данных. Параметр bExclusive (в Visual C++ 5.0 не поддерживается): источник данных всегда открывается для совместного использования, и значение параметра должно быть равно FALSE, в противном случае будет выдано сообщение об ошибке. Параметр bReadOnly позволяет установить соединение с источником данных в режиме "только для чтения" (TRUE), что приводит к запрещению его обновления. После установления такого соединения все зависимые результирующие множества наследуют этот атрибут. Параметр IpszConnect определяет строку, описывающую соединение, которая содержит информацию об источнике данных, идентификаторе пользователя, имеющего к нему доступ, пароль, если он требуется источнику данных, и другую информацию. Для совместимости с будущими версиями требуется, чтобы эта строка начиналась с подстроки "ODBC", указывающей на то, что соединение устанавливается с источником данных ODBC. Параметр bUseCursorLib указывает на необходимость (TRUE) или необязательность загрузки динамической библиотеки ODBC Cursor Library, позволяющей работать с курсорами базы данных.
Упрощенная версия рассмотренной функции имеет вид:
virtual BOOL CDatabase::OpenEx (
LPCTSTR IpszConnectString,
DWORD dwOptions =0)
Параметр IpszConnectString определяет строку соединения с источником данных ODBC, которая включает его имя, а также дополнительную необязательную информацию, такую как идентификатор и пароль пользователя, например, "DSN=Publisher;UID=sa;PWD=irishka". Если в качестве параметра передается NULL, то выводится блок диалога Data Source, в котором пользователь может выбрать источник данных. Параметр dwOptions — битовая маска, которая определяет комбинацию следующих значений:
CDatabase::openExclusive
B Visual C++ до версии 6.0 не поддерживается — источник данных всегда открывается для совместного использования. При задании этой опции будет выдано сообщение об ошибке
CDatabase::openReadOnly
Источник данных открывается в режиме "только для чтения"
CDatabase::openUseCursorLib
Указывает на необходимость загрузки динамической библиотеки ODBC Cursor Library, позволяющей работать с курсорами
CDatabase::noOdbcDialog
He выводить блок диалога соединения
CDatabase::forceOdbcDialog
Всегда выводить блок диалога соединения
Значение, заданное по умолчанию (0), означает, что база данных открывается для совместного использования, с доступом для записи, динамическая библиотека поддержки курсора не загружается и блок диалога для выбора источника данных отображается только в том случае, если не указана дополнительная информация о соединении.
Как видите, обе функции выполняют одну и ту же задачу — установить соединение с источником данных. Разница заключается только в способе задания параметров.
Чтобы закончить эту тему, рассмотрим несколько примеров:
// Создаем объект класса CDatabase
CDatabase m_dbSamp;
// Открываем соединение с источником данных, указав имя источника
и идентификатор пользователя (без пароля)
m_dbSamp.Open(_T("Samples"), FALSE, FALSE, _T("ODBC;UID=sa");
// или запрашиваем всю информацию у пользователя
m_dbSamp.Open(NULL);
К этой же категории относится и функция закрытия соединения после того, как работа с ним закончена:
virtual void CDatabase::Close()
Эта функция не имеет параметров и не возвращает никакого значения:
// Закрываем текущее
m_dbSamp.Close()
// ... и открываем новое соединение
m_dbPubl.OpenEx(_T("DSN=Authors;UID=sa"),
CDatabase::openReadOnly I
CDatabase::noOdbcDialog));
Но, как вы понимаете, никому не нужно просто открыть и закрыть соединение с источником данных. Поэтому рассмотрим методы остальных перечисленных категорий класса CDatabase.
Входящие в эту категорию функции используются для предоставления информации о соединении, драйвере и источнике данных, а также для установки некоторых опций источника данных. Наиболее часто они применяются в интерфейсных приложениях. Рассмотрим основные из них.
const CStringS CDatabase::GetConnect()
Вызов этой функции позволяет получить описывающую соединение строку, которая использовалась во время вызова функций открытия соединения (Open или ОрепЕх). Если до момента вызова этой функции соединение не было установлено, то возвращается ссылка на пустую строку.
BOOL CDatabase::IsOpen ()
Позволяет определить, имеется ли (возвращается ненулевое значение) или нет (0) текущее соединение объекта CDatabase с источником данных.
BOOL CDatabase::CanUpdate ()
Устанавливает, может ли пользователь обновлять базу данных. Сама возможность обновления определяется двумя факторами — возможностями драйвера ODBC (не все драйверы предоставляют возможность обновления) и режимом, в котором была открыта база данных. Если такая возможность есть, то функция возвращает ненулевое значение, а в противном случае 0. Фактор, влияющий на возможность обновления, можно задать, вызвав функцию ::SQLGetlnfo с параметром SQL_DATASOURCE_READ_ONLY.
Функции этой категории используются для непосредственной работы с базой данных. К ним относятся функции обработки транзакций (используемые для обновления базы данных) и непосредственного выполнения команд SQL:
void CDatabase::ExecuteSQL (LPCSTR IpszSQL)
Позволяет непосредственно выполнить команду SQL, задаваемую в завершающейся нулем строке, на которую указывает параметр JpszSQL В качестве параметра можно использовать объект класса CString. Следует иметь в виду, что выполнение этой функции не возвращает записей из базы данных и, следовательно, не рекомендуется для выполнения операций выборки:
CString strCmd = "UPDATE Products SET UnitPrice = 30";
try
(
m__dbCust. ExecuteSQL (strCmd) ;
}
catch(CDBException, e)
{
// Код ошибки находится в e->m_nRetCode
}
Другие составляющие класса CDatabase мы рассмотрим в следующей главе. непосредственно при разработке нашего приложения. А пока познакомимся с классом, который позволяет работать с наборами записей (рис. 20.9).
Рис. 20.9. Место класса CRecordset в иерархии библиотеки MFC
Все компоненты и методы этого класса можно разбить на семь категорий:
В классе CRecordset определены следующие основные компоненты данных:
UINT CReqordset::m_nFields
Содержит число полей данных в результирующем наборе — число столбцов, получаемых из источника данных. Это поле должно быть корректно инициализировано в конструкторе класса CRecordset.
Примечание
Если воспользоваться мастером ClassWizard, то это будет сделано автоматически.
UINT CRecordset::m_nParams
Содержит число параметров в результирующем наборе — число параметров, посылаемых в параметризированном запросе результирующего набора. Если эта переменная используется, то она должна быть корректно инициализирована в конструкторе. По умолчанию инициализируется нулем.
CDatabase CRecordset::m_pDatabase
Содержит указатель на объект класса CDatabase, посредством которого результирующий набор соединяется с источником данных. Эта переменная устанавливается двумя способами: если вы уже установили соединение с источником данных, то при создании объекта класса CRecordset передайте туда указатель на объект класса CDatabase или же можно передать туда NULL — при этом CRecordset сам создаст объект CDatabase и соединится с ним. В обоих случаях указатель на базу данных хранится в этой переменной m_pDatabase. И хотя обычно нет необходимости отслеживать ее состояние, все же есть случаи, когда делать это необходимо, например, при запуске транзакции или непосредственного выполнения оператора SQL.
CString CRecordset::m_strFilter
Используется в качестве фильтра, что позволяет выбирать только записи, удовлетворяющие заданному критерию. Для осуществления "фильтрации" эту переменную следует определять после создания объекта класса CRecordset, но до вызова функции Open, или же "перечитать" запрос с помощью функции Requery.
Примечание
Для тех, кто знаком с языком SQL, скажу, что эта строка служит для определения предложения WHERE оператора SQL. Ключевое слово WHERE не нужно включать в строку фильтра, поскольку библиотека подставляет его сама. Те же читатели, кто еще не познакомился с "языком запросов" к базе данных SQL, должны обязательно это сделать. Думаю, что причины объяснять не надо, и поэтому только порекомендую свою книгу "Microsoft SQL Server 7.0", в которой приведено подробное и простое описание этого языка.
CString CRecordset: :m_strSort
Используется в качестве фильтра, позволяющего сортировать записи, удовлетворяющие заданному критерию. Для того чтобы осуществить "сортировку", эту переменную следует определять после создания объекта класса CRecordset, но до вызова функции Open, или же "перечитать" запрос с помощью функции Requery.
Примечание
Эта строка служит для определения предложения ORDER BY оператора SQL. He нужно включать в строку фильтра ключевое слово ORDER BY, поскольку библиотека подставляет его сама.
Помимо перечисления полей, по которым производится сортировка, можно указать так же одно из ключевых слов (отдельно для каждого поля), определяющего направление сортировки: ASC — по возрастанию и DESC — по убыванию.
В эту категорию входят всего три функции: конструктор, Open и Close, отвечающие за создание объекта, открытие и закрытие соединения с источником данных.
CRecordset::CRecordset(CDatabase* pDatabase = NULL)
Служит для создания и инициализации объекта класса CRecordset. В качестве параметра в конструктор можно передать либо указатель на открытую базу данных (параметр pDatabase), либо NULL, что говорит о необходимости создания : базы данных по умолчанию. !
Примечание
При создании производного от CRecordset класса у него должен быть только один конструктор, по параметрам совпадающий с базовым.
virtual BOOL CRecordset::Open (
UINT nOpenType = AFX_DB_USE_DEFAULT_TYPE,
LPCTSTR IpszSQL = NULL,
DWORD dwOptions = none)
При успешном выполнении функции возвращается ненулевое значение и 0 — в противном случае. Если полученный результирующий набор не пустой, то текущей является первая запись. Параметр nOpenType определяет тип доступа к источнику данных и может принимать следующие значения:
CRecordset: :dynaset
Результирующий набор с возможностью двунаправленного просмотра. При этом режиме изменения, вносимые в базу данных другими пользователями, отображаются сразу же. К сожалению, декларированное поведение результирующего набора при этом режиме не поддерживается
CRecordset:snapshot
Статический результирующий набор с возможностью двунаправленного просмотра. При этом режиме изменения, вносимые в базу данных другими пользователями, отображаются не сразу, а только после закрытия и повторного открытия результирующего набора
CRecordset: :dynamic
Результирующий набор с возможностью двунаправленного просмотра. При этом режиме изменения, вносимые в базу данных другими пользователями, отображаются при выполнении следующей операции. Многие драйверы ODBC не поддерживают этот режим доступа
CRecordset: :forwardOnly
Результирующий набор "только для чтения" с возможностью просмотра "только вперед"
Для класса CRecordset значение этого параметра по умолчанию — CRecordset:: snapshot.
Примечание
Если выбранный тип не поддерживается, то происходит обработка исключения.
Параметр IpszSQL — указатель на строку, содержащую одно из значений: NULL, имя таблицы, оператор SQL, не обязательно с предложениями WHERE или ORDER BY, или оператор CALL, определяющий имя предопределенного запроса или сохраненной процедуры.
Примечание
Порядок столбцов в результирующем наборе должен совпадать с порядком вызова в переопределенной функции DoFieldExchange.
Параметр dwOptions— битовая маска, которая определяет комбинацию перечисленных ниже значений:
CRecordset::none
Опции не установлены. Это значение параметра не может использоваться с другими значениями. По умолчанию результирующий набор можно редактировать (Edit) и удалять (Delete), а также добавлять в него новые записи (AddNew). Возможность обновления зависит от источника данных, а также от режима открытия результирующего набора
CRecordset::appendOnly
Нельзя редактировать и удалять записи результирующего набора. Можно только добавлять новые записи. Не может использоваться совместно со значением CRecordset:: readOnly
CRecordset::readonly
Результирующий набор открывается в режиме "только для чтения". Не может использоваться совместно со значением CRecordset:appendOnly
CRecordset: :optimizeBulkAdd
Для оптимизации добавления нескольких записей за один раз используется подготовленный оператор SQL. Это значение можно использовать только, если для обновления результирующего набора не используется функция ODBC API SQLSetPos. Не может использоваться совместно со значением CRecordset::useMultiRowFetch
CRecordset::useMultiRowFetch
Позволяет извлекать несколько строк в одной операции выборки — блочная выборка строк. Не может использоваться совместно с CRecordset::optimizeBulkAdd. Если в качестве параметра установлена эта опция, то автоматически устанавливается опция CRecordset::noDirtyFieldCheck, а для результирующих наборов с возможностью просмотра "только вперед" (CRecordset:: forwardOnly) устанавливается также опция CRecordset: :useExtendedFetch
CRecordset::skipDeletedRecords
Пропускает все удаленные записи при перемещении по результирующему набору. Эта опция недоступна для результирующих наборов с возможностью просмотра "только вперед". Записи, удаленные другими пользователями в то время, пока результирующий набор открыт, не пропускаются
CRecordset::useBookmarks
Если источник данных поддерживает закладки (Bookmarks), то для работы с ними необходимо указать эту опцию. Закладки замедляют выборку данных, но вместе с тем улучшают производительность при перемещении по записям. Недоступна для результирующих наборов с возможностью просмотра "только вперед"
CRecordset: :noDirtyFieldCheck
Выключает автоматическую проверку "грязных" полей (двойная буферизация), что позволяет улучшить производительность. При этом отмечать поля как "грязные" необходимо вручную с помощью функций SetFieldDirty и SetFieldNull. Для результирующих наборов недоступна двойная буферизация отдельных полей — действует принцип "или все, или ничего". Эта опция устанавливается автоматически, если задается опция CRecordset::useMultiRowFetch. Однако в этом случае нельзя использовать функции SetFieldDirty и SetFieldNull для результирующих наборов, поддерживающих блочную выборку строк
CRecordset:rexecuteDirect
He следует использовать эту опцию для подготовленных операторов SQL. Если не предполагается вызывать функцию Requery, то определение этой опции позволит повысить производительность
CRecordset::useExtendedFetch
Установка этой опции приводит к тому, что вместо SQLFetch выполняется SQLExtendedFetch. Она спроектирована для реализации блочной выборки строк из результирующих наборов с возможностью просмотра "только вперед" и устанавливается автоматически при задании опции CRecordset::useMultiRowFetch
CRecordset::userAllocMuttiRowBuffers
При установке этой опции пользователю необходимо будет самому выделять память в буфере для хранения данных. Ее следует использовать вместе с опцией CRecordset::useMultiRowFetch
Функцию С Recordset:: Open следует вызывать для выполнения запроса, определяющего результирующий набор. Очевидно, что перед ее вызовом объект класса CRecordset должен быть создан. Более того, соединение с источником данных зависит от того, как именно он создан:
Примечание
Доступ к источнику данных посредством объекта CRecordset никогда не может быть эксклюзивным.
При вызове функции Open выполняется запрос, обычно оператор SELECT, в результате чего выбираются записи на основе критериев, перечисленных ниже.
Значение параметра IpszSQL |
Выбираемые записи определяются |
Пример |
NULL |
Строкой, возвращаемой функцией GetDefauttSQL |
|
Имя таблицы |
Столбцами, указанными в функции DoFieldExchange |
"Products" |
Имя предопределенного запроса (сохраненной процедуры) |
Столбцами, определенными в запросе |
"(call ProductList)" |
SELECT список-столбцов FROM список-таблиц |
Заданными столбцами из определенных таблиц |
SELECT ProductName, UnitPrice FROM Products |
Примечание
При написании строки SQL необходимо тщательно следить за тем, чтобы в нее не попали дополнительные пробелы. Например, если между круглой скобкой и ключевым словом CALL (а также SELECT) будет хоть один пробел, то библиотека MFC неправильно поймет имя таблицы, что приведет к обработке исключения (исключительной ситуации). Аналогичная ситуация возникает, если пробел появится между круглой скобкой и символом '?' в параметризованном запросе.
Перед вызовом функции Open можно определить дополнительные условия, соответствующие параметрам предложений WHERE и ORDER BY оператора SQL, используя для этого описанные выше переменные класса m_strFilter и m_strSort. Если же их определить после того, как результирующий набор открыт, то они не окажут никакого влияния на результат выполнения запроса. В этом случае необходимо будет воспользоваться функцией Requery для обновления записей.
virtual void CRecordset::Close ()
Используется для закрытия результирующего набора. Если он не был открыт, то функция просто завершается. После ее выполнения вся память, выделенная для результирующего набора, и дескриптор HSTMT ODBC, ассоциированный с ним, возвращается системе. В том случае, когда объект класса CRecordset создавался с помощью оператора new, вызов функции Close осуществляется автоматически при удалении объекта.
Атрибуты результирующего набора
В эту категорию входят функции, работающие непосредственно с атрибутами CRecordset и позволяющие получить информацию о результирующем наборе.
BOOL CRecordset::CanAppend ()
Вызов этой функции позволяет определить, можно ли добавлять записи в результирующий набор. Да — если она возвращает ненулевое значение и нет — если 0. Для добавления записей необходимо вызвать функцию AddNew.
BOOL CRecordset::CanBookmark ()
Вызов этой функции позволяет определить, можно ли отмечать записи в результирующем наборе с помощью закладок. Да — если она возвращает ненулевое значение, и нет— если 0. Результат работы функции не зависит от установки опции CRecordset::useBookmarks параметра dwOptions функции Open, и определяется только тем, поддерживают ли закладки используемый драйвер QDBC.
Примечание
Закладки не поддерживаются в результирующих наборах с возможностью просмотра "только вперед".
BOOL CRecordset::CanRastart 0
Позволяет определить, можно ли обновлять данные в результирующем наборе, используя для этого функцию Requery. Да — если она возвращает ненулевое значение, и нет — если 0.
BOOL CRecordset::CanUpdate ()
Позволяет определить, можно ли обновлять записи в результирующем наборе. Да — если она возвращает ненулевое значение, и нет — если 0. Обновление не поддерживается, если источник данных "только для чтения" или если результирующий набор открыт в режиме "только для чтения" путем установки значения CRecordset::readOnly параметра dwOptions.
long CRecordset::GetRecordCount ()
Позволяет определить размер результирующего набора. Она возвращает или число записей в результирующем наборе, или 0 — если он не содержит записей, или —1, если число записей не может быть определено.
Примечание
Общее число записей в результирующем наборе может быть определено только после того, как приложение переместилось за последнюю запись. Это число не обновляется при вызове функции MoveLast. Добавление записей посредством вызова функций AddNew и Update увеличивает это число, а удаление посредством функции Delete — уменьшает его.
void CRecordset::GetStatus(CRecordsetStatusS rStatus) const
Позволяет определить индекс текущей записи в результирующем наборе и/или найдена ли последняя запись. В качестве параметра функция принимает ссылку на структуру CRecordsetStatus
struct CRecordsetStatus{
long m_lCurrentRecord;
BOOL m_bRecordCountFinal;
}
Поля этой структуры имеют следующий смысл:
• m_ICurrentRecord содержит индекс (начиная с 0) текущей записи в результирующем наборе. Если индекс Не может быть определен, то в это поле записывается значение AFX_CURRENT_RECORD_UNDEFINED (-2). Если результирующий набор пустой или была выполнена попытка переместиться до первой записи, то в поле записывается AFX_CURRENT_RECORD_BOF (-1).
• m_bRecordCountFinal содержит ненулевое значение, если было определено общее число записей в результирующем наборе, 0 — если он не содержит записей, и -1, если число записей не может быть определено.
const CStringS CRecordset::GetTableName ()
Позволяет получить имя таблицы, на которой основывается запрос результирующего набора, или пустую строку. Эта функция работает корректно только в том случае, если результирующий набор базируется на таблице, и не работает, если в основе лежит объединение нескольких таблиц или предопределенный запрос (сохраненная процедура).
const CStringS CRecordset::GetSQL ()
Позволяет получить оператор SQL, который используется для выборки записей результирующего набора. Чаще всего это оператор SELECT. Возвращаемая функцией строка, естественно, отличается от той, которая передавалась в качестве параметра IpszSQL функции Open. Связано это с тем, что CRecordset создает полное предложение SQL, базируясь на информации, переданной функции Open, тем, что было определено с помощью ClassWizard, на значениях, записанных в переменные m_strFilter и m_strSort, а также на специфицированных параметрах запроса.
BOOL CRecordset::IsOpen ()
Позволяет определить, открыт ли уже результирующий набор. Хорошим стилем программирования является использование этой функции перед тем, как вызывать функцию Open.
BOOL CRecordset::IsBOF ()
Позволяет определить, является ли текущая запись первой в наборе данных. Она возвращает ненулевое значение, если результирующий набор не содержит записей или указатель помещен до первой записи, и 0 — в противном случае. Если функция возвратила ненулевое значение, то текущая запись не определена, и при вызове функции MovePrev возникает ошибка.
BOOL CRecordset::IsEOF ()
Позволяет определить, является ли текущая запись последней в наборе данных. Возвращает ненулевое значение, если результирующий набор не содержит записей или указатель помещен за последней записью, и 0 — в противном случае. Если функция возвратила ненулевое значение, то текущая запись не определена, и при вызове функции MoveNext возникает ошибка.
BOOL CRecordset::IsDeleted ()
Позволяет определить, была ли текущая запись удалена. Если при перемещении на запись и вызове этой функции она возвращает ненулевое значение, необходимо перейти к другой записи до того, как производить какие-либо операции над результирующим набором.
Эту функцию не следует использовать при работе с блочной выборкой строк. Вместо нее следует вызывать функцию GetRowStatus.
Примечание
Результат, возвращаемый функцией IsDeleted, зависит от многих факторов, таких как тип результирующего набора, можно ли его обновлять, определена ли при открытии опция CRecordset::skipDeletedRecords, позволяет ли используемый драйвер удалять записи и каково число работающих пользователей.
Операции обновления результирующего набора
В эту категорию входят пять функций, которые позволяют добавлять, удалять и редактировать записи в результирующем наборе.
virtual void CRecordset::AddNew ()
Используется для подготовки новой записи к добавлению в результирующий набор. Все поля записи исходно устанавливаются в Null (в терминологии баз данных это означает " значение не задано" и отличается от смысла NULL в C++).
Чтобы сохранить внесенные в эту пустую запись изменения в источнике данных, необходимо вызвать функцию Update.
Примечание
Если перейти к другой записи до вызова Update, новая запись будет потеряна без какого-либо предупреждения.
Для динамического набора (dynaset) новая запись автоматически добавляется в конец результирующего набора. Чтобы увидеть эту новую запись в "мгновенном снимке", необходимо вызвать функцию Requery.
При попытке вызвать функцию Update для результирующего набора, который еще не открыт, а также, если он не поддерживает режим обновления (проверяется вызовом функции CanUpdate), происходит обработка исключения.
virtual void CRecordset::Delete ()
Удаляет текущую запись. После успешного удаления необходимо вызвать одну из функций перемещения по записям, чтобы корректно завершить операцию. Например, если этого не сделать и снова вызвать функцию Delete, то произойдет обработка исключения.
Примечание
В отличие от функций AddNew и Edit вызов функции Delete не требует вызова Update. Если по каким-либо причинам вызов этой функции завершился неудачно, поля данных просто остаются без изменений.
virtual void CRecordset::Edit ()
Позволяет изменять поля текущей записи. Для корректного завершения операции обновления записи и сохранения изменений в источнике данных необходимо вызвать функцию Update. Если переместиться к другой записи до вызова Update, восстанавливаются предыдущие значения текущей записи. В тех случаях, когда требуется записать в некоторый столбец значение Null (не содержит данных), необходимо вызвать функцию SetFieldNull с параметром TRUE. При необходимости оставить без изменений какое-либо поле вызовите функцию SetFieldDirty с параметром TRUE, даже если в нем было записано значение Null.
В зависимости от текущего режима блокировки запись может быть блокирована либо пока не вызвана функция Update или до перемещения к другой записи, либо только во время вызова функции Edit Режим блокировки можно изменить с помощью функции SetLockingMode.
virtual BOOL CRecordset::Update ()
Вызов этой функции является обязательным для корректного завершения операций добавления и обновления записей в результирующий набор и возвращает ненулевое значение, если запись была успешно обновлена, и 0 — в противном случае. Обе функции AddNew и Edit подготавливают специальный буфер, в котором собственно и происходит добавление и редактирование данных. Вызов функции Update сохраняет эти данные в источнике данных.
Примечание
Если вызвать функцию Update до вызова AddNew или Edit, возникает исключение.
Все рассмотренные выше функции (AddNew, Edit, Delete и Update) можно включать в транзакции, если работу с ними поддерживает результирующий набор.
void CRecordset::CancelUpdate ()
Позволяет сбросить любое "повисшее" обновление, осуществленное операциями AddNew или Edit, до вызова функции Update, Если установлен режим автоматической отметки поля как "грязного", устанавливаемый по умолчанию при открытии результирующего набора, то CancelUpdate восстанавливает переменные в те значения, которые они имели до вызова функции Edit или AddNew. В противном случае будут оставлены измененные значения (для этого при вызове функции Open необходимо установить параметр dwOptions в значение CRecordset::noDirtyFieldCheck).
Примечание
Использование функций AddNew, Edit, Delete, Update и CancelUpdate при работе с записями в режиме блочной выборки строк приведет к ошибке доступа. В классе CRecordset не реализован механизм для обновления блоков строк данных. В этом случае необходимо воспользоваться функциями ODBC API.
Операции перемещения по результирующему набору
К основным функциям этой категории относятся следующие: Move, MoveFirst, Move Last, Move Next и MovePrev. Их мы и рассмотрим.
virtual void CRecordset::Move (
long nRows,
WORD wFetchType = SQL_FETCH_RELATIVE)
Функция имеет два параметра: nRows — количество строк, на которое необходимо переместиться вперед (положительное значение) или назад (отрицательное) и wFetchType — определяет набор строк, которые функция должна выбрать.
wFetchType |
Выбираемый набор строк |
SQL_FETCH_RELATIVE (значение по умолчанию) SQL_FETCH_NEXT SQL_FETCH_PRIOR SQL_FETCH_FIRST SQL_FETCH_LAST |
Набор строк включает nRows строк, начиная с первой в текущем наборе Следующий набор строк. Параметр nRows игнорируется Предыдущий набор строк. Параметр nRows игнорируется Первый набор строк. Параметр nRows игнорируется Последний набор строк. Параметр nRows игнорируется |
SQL_FETCH_ABSOLUTE
SQL_FETCH_BOOKMARK |
Если nRows > 0, то набор строк включает nRows строк, начиная с начала результирующего набора. Если nRows < 0, то набор строк включает nRows строк, начиная с конца результирующего набора. Если nRows = 0, то возвращается условие начало файла (ВОР) Набор строк начинается со строки, значение закладки которой соответствует nRows |
Если результирующий набор не содержит записей или предпринимается попытка переместиться за начало или конец результирующего набора, то возникает исключение.
Чтобы как следует разобраться с работой функции Move, рассмотрим фрагмент кода.
// rs — объект класса CRecordset или производного от него
// Устанавливаем размер набора строк в значение 5
rs.SetRowsetSize(5);
// Переходим к первой записи в результирующем наборе
rs.MoveFirst ();
// Переходим к шестой записи
rs.Move(5);
//К ней же можно было перейти и другим способом
// гs.Move(6, SQL_FETCH_ABSOLUTE);
// rs.SetAbsolutePosition(6);
// После установки абсолютной позиции на шестую запись
// она становится первой в следующем наборе строк;
// поэтому следующие вызовы эквивалентны
// гs.Move(1, SQL_FETCH_NEXT);
// rs.MoveNext();
Примечание
Для результирующего набора с возможностью просмотра "только вперед" разрешено только одно значение параметра wFetchType— SQL_FETCH_NEXT.
void CRecordset::MbveFirst ()
Делает текущей первую запись результирующего набора. Перед использованием данной функции рекомендуется вызвать функцию IsBOF.
void CRecordset::MoveLast ()
Делает текущей последнюю запись результирующего набора. Перед использованием данной функции рекомендуется вызвать функцию IsEOF.
void CRecorciset: rMoveNext ()
Делает текущей первую запись следующего набора строк. Перед использованием рекомендуется вызвать функцию IsBOF. Если работа идет не с блоками, а с отдельными записями, то текущей становится следующая запись.
void CRecordset::MovePrev ()
Делает текущей первую запись предыдущего набора строк. Перед ее использованием рекомендуется вызвать функцию IsEOF. При работе не с блоками, а с отдельными строками текущей становится предыдущая запись.
Из оставшихся трех функций этой категории, использующихся не так часто, однако тоже весьма полезных, рассмотрим две:
void CRecordset::GetBookmark (CDBVariantS varBookmark)
Позволяет получить значение закладки текущей записи, которое записывается по ссылке в передаваемый ей параметр.
void CRecordset::SetBookmark (const CDBVariants varBookmark)
Позволяет перейти к записи, содержащей определенную (varBookmark) закладку. Чтобы получить закладку для текущей записи, необходимо вызвать функцию GetBookmark, после чего можно перейти непосредственно к этой записи, вызвав функцию SetBookmark с сохраненным значением закладки.
Если закладки не поддерживаются, то вызов этих двух функций приведет к возникновению исключения. Если же они поддерживаются, то, чтобы сделать их доступными, необходимо при вызове функции Open задать значение CRecordset::useBookmarks для параметра dwOptions.
Примечание
После определенных операций с результирующим набором необходимо проверить сохранность закладки перед тем, как вызывать функцию SetBookmark. Например, если после получения закладки с помощью функции GetBookmark вы вызвали функцию Requery, то закладка может быть утеряна. Поэтому перед использованием закладки необходимо вызвать функцию CDatabase:: GetBookmarkPersistence, чтобы проверить ее сохранность.
Другие операции над результирующим набором
В эту категорию входят функции, которые трудно отнести к какому-то одному типу.
void CRecordset::Cancel ()
Вызов этой функции является просьбой к источнику данных отменить либо обрабатываемую асинхронную операцию, либо второй поток. Библиотека MFC не поддерживает асинхронные операции, и для них необходимо непосредственно вызвать функцию SQLSetConnectOption из ODBC API.
BOOL CRecordset::IsFieldDirty(void* pv)
Возвращает ненулевое значение, если поле данных было изменено после вызова AddNew или Edit, в противном случае 0. Проверяемое поле данных задается указателем pv, если это значение равно NULL, то проверяются все поля.
BOOL CRecordset::IsFieldNull (void* pv)
Возвращает ненулевое значение, если поле данных отмечено как содержащее значение Null (в терминологии баз данных), и 0 — в противном случае. Проверяемое поле данных задается указателем pv, если это значение равно NULL, то проверяются все поля.
BOOL CRecordset::IsFieldNullable(void* pv)
Возвращает ненулевое значение, если поле данных допускает значение Null (в терминологии баз данных), и 0 — в противном случае. Проверяемое поле данных задается указателем pv, если это значение равно NULL, то проверяются все поля.
virtual BOOL CRecordset::Requery ()
Позволяет обновить результирующий набор. Функцию следует вызывать для результирующего набора типа "мгновенный снимок" (snapshot) после того, как вы или другой пользователь внесли в него изменения. Для динамического (dynaset) результирующего набора обновление производится автоматически, кроме случая добавления новой записи. Кроме этого, ее следует вызывать при изменении значений параметров m_strFilter и/или m_strSort. При параметризованном запросе также следует использовать эту функцию. Если функция возвращает значение FALSE — ошибка, то результирующий набор закрывается.
Примечание
Функцию Requery можно вызывать только для открытого результирующего набора. Проверить это можно с помощью функции CanRestart.
void CRecordset::SetFieldDirty ( :
void* pv,
BOOL bDirty = TRUE)
Позволяет пометить поле данных результирующего набора (параметр pv) как измененное (bDirty = TRUE) или не изменившееся (bDirty = FALSE). Если в качестве pv передается NULL, помечаются все поля. Использование этой функции позволяет снизить трафик SQL, если изменились не все поля записи.
void CRecordset::SetPieldNull (
void* pv,
BOOL bNull = TRUE)
Позволяет пометить поле данных результирующего набора (параметр pv) как содержащие (bNull = TRUE) или не содержащие (bNull = FALSE) значение Null. Если в качестве pv передается NULL, помечаются все поля. При добавлении новой записи в результирующий набор все поля исходно устанавливаются в значение Null и помечаются как измененные.
Примечание
Функции SetFieldDirty и SetFieldNull следует вызывать только после вызова функций AddNew или Edit.
void CRecordset:rSetParairiNull (
int nlndex,
BOOL bNull = TRUE)
Позволяет установить параметр, заданный своим индексом nlndex, в значение Null (параметр bNull равен TRUE), или не Null (bNull - FALSE). Обычно эта функция используется в предопределенных запросах (сохраненных процедурах).
В эту категорию входят восемь функций, которые можно переопределить в каждом конкретном приложении, чтобы настроить его на выполнение текущих задач. Мы рассмотрим только некоторые из них.
virtual void CRecordset::DoFieldExchange (CFieldExchange* pFX)
Вызывается для организации обмена данными между полями результирующего набора и соответствующими столбцами текущей записи в источнике данных. В качестве параметра функция принимает указатель на объект CFieldExchange, который автоматически создается и передается библиотекой MFC.
Примечание
Эти функция доступна только в том случае, если для результирующего набора используется класс, производный от CRecordset. Если же применяется непосредственно класс CRecordset, то для получения данных необходимо вызывать функцию GetFieldValue.
Собственно обмен данными осуществляется с помощью механизма RFX (Record Field Exchange, Обмен полями записи), который работает в обоих направлениях: от полей данных результирующего набора к записям источника данных и наоборот. Ниже приведен фрагмент, демонстрирующий переопределение функции DoFieldExchange.
void CDBSet::DoFieldExchange(CFieldExchange* pFX)
{
//{{AFX_FIELD_MAP(CDBSet)
pFX->SetFieldType(CFieldExchange::outputColumn);
RFXJText(pFX, _T("[ProductName]"), m_ProductName);
RFX_Long(pFX, _T("[CategorylD]"), m_CategoryID);
RFX_Text(pFX, _T("[QuantityPerUnit]"), m_QuantityPerUnit);
RFX_Text(pFX, _T("[UnitPrice]"), m_UnitPrice);
//}}AFX_FIELD_MAP
}
Примечание
Мастер ClassWizard не поддерживает обмен полями блочных записей, и необходимо переопределить соответствующую функцию вручную, реализовав в ней вызовы функций Bulk RFX (блочный обмен полями записи). Функции для собственно обмена данными мы рассмотрим при обсуждении класса CFieldExchange.
virtual CString CRecordset::GetDefaultCormect ()
Библиотека MFC вызывает данную функцию, чтобы получить строку, содержащую источник данных, на котором базируется результирующий набор. Ниже приведен фрагмент кода, подробно рассматриваемый в следующей главе и иллюстрирующий использование этом функции.
CString CDBSet::GetDefaultCormect()
{
return _T("ODBC;DSN=MS Access 97 Database");
}
virtual CString CRecordset::GetDefaultSQL ()
Библиотека MFC вызывает эту функцию, чтобы получить строку, содержащую оператор SQL, на котором базируется результирующий набор. Это должно быть или имя таблицы, или непосредственно оператор SELECT. Можно также определить здесь вызов предопределенного запроса, используя оператор CALL. Ниже приведен фрагмент кода, подробно рассматриваемый в следующей главе, который иллюстрирует использование этой функции
CString CDBSet::GetDefaultSQL()
{
return _T("[Products]");
}
Если библиотека MFC не сможет найти имя таблицы или корректно интерпретировать оператор CALL, функция возвращает пустую строку.
Примечание
Между круглой скобкой и ключевым словом, например, CALL или SELECT, не должно быть пробелов.
Рассмотренные классы, безусловно, существенно облегчают работу с результирующими наборами, однако без предоставления этой информации пользователю, да еще в удобном виде, она мало что значит. К счастью, в библиотеке MFC реализовано множество классов представления (view), которые сделают эту работу достаточно простой (рис. 20.10).
Рис. 20.10. Классы представлений библиотеки MFC
Объекты этого класса предоставляют для изображения записей базы данных в элементах управления форму, которая непосредственно соединена с объектом CRecordset. Объекты CRecordView используют механизм DDX (Dialog Data Exchange, Обмен данными с блоком диалога) и RFX (Record Field Exchange, Обмен полями записей) для автоматического перемещения данных между элементами управления формы и полями результирующего набора. Кроме того, можно воспользоваться реализованными возможностями перемещения по записям и обновления текущей записи.
Все компоненты и методы этого класса можно условно разбить на три категории.
Ниже приведено описание каждой из этих категорий.
В эту категорию входит только конструктор, имеющий две реализации.
CRecordView::CRecordView(LPCSTR IpszTemplateName)
CRecordView::CRecordView(UINT nIDTemplate)
Создает объект класса. В качестве параметра конструктор принимает идентификатор шаблона блока диалога, задаваемый либо строкой (IpszTemplateName), либо номером (nIDTemplate). При создании класса, производного от CRecordView, в нем можно определить только один конструктор, в котором необходимо вызвать конструктор базового класса CRecordView::CRecordView с идентификатором ресурса в качестве параметра, как это показано в приведенном ниже фрагменте:
CDBView::CDBView() : CRecordView(CDBView::IDD)
{
//{{AFX_DATA_INIT(CDBView)
m_pSet = NULL; m_strPrice = _T("");
//}}AFX_DATAJTNIT
m_bAdd = FALSE;
m_nSort = ID_SORT_TITLE;
}
Функции этой категории позволяют получить информацию о представлении записи.
virtual CRecordset* CRecordView::OnGetRecordset ()
Возвращает указатель на объект CRecordset, ассоциированный с формой, позволяя тем самым работать с некоторым результирующим набором. Чисто виртуальная функция, которая требует обязательного переопределения. Причина этого понятна — библиотека не может знать, с каким результирующим набором, т. е. объектом класса CRecordset, вы работаете.
Ниже показан фрагмент кода, который выдает мастер AppWizard при создании приложения для работы с базой данных:
CRecordset* CDBView::OnGetRecordset()
(
return m_pSet;
}
BOOL CRecordView::IsOnFirstRecord ()
Позволяет определить, является ли текущая запись первой в результирующем наборе, ассоциированном с данной формой. Когда пользователь перемещается за первую запись, библиотека блокирует доступ к элементам пользовательского интерфейса для перемещения на первую и предыдущую запись. Реализация этой функции по умолчанию представлена в приведенном ниже фрагменте:
BOOL CRecordView::IsOnFirstRecord()
{
ASSERT_VALID(this);
CRecordsetStatus status;
OnGetRecordset()->GetStatus(status);
return status.m_lCurrentRecord == 0;
}
BOOL CRecordView::IsOnLastRaoord()
Позволяет определить, является ли текущая запись последней в результирующем наборе, ассоциированном с данной формой. Когда пользователь перемещается за последнюю запись, библиотека блокирует доступ к элементам пользовательского интерфейса для перемещения на последнюю и следующую запись. Реализация этой функции по умолчанию представлена в приведенном ниже фрагменте:
BOOL CRecordView::IsOnLastRecord()
{
ASSERT_VALID(this);
CRecordset* pRecordset = OnGetRecordset();
CRecordsetStatus status; > pRecordset->GetStatus(status);
if (!status.m_bRecordCountFinal)
return FALSE;
return((status.m_lCurrentRecord+l=pRecordset->GetRecordCount())) ;
}
В этой категории имеется единственная функция, позволяющая программисту перемещаться по записям результирующего набора.
virtual BOOL CRecordView::OnMove(UINT nlDMoveCorranand)
Позволяет изменять указатель на текущую запись, или, другими словами, перемещаться по записям результирующего набора и отображать его поля в элементах управления формы. Параметр nIDMoveCommand задает направление перемещения и может принимать следующие значения:
ID_RECORD_FIRST Переход к первой строке в результирующем наборе
ID_RECORD_LAST Переход к последней строке в результирующем наборе
ID_RECORD_NEXT Переход к следующей строке в результирующем наборе
ID_RECORD_PREV Переход к предыдущей строке в результирующем наборе
Реализация этой функции по умолчанию обновляет текущую запись источника данных, если пользователь изменил ее в форме.
Примечание
Если результирующий набор не имеет записей, то вызов функции On/Wove приводит к исключению. Поэтому перед ее использованием необходимо определить, имеются ли записи в результирующем наборе.
Как видите, функций совсем немного, но не следует забывать, что класс CRecordView базируется на многих других классах (рис. 20.10), откуда и наследует все их возможности.
Как видно из рис. 20.11, класс CFieldExchange не имеет базового.
Рис. 20.11. Место класса CFieldExchange в библиотеке MFC
Он используется для обмена данными между записями результирующего набора и переменными приложения, которые хранят столбцы данных. Для решения этой задачи класс реализует механизмы обмена полями записи RFX (Record Field eXchange) и обмена блоками полей записи Bulk RFX (Bulk Record Field eXchange).
Класс содержит всего две функции, которые мы и рассмотрим.
BOOL CFieldExchange::IsFieldType(UINT* pnField)
Позволяет определить, может ли текущая операция быть выполнена для текущего поля. Имеет один параметр — pnField, указатель на индекс поля, и возвращает ненулевое значение, если операция может быть выполнена.
Непосредственно в приложениях вызывать эту функцию практически нет необходимости, если пользоваться функциями RFX, где она применяется. Исключение составляет случай, когда вам необходимо написать некоторую свою функцию RFX. Ниже приведен фрагмент кода из файла <dbrfx.cpp>, демонстрирующий использование функции IsFieldType.
void AFXAPI RFX_Int(CFieldExchange* pFX, LPCTSTR szName, ints value)
{
ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
ASSERT(AfxIsValidString(szName));
UINT nField;
if (!pFX->IsFieldType(SnField))
return;
LONG"* plLength = pFX->m_prs->GetFieldLengthBuffer(
nField - I, pFX->m_nFieldType);
switch (pFX->m_nOperation)
{
case CFieldExchange::BindFieldToColumn:
{
#ifdef _DEBOG
// Принять все связанные поля ДО несвязанных полей
CODBCFieldlnfo* pODBCInfo =
&pFX->m_prs->m_rgODBCFieldInfos[nField - 1];
if (pODBCInfo->m_nSQLType != SQL_C_SHORT)
{
// Предупредить о возможном несовпадении схемы полей
if (afxTraceFlags & traceDatabase)
TRACE1("Warning: int converted from SQL type %ld.\n",
pODBCInfo->m_nSQLType); }
#endif // _DEBUG
}
default:
LDefault:
pFX->Default(szName, Svalue, plLength, SQL_C_LONG,
sizeof(value), 5);
return;
case CFieldExchange: : Fixup:
if (*plLength == SQLJTOLL_DATA)
{
pFX->m_prs->SetNullFieldStatus(nField — 1);
value = AFX_RFX__INT_PSEUDO_NULL;
}
return;
case CFieldExchange::SetFieldNull:
if ( (pFX->m__pvField == NULL &&
pFX->m_nFieldType = CFieldExchange::outputColumn) ||
pFX->m_pvField == &value)
{
if (pFX->m_bField)
{
// Помечаем поля, как допускающие значение Null
pFX->m_prs->SetNullFieldStatus(nField - 1);
value = AFX_RFX_INT_PSEUDO_NULL;
*plLength = SQL_NULL_DATA;
} else
{
pFX->m_prs->ClearNullFieldStatus(nField — 1) ;
*plLength = sizeof(value);
}
#ifdef _DEBUG
pFX->m_nFieldFound = nField;
#endif
}
return;
case CFieldExchange::MarkForAddNew:
// Можно принудительно записать псевдонулевое значение
// при маркировке поля как изменившееся ("грязное")
if (value != AFX_RFX_INT_PSEUDO_NULL)
{
pFX->m_prs->SetDirtyFieldStatus(nField — 1);
pFX->m_prs->ClearNullFieldStatus (nField - 1) ;
. }
return;
case CFieldExchange::MarkForUpdate:
if (value != AFX_RFX_INT_PSEUDO_NULL)
pFX->m_prs->ClearNullFieldStatus(nField - 1) ;
goto LDefault;
case CFieldExchange::AllocCache:
{
CFieldlnfo* plnfo = &pFX->m_prs->m_rgField!nfos[nField — 1];
// Данные кэшированы значением,
// нет необходимости в распределении памяти
pInfo->m_nDataType = AFX_RFX_INT;
}
return;
#ifdef _DEBUG
case CFieldExchange::DumpField:
*pFX->m_pdcDump « "\n" « szName « " = " « value; return;
#endif // _DEBUG
}
}
void CFieldExchange::SetFieldType(UINT nFieldType)
Используется для установки типа поля перед тем, как вызывать функцию RFX. Она имеет один параметр — nFieldType, типа enum, который может принимать одно из следующих значений:
CFieldExchange::outputColumn
Поле является выходным столбцом
CFieldExchange::inputParam
Входной параметр— значение, посылаемое в запрос или сохраненную процедуру результирующего набора
CFieldExchange::param
To же самое, что и CFieldExchange::inputParam
CFieldExchange::outputParam
Выходной параметр— значение, которое возвращается сохраненной процедурой результирующего набора
CFieldExchange::inoutParam
Входной/выходной параметр— значение, которое посылается и возвращается из сохраненной процедуры результирующего набора
Функцию SetFieldType необходимо вызывать в переопределенной функции класса результирующего набора. Для полей данных ее следует вызвать с параметром CFieldExchange::outputColumn, а затем уже реализовать вызовы функций RFX или Bulk RFX. Ниже приведен фрагмент, иллюстрирующий работу класса CFieldExchange:
void CSections::DoFieldExchange(CFieldExchange* pFX)
{
//{{AFX_FIELD_MAP(CSections)
pFX->SetFieldType(CFieldExchange::outputColumn);
RFXJText(pFX, "CourselD", m_strCourseID); .
RFX_Text(pFX, "InstructorlD", m_str!nstructorID);
RFX_Text (pFX, "RoomNo", m_strRooinNo) ;
RFXJText(pFX, "Schedule", m_strSchedule); //}}AFX_FIELD_MAP
// выходной параметр
pFX->SetFieldType(CFieldExchange::outputParam);
RFX_Long(pFX, "Instructor_Count", m_nCountParam);
// входной параметр
pFX->SetFieldType(CFieldExchange::inputParam) ;
RFX_Text(pFX, "Department_Name", m_strNameParam);
}
Если среди имеющихся функций нет тех, которые поддерживают необходимый вам тип данных, можно создать такую функцию самостоятельно, что совсем несложно, если в качестве основы воспользоваться существующими (приведенными в файле <dbrfx.cpp>). Пример одной из этих функций приведен выше.
На этом мы закончим знакомство с возможностями, предоставляемыми библиотекой MFC для создания приложений, работающих с базами данных. Практические примеры использования приведенного материала рассматриваются в следующей главе.