Просмотров: 85454

Как сделать sql запрос в excel


Глава из книги Кэнту М. (Marco Cantu): Delphi 7. Для профессионалов
Авторы рассылки благодарят издательский дом ПИТЕР за предоставленные к публикации материалы.

Аннотация издательства

Среда Delphi была и до сих пор является наилучшим сочетанием объектно-ориентированного и визуального программирования не только для Windows, но теперь уже и для Linux и в ближайшем будущем — и для.NET. В этой книге автор попытался практически полностью исключить справочный материал, сконцентрировавшись на технологиях эффективного использования Delphi. В книге приведено более 300 примеров. Как сказал один из подписчиков групп новостей, «книги Кэнту — это по сути „delphi.filtered“, только больше и лучше». Книга предназначена для программистов, разработчиков и всех, серьезно интересующихся программированием в среде Delphi.

Технология ADO (глава 15)

СОДЕРЖАНИЕ

С середины 1980-х годов программисты RDBMS пытаются найти «волшебный ключик» от двери, которая ведет в страну независимости от конкретной базы данных. Проблема состоит в том, что данные могут поступать из самых разных источников, каждый из которых обладает своей спецификой. Однако разработка приложений существенно упростилась бы, если бы удалось создать унифицированный механизм взаимодействия с самыми разными источниками данных. Это мог бы быть универсальный программный интерфейс API, который позволил бы программистам разрабатывать приложения, одинаковым образом взаимодействующие с различными источниками данных. Такие приложения можно было бы использовать для взаимодействия с самими разными системами RDBMS, а также с другими источниками данных. За истекшее время различными компаниями было предложено множество решений в этой области. Наиболее значительными являются Microsoft ODBC (Open Database Connectivity) и Borland IDAPI (Integrated Database Application Programming Interface). Технология Borland IDAPI больше известна под именем BDE (Borland Database Engine).
В середине 1990-х годов, с развитием и распространением технологии COM (Component Object Model), компания Microsoft объявила о постепенном переходе от ODBC к использованию новой технологии OLE DB. Однако OLE DB, по мнению самой компании Microsoft, является интерфейсом системного уровня, этот интерфейс должен использоваться системными программистами. Технология OLE DB является тяжеловесной, сложной и очень чувствительной к ошибкам. Она требует от программиста слишком многого. Работать с OLE DB слишком сложно. Чтобы облегчить работу с OLE DB, был создан дополнительный прикладной уровень, который получил название ADO (ActiveX Data Objects). Работать с ADO существенно проще, чем с OLE DB. Технология ADO предназначена для прикладных программистов.
В главе 14 уже говорилось о том, что компания Borland также решила заменить технологию BDE новой технологией под названием dbExpress. Следует отметить, что ADO по своим возможностям и идеологии в большей степени напоминает BDE. Как BDE, так и ADO поддерживают навигацию, манипулирование наборами данных, обработку транзакций, кэшируемые обновления (в ADO они называются batch updates (пакетные обновления)). Иными словами,концептуально и идеологически ADO и BDE являются похожими технологиями.

ПРИМЕЧАНИЕ

Я хотел бы поблагодарить Гая Смита Ферриера (Guy Smith Ferrier) за то, что он написал данную главу для книги Mastering Delphi 6 (Русское издание: Delphi 6. Для профессионалов. — СПб.: Питер, 2002. — Примеч. перев.). Гай — программист, автор книг и статей, кроме того, он выступает на конференциях. Он является автором нескольких коммерческих программных продуктов и многочисленных внутренних систем как для небольших, так и для крупных компаний. Он написал множество статей для журнала The Delphi Magazine, а также для других изданий. Кроме того, он неоднократно выступал на различных конференциях в Северной Америке и в Европе. Гай живет в Англии вместе с женой, сыном и кошкой.

В данной главе мы рассмотрим работу с ADO. Мы также рассмотрим dbGo — набор компонентов Delphi, который изначально назывался ADOExpress, однако в Delphi 6 был переименован, так как компания Microsoft противится использованию обозначения ADO в продуктах, разработанных сторонними производителями. В среде Delphi вы можете работать с ADO без помощи dbGo. Вы можете импортировать библиотеку типов ADO и получить прямой доступ к интерфейсам ADO. Именно так приходилось работать с ADO в Delphi до появления версии Delphi 5. Однако такой подход не позволяет вам воспользоваться преимуществами встроенной в Delphi инфраструктуры взаимодействия с базами данных. В частности, вы не сможете воспользоваться элементами управления, специально предназначенными для работы с данными, кроме того, для вас будет недоступной технология DataSnap. Во всех примерах данной главы для взаимодействия с ADO используется dbGo. Во-первых, dbGo входит в стандартный комплект поставки Delphi, во-вторых, dbGo является очень удобной технологией. Вне зависимости от того, будете ли вы использовать dbGo или откажетесь от использования этой технологии, материал данной главы будет для вас полезным.

ПРИМЕЧАНИЕ

Помимо dbGo вы можете использовать для взаимодействия с ADO множество других продуктов, разработанных сторонними производителями, например Adonis, AdoSlutio, Diamond ADO и Kamiak.

В данной главе рассматриваются следующие вопросы:

  • Microsoft Data Access Components (MDAC);

  • Delphi dbGo;

  • файлы связи с данными (Data link files);

  • получение информации о схеме;

  • использование механизма Jet;

  • обработка транзакций;

  • отключенные и хранимые на диске наборы записей;

  • модель портфеля и установка MDAC.

[В начало]

MDAC (Microsoft Data Access Components)

На самом деле ADO является частью более крупномасштабной технологии под названием Microsoft Data Access Components (MDAC). Термин MDAC является общим обозначением для всех разработанных компанией Microsoft технологий, связанных с БД. К этому набору относятся ADO, OLE DB, ODBC и RDS (Remote Data Services). Часто приходится слышать, что люди используют термины MDAC и ADO как синонимы, однако это неправильно. На самом деле ADO является лишь одной из частей MDAC. Когда мы говорим о версиях ADO, мы имеем в виду версии MDAC. К основным версиям MDAC относятся версии 1.5, 2.0, 2.1, 2.5 и 2.6. Компания Microsoft распространяет MDAC в виде отдельного продукта. Этот продукт может быть загружен с веб-узла Microsoft бесплатно. Мало того, фактически его можно бесплатно включать в состав ваших собственных продуктов (существуют определенные ограничения, однако большинство разработчиков Delphi без каких-либо проблем удовлетворяют всем этим требованиям). Кроме того, MDAC входит в комплект поставки большинства продуктов Microsoft, имеющих отношение к базам данных. В состав Delphi 7 входит версия MDAC 2.6.
Необходимо принять во внимание два важных обстоятельства. Во-первых, с большой долей уверенности можно сказать, что технология MDAC уже установлена на клиентских компьютерах ваших пользователей. Во-вторых, вне зависимости от версии MDAC, которая была установлена на клиентских компьютерах ваших пользователей, можно с уверенностью сказать, что эта версия рано или поздно будет обновлена до самой свежей (текущей) версии MDAC. Обновление может быть выполнено вами, вашими пользователями или одним из устанавливаемых в системе приложений Microsoft. Подобное обновление фактически невозможно предотвратить, так как MDAC устанавливается в составе такого широко распространенного приложения, как Internet Explorer. К этому следует добавить, что компания Microsoft поддерживает лишь самую последнюю версию MDAC, а также версию, предшествующую самой последней. Исходя из всего этого, можно прийти к выводу: ваше приложение должно работать с самым свежим выпуском MDAC или с предшествующей ему версией.
Как разработчик ADO, вы должны регулярно просматривать страницы веб-узла Microsoft, посвященные MDAC. Для этого следует обратиться по адресу www. microsoft.com/data. Здесь вы сможете бесплатно загрузить самую свежую версию MDAC. Также рекомендуется загрузить MDAC SDK (13 Мбайт), если у вас еще нет этого пакета. На самом деле MDAC SDK входит в состав Platform SDK, так что, если у вас есть Platform SDK, значит, вы уже обладаете MDAC SDK. Пусть пакет MDAC SDK станет вашей библией. Вы должны загрузить его и регулярно обращаться к нему для получения необходимых сведений и ответов на любые вопросы, связанные с ADO. Если вы нуждаетесь в информации, связанной с MDAC, прежде всего вы должны обратиться к MDAC SDK.

[В начало]

Провайдеры OLE DB

Провайдеры OLE DB обеспечивают доступ к источникам данных. В dbExpress для этой цели используются драйверы, а в BDE — связи SQL Links. В процессе установки MDAC в системе автоматически устанавливаются провайдеры OLE DB, перечисленные в табл. 15.1.

Таблица 15.1. Провайдеры OLE DB, входящие в состав MDAC

Драйвер

Провайдер

Описание

MSDASQL

ODBC Drivers

Драйверы ODBC (по умолчанию)

Microsoft.Jet.OLEDB.3.5

Jet 3.5

Только базы данных MS Access 97

Microsoft.Jet.OLEDB.4.0

Jet 4.0

Базы данных MS Access и другие БД

SQLOLEDB

SQL Server

Базы данных MS SQL Server

MSDAORA

Oracle

Базы данных Oracle

MSOLAP

OLAP Services

Online Analytical Processing

SampProv

Sample provider

Пример провайдера OLE DB для файлов CSV

MSDAOSP

Simple provider

Для создания ваших собственных провайдеров для простых текстовых данных

[В начало]

Вот перечень этих провайдеров.

  • ODBC OLE DB используется для обратной совместимости с ODBC. Подробнее ознакомившись с работой ADO, вы узнаете об ограничениях, присущих этому провайдеру.

  • Jet OLE DB — поддержка MS Access и других локальных баз данных. Мы вернемся к рассмотрению этих провайдеров далее.

  • SQL Server обеспечивает взаимодействие с SQL Server 7, SQL Server 2000 и Microsoft Database Engine (MSDE). MSDE — это упрощенная версия SQL Server, в которой отсутствует большинство инструментов, а кроме того, добавлен специальный код, который намеренно снижает производительность в случае, если к базе данных одновременно подключаются более пяти пользователей. К преимуществам MSDE следует отнести то, что этот механизм распространяется бесплатно и полностью совместим с SQL Server.

  • OLE DB для OLAP может использоваться напрямую, однако чаще обращение к нему осуществляется через ADO Multi-Dimentional (ADOMD). ADOMD — это дополнительная технология ADO, специально разработанная для Online Analytical Processing (OLAP). Если ранее вы работали с Delphi Decision Cube, Excel Pivot Tables или Access Cross Tabs, значит, вы работали с одной из форм OLAP. Помимо уже перечисленных здесь провайдеров, компания Microsoft осуществляет поддержку некоторых других провайдеров OLE DB, которые входят в состав других продуктов или в состав SDK.

  • Active Directory Services OLE DB входит в состав ADSI SDK; AS/400 OLE DB и VSAM OLE DB входят в состав SNA Server; Exchange OLE DB входит в состав Microsoft Exchange 2000.

  • Indexing Service OLE DB входит в состав Microsoft Indexing Service — внутренний механизм Windows, ускоряющий поиск информации в файлах при помощи построения каталога с файловой информацией. Служба индексирования Indexing Service интегрирована в IIS и часто используется для индексирования веб-узлов.

  • Internet Publishing OLE DB позволяет разработчикам манипулировать каталогами и файлами с использованием HTTP.

  • Существует также категория провайдеров OLE DB, которые называются провайдерами обслуживания (service providers). Как следует из имени, эти провайдеры обеспечивают обслуживание других провайдеров OLE DB и зачастую активизируются автоматически без участия программиста. Например, служба Cursor Service активизируется в случае, если вы создаете курсор на стороне клиента, а провайдер Persistent Recordset активизируется в случае, если вы собираетесь сохранить данные на локальном диске.

Помимо перечисленных, существует также огромное количество других провайдеров OLE DB для MDAC. Провайдеры OLE DB можно получить как от Microsoft, так и от независимых производителей. Список провайдеров OLE DB очень большой и постоянно меняется, поэтому его невозможно воспроизвести в данной книге. Кроме независимых производителей поставку и поддержку провайдеров OLE DB осуществляют многие производители систем RDBMS. Например, компания Oracle поддерживает собственный провайдер OLE DB под названием ORAOLEDB.

СОВЕТ

Вы уже, наверное, обратили внимание на то, что в списке отсутствует провайдер OLE DB для InterBase. Во-первых, вы можете воспользоваться драйвером ODBC, во-вторых, вы можете использовать провайдер IBProvider, разработанный Дмитрием Коваленко (www.lipetsk.ru/prog/eng/index.html). Наконец, вы можете попробовать разработать провайдер самостоятельно. Для этого удобно использовать комплект OLE DB Provider Development Toolkit, разработанный Бинхом Ли (Binh Ly) и доступный по адресу http://www.techvanguards.com/products/optk/install.htm.

[В начало]

Использование компонентов dbGo

Программисты, уже знакомые с BDE, dbExpess или IBExpress, без труда узнают компоненты, входящие в состав dbGo (табл. 15.2).

Таблица 15.2. Компоненты dbGo

Компонент dbGo

Описание

Эквивалент из комплекта BDE

ADOConnection

Подключение к базе данных

База данных

ADOCommand

Исполняет команду SQL

Нет эквивалента

ADODataSet

Многоцелевой наследник TDataSet

Нет эквивалента

ADOTable

Инкапсулирует таблицу

Table

ADOQuery

Инкапсулирует SQL SELECT

Query

ADOStoredProc

Инкапсулирует сохраненную процедуру (stored procedure)

StoredProc

RDSConnection

Подключение Remote Data Services

Нет эквивалента

Четыре компонента наборов данных (ADODataSet, ADOTable, ADOQuery и ADOStoredProc) фактически полностью реализованы общим для них базовым классом TCustomADODataSet. Этот компонент несет ответственность за выполнение большинства функций, присущих набору данных. Производные компоненты являются тонкими оболочками, которые делают доступными для внешнего мира те или иные возможности базового компонента. Таким образом, компоненты обладают множеством общих черт. Компоненты ADOTable, ADOQuery и ADOStoredProc предназначены для упрощения адаптации кода, ориентированного на BDE. Однако следует иметь в виду, что эти компоненты нельзя считать полностью идентичными эквивалентами аналогичных компонентов BDE. Различия обязательно проявят себя при разработке фактически любого приложения за исключением, может быть, самых тривиальных. В качестве основного компонента при разработке новых программ следует считать компонент ADODataSet, так как, во-первых, этот компонент является достаточно удобным, а во-вторых, его интерфейс сходен с интерфейсом ADO Recordset. В данной главе я продемонстрирую использование каждого из упомянутых компонентов.

[В начало]

Практический пример

Хватит теории, давайте перейдем к делу. Разместим на форме компонент ADOTable. Для индикации базы данных, к которой следует подключиться, в рамках ADO используются строки подключения (connection strings). Если вы знаете, что делаете, вы можете набрать строку подключения вручную. Однако в общем случае для создания строки подключения рекомендуется использовать специальный редактор (редактор свойства ConnectionString), рабочее окно которого показано на рис. 15.1.

Щелкните на Build (Сформировать), чтобы запустить разработанный компанией Microsoft редактор строк подключения. Его рабочее окно показано на рис. 15.2. Давайте рассмотрим этот инструмент подробнее, так как он является важным средством при работе с ADO. На первой вкладке показаны провайдеры OLE DB и провайдеры обслуживания, установленные на вашем компьютере. Перечень провайдеров может быть разным для разных версий MDAC, кроме того, новые провайдеры могут появиться в списке в результате установки на компьютере новых прикладных программ. Вернемся к нашему примеру. Выберите провайдер Jet 4.0 OLE DB — для этого сделайте двойной щелчок на надписи Jet 4.0 OLE DB Provider, на экране появится вкладка Connection (Подключение). Внешний вид этой страницы для разных провайдеров может быть разным. Для провайдера Jet редактор предложит вам ввести имя базы данных и аутентификационные данные. Вы можете выбрать MDB-файл базы данных Access, входящий в комплект поставки Delphi (например, C:\Program Files\Common Files\Borland Shared\Data\dbdemos.mdb). Щелкните на кнопке Test Connection (Протестировать соединение) для того, чтобы убедиться в правильности вашего выбора.
На вкладке Advanced (Дополнительно) вы можете контролировать режим доступа к базе данных. Здесь вы можете настроить эксклюзивный доступ или доступ только для чтения. На вкладке All (Все) перечисляются все параметры строки подключения. Этот список может быть разным для разных провайдеров OLE DB. Хорошо запомните эту страницу, так как с ее помощью можно решить множество разнообразных проблем. Закрыв редактор Microsoft, вы вернетесь к редуктору строк подключения Borland. В рабочем окне этого редактора будет показана строка, которая будет присвоена ConnectionString (здесь я разделил ее на несколько строчек, чтобы удобнее было читать):


Provider=Microsoft.Jet.OLEDB.4.0; Data Source=C:\Program Files\Common Files\Borland Shared\Data\dbdemos.mdb; Persist Security Info=False

Строка подключения — это обычная строка символов, в которой через точку с запятой перечисляются параметры и их значения. Такую строку можно редактировать вручную. Параметры и их значения можно перенастраивать в процессе выполнения программы, для этого вы должны написать собственный набор подпрограмм для выполнения поиска параметра в списке и внесения изменения в его значение. Существует также более простой способ: вы можете скопировать строку в список строк Delphi и воспользоваться механизмом обработки пар «имя—значение ». Этот прием будет продемонстрирован в примере JetText, о котором будет рассказано далее в разделе «Доступ к текстовым файлам через Jet».
После того как вы сформировали строку подключения, вы можете выбрать таблицу. Раскройте список таблиц при помощи свойства TableName в окне Object Inspector. Выберите таблицу Customer. Добавьте компонент DataSource и элемент управления DBGrid, а затем соедините их вместе. В результате получилась реальная, хотя и примитивная программа, использующая ADO (полный исходный код оформлен в виде примера FirstAdoExample). Чтобы увидеть данные, занесите в свойство Active набора данных значение True или откройте набор данных внутри обработчика события FormCreate (как это сделано в примере). Второй способ позволяет избежать проблем, если на этапе проектирования база данных недоступна.

СОВЕТ

Если вы планируете использовать dbGo в качестве основной технологии доступа к БД, вам наверняка захочется переместить компонент DataSource на страницу ADO палитры компонентов, чтобы не перескакивать постоянно со страницы на страницу. Если вы используете ADO в комбинации с другой технологией, вы можете имитировать установку DataSource на нескольких страницах. Для этого необходимо создать шаблон (Component Template) компонента DataSource и поместить его на страницу ADO.

[В начало]

Компонент ADOConnection

Когда вы используете компонент ADOTable, он создает свой собственный компонент соединения с БД у вас за спиной. Однако вы вовсе не обязаны использовать именно это соединение. В общем случае вы должны создать свое собственное соединение при помощи компонента ADOConnection, который по сути является эквивалентом компонента SQLConnection из библиотеки dbExpress и компонента Database из библиотеки BDE. Компонент ADOConnection позволяет вам должным образом настроить процедуру аутентификации, контролировать транзакции, напрямую выполнять команды, адресованные БД, кроме того, он позволяет сократить количество подключений, существующих в рамках приложения.
Использовать ADOConnection достаточно просто. Разместите этот компонент на форме и настройте его свойство ConnectionString таким же образом, как вы делали это для компонента ADOTable. Кроме того, вы можете сделать двойной щелчок на компоненте ADOConnection (или выбрать пункт Component Editor в контекстном меню) для того, чтобы напрямую обратиться к редактору строки подключения. Если строка подключения (ConnectionString) указывает на необходимую вам базу данных, вы можете отключить диалоговое окно подключения к БД, для этого необходимо присвоить свойству LoginPrompt значение False. Чтобы в предыдущем примере воспользоваться новым соединением, присвойте значение ADOConnection1 свойству Connection компонента ADOTable1. Вы увидите, что значение свойства ConnectionString станет пустым, так как свойства Connection и ConnectionString исключают друг друга. Преимущество использования ADOConnection состоит в том, что строка подключения теперь хранится в одном месте, вместо того чтобы храниться в нескольких разных компонентах. Еще одно более важное преимущество заключается в том, что несколько разных компонентов могут использовать одно и то же соединение с сервером базы данных. Если вы не добавите в программу вручную сделанный вами компонент ADOConnection, каждый компонент ADO будет обладать собственным соединением с сервером.

[В начало]

Файлы связи с данными (Data Link Files)

Итак, компонент ADOConnection позволяет вам централизовать определение строки подключения в рамках формы или модуля данных. Однако у описанного подхода по-прежнему имеется один существенный недостаток: если вы идентифицируете базу данных при помощи некоторого имени файла, путь к этой базе будет жестко закодирован внутри исполняемого файла приложения. В результате возможности приложения будут существенно ограничены. Чтобы решить проблему, в ADO используются так называемые файлы связи с данными (Data Link Files). Файл связи с данными — это строка подключения, оформленная в виде INIфайла. Например, в рамках Delphi устанавливается файл dbdems.udl, в котором содержится следующий текст:


[oledb] ; Все, что расположено ниже данной строки, является строкой инициализации OLE DB Provider=Microsoft.Jet.OLEDB.4.0; Data Source=C:\Program Files\Common Files\Borland Shared\Data\dbdemos.mdb

Файл связи с данными может обладать любым расширением, однако рекомендуется использовать расширение.UDL. Вы можете создать такой файл при помощи любого текстового редактора. Кроме того, чтобы создать такой файл, вы можете открыть окно проводника Windows, правой кнопкой мыши щелкнуть в одной из папок диска, выбрать New > Text Document (Создать > Текстовый документ), сменить расширение файла на.UDL (я предполагаю, что в вашей системе проводник отображает расширения файлов), затем сделать двойной щелчок на файле — в результате будет запущен редактор строки подключения Microsoft.
Если в редакторе свойства ConnectionString вы выберете Use Data Link File (Использовать файл связи с данными), в этом свойстве будет автоматически размещена строка 'FILE NAME =', за которой будет указано имя файла связи с данными. Такой прием продемонстрирован в примере DataLinkFile. Файлы связи с данными можно разместить в любом месте диска, однако ADO использует для хранения таких файлов некоторый стандартный каталог. Узнать имя этого каталога можно при помощи функции DataLinkDir, которая определяется в модуле ADODB. Если конфигурация — по умолчанию используемая в MDAC, значит, эта функция вернет следующее:


C:\Program Files\Common Files\System\OLE DB\Data Links

[В начало]

Динамические свойства

Представьте, что вы занимаетесь разработкой среднего звена, расположенного между клиентами и несколькими базами данных. С одной стороны, вы должны сформировать единый унифицированный программный интерфейс для доступа к нескольким разным базам данных, с другой стороны, этот интерфейс должен обеспечивать доступ к специфическим возможностям каждой из баз данных. Чтобы решить обе эти задачи, вы можете разработать тяжеловесный интерфейс, который будет представлять собой сумму возможностей всех баз данных, для взаимодействия с которыми он предназначен. Каждый класс такого интерфейса должен включать в себя все возможные свойства и методы, однако для работы с конкретной БД можно будет использовать лишь подмножество свойств и методов класса. Надеюсь, не стоит доказывать вам, что это решение не является самым лучшим. Для решения проблемы в ADO используются динамические свойства (dynamic properties). Фактически все интерфейсы ADO, равно как и соответствующие им компоненты dbGo, обладают свойством под названием Properties. Это свойство является коллекцией свойств, специфичных для текущей базы данных. К этим свойствам можно обратиться, указав их порядковый номер, например:


ShowMessage(ADOTable1.Properties[1].Value);

Однако в большинстве случаев удобнее использовать имя: ShowMessage(ADOConnection1.Properties['DBMS Name'].Value); Набор динамических свойств определяется типом объекта и провайдером OLE DB. Чтобы вы получили представление о важности сделать динамических свойств, я замечу, что такие компоненты, как ADOConnection или Recordset, поддерживают приблизительно 100 динамических свойств. Как будет показано в данной главе, динамические свойства активно используются в ADO для решения множества разнообразных задач.

СОВЕТ

Важным событием, имеющим отношение к использованию динамических свойств, является событие OnRecordsetCreate. Впервые это событие появилось в Delphi 6. Событие OnRecordsetCreate генерируется сразу же после создания Recordset, но перед тем как этот компонент будет открыт. Это событие полезно использовать для настройки тех динамических свойств, которые могут быть настроены только тогда, когда набор записей (Recordset) находится в закрытом состоянии.

[В начало]

Получение информации о схеме

В ADO для получения информации о схеме используется метод OpenSchema компонента ADOConnection. Этот метод принимает четыре параметра:

  • Тип данных, которые будут возвращаться методом OpenSchema. Это значение типа TSchemaInfo: набор из 40 значений, включая перечни таблиц, индексов, столбцов, представлений и сохраненных процедур.

  • Фильтр, который необходимо применить в отношении к данным, прежде чем они будут возвращены. Пример этого параметра будет продемонстрирован чуть позже.

  • GUID для запроса, специфичного для провайдера. Этот параметр используется, только если первый параметр равен значению siProviderSpecific.

  • Компонент ADODataSet, в составе которого будут возвращены данные. Этот параметр иллюстрирует распространенную в рамках ADO тему: если метод возвращает некоторое количество данных, он заносит эти данные в Recordset или, в терминологии Delphi, — в компонент ADODataSet.

Чтобы воспользоваться методом OpenSchema, вы должны открыть ADOConnection. Следующий код, который является частью примера OpenSchema, извлекает список первичных ключей для каждой таблицы и заносит их в компонент ADODataSet:


ADOConnection1.OpenSchema(siPrimaryKeys, EmptyParam, EmptyParam, ADODataSet1);

Каждому полю в составе первичного ключа соответствует одна строка в результирующем наборе данных. Таким образом, если таблица обладает первичным ключом, состоящим из двух полей, в результирующем наборе данных такой таблице будут соответствовать две строки. Значение EmptyParam указывает на то, что параметру присваивается пустое значение, значит, параметр игнорируется. Результат работы кода показан на рис. 15.3.

Если в качестве второго параметра вы передаете значение EmptyParam, в состав результирующего набора данных включается вся информация указанного типа для всей базы данных. Очень часто для удобства вы хотите выполнить фильтрацию информации. Конечно же, для этой цели можно применить к результирующему набору данных традиционный фильтр Delphi (для этого можно использовать свойства Filter и Filtered или событие OnFilterRecord). Однако в этом случае фильтрация будет выполняться на стороне клиента. Второй параметр позволяет выполнить фильтрацию более эффективно на стороне источника информации о схеме. Фильтр определяется как массив значений. Каждый элемент массива обладает специальным смыслом, имеющим отношение к типу возвращаемых данных. Например, массив фильтров для первичных ключей включает в себя три элемента: каталог (то есть базу данных), схему и имя таблицы. Этот пример возвращает перечень первичных ключей в таблице Customer:


var Filter: OLEVAriant; begin Filter := VarArrayCreate([0, 2], varVariant); Filter[2] := 'CUSTOMER'; ADOConnection1.OpenSchema( siPrimaryKeys, Filter, EmptyParam, ADODataSet1); end;

ПРИМЕЧАНИЕ

Ту же информацию можно получить при помощи ADOX. ADOX — это дополнительная технология ADO, которая позволяет вам получать и изменять информацию о схеме. В SQL эквивалентом ADOX является язык DDL (Data Definition Language), то есть выражения CREATE, ALTER, DROP и DCL (Data Control Language), то есть выражения GRANT, REVOKE. В рамках dbGo технология ADOX напрямую не поддерживается, однако вы можете импортировать библиотеку типов ADOX и использовать ее в приложениях Delphi. В отличие от метода OpenSchema, реализация ADOX в Delphi не универсальна, поэтому использовать ее не всегда удобно. Если вы хотите просто получить информацию о схеме, но не изменять ее, для этой цели, как правило, удобнее использовать метод OpenSchema.

[В начало]

Использование механизма Jet

Теперь, когда вы получили базовое представление об MDAC и ADO, мы можем перейти к рассмотрению механизма Jet. Для одних этот механизм представляет интерес, другим он совершенно не нужен. Если вы имеете дело с Access, Paradox, dBase, Excel, Lotus 1-2-3, HTML или данными, хранящимися в текстовых файлах, значит, рассматриваемый здесь материал будет для вас полезным. Если вы не заинтересованы в перечисленных здесь форматах, вы можете пропустить весь этот раздел.
Как правило, механизм Jet ассоциируется с базами данных Microsoft Access. Действительно, Access является основной системой, с которой взаимодействует Jet. Однако помимо Access механизм Jet позволяет работать с множеством других локальных источников данных. Многие не подозревают об этом, однако именно в этом заключается одно из основных преимуществ Jet. Взаимодействие с Access через Jet в стандартном режиме работы этого механизма выполняется относительно просто, поэтому здесь мы не будем рассматривать этот режим использования Jet. Вместо этого мы подробно рассмотрим взаимодействие Jet с другими форматами.

ПРИМЕЧАНИЕ

Механизм Jet входит в состав некоторых (но не всех) версий MDAC. В частности, он отсутствует в версии 2.6. В свое время было много споров относительно того, могут ли программисты, использующие средства разработки, не принадлежащие Microsoft, включать в комплект поставки своих программных продуктов механизм Jet. Официально считается, что такое возможно. Механизм Jet можно загрузить бесплатно с веб-узла компании Microsoft, кроме того, этот механизм входит в комплект поставки многих продуктов компании Microsoft.

Существует два провайдера OLE DB для механизма Jet: Jet 3.51 OLE DB и Jet 4.0 OLE DB. Провайдер Jet 3.51 OLE DB использует Jet 3.51 и поддерживает работу только с Access 97. Если вы будете применять только Access 97 и не собираетесь переходить на Access 2000, то Jet 3.51 в большинстве случаев даст более высокую производительность по сравнению с провайдером Jet 4.0 OLE DB.
Провайдер Jet 4.0 OLE DB поддерживает работу с Access 97, Access 2000 и с драйверами IISAM (Installable Indexed Sequential Access Method). Устанавливаемые драйверы ISAM специально написаны для механизма Jet и обеспечивают доступ к таким форматам, как Paradox, dBase и текстовые файлы. Именно возможность использования этих драйверов делает Jet полезным и удобным инструментом. Полный список драйверов ISAM, установленных на вашем компьютере, определяется набором установленного в системе программного обеспечения. Этот список располагается в реестре по адресу:


HKEY_LOCAL_MACHINE\Software\Microsoft\Jet.0\ISAM Formats

В состав комплекта поставки Jet входят драйверы для Paradox, dBase, Excel, текстовых файлов и HTML.

[В начало]

Импорт и экспорт

Механизм Jet удобно использовать для импорта и экспорта данных. Процесс экспортирования данных одинаков для каждого экспортированного формата и состоит из исполнения выражения SELECT в специальном формате. Рассмотрим пример экспортирования данных из базы данных Access в примере DBDemos в таблицу Paradox. Для этого добавим в программу JetImportExport активное соединение ADOConnection с названием ADOConnection1. Это соединение использует механизм Jet для того, чтобы открыть базу данных. Следующий код экспортирует таблицу Customer в файл Customer.db формата Paradox:


SELECT INTO Customer IN "C:\tmp" "Paradox 7.x;" FROM CUSTOMER

Рассмотрим составные части этого SQL-выражения. После ключевого слова INTO указывается новая таблица, которая будет создана в результате выполнения оператора SELECT. До выполнения этого кода таблица с этим именем должна отсутствовать в базе. После ключевого слова IN указывается база данных, в которую добавляется новая таблица. В Paradox это должен быть каталог, который уже существует на диске. Сразу же после имени базы данных указывается имя драйвера IISAM, который будет использоваться для экспорта данных. В конце имени драйвера обязательно нужно добавить символ точки с запятой (;). Ключевое слово FROM является стандартным компонентом любого выражения SELECT. В рассматриваемом примере эта операция выполняется при помощи компонента ADOConnection1, вместо фиксированного имени каталога используется текущий каталог программы:


ADOConnection1.Execute (''SELECT INTO Customer IN "' + CurrentFolder + '" "Paradox 7.x;" FROM CUSTOMER');

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


ADOConnection1.Execute ('SELECT INTO Customer IN "' + CurrentFolder + 'dbdemos.xls" "Excel 8.0;" FROM CUSTOMER’);

Новый файл Excel с именем dbdemos.xls создается в текущем каталоге программы. В этот документ Excel добавляется рабочая книга с именем Customer, в которую заносятся все данные из таблицы Customer базы данных Access с именем dbdemo. mdb.
Вот еще одно выражение, которое экспортирует те же самые данные в HTMLфайл:


ADOConnection1.Execute ('SELECT INTO [customer.htm] IN "' + CurrentFolder + '" "HTML Export;" FROM CUSTOMER');

В данном случае база данных — это каталог (как и в Paradox). Имя таблицы включает в себя расширение.htm, поэтому имя таблицы необходимо заключить в квадратные скобки. Обратите внимание, что драйвер IISAM называется не просто HTML, а HTML Export. Как следует из названия, драйвер позволяет только экспортировать данные, но не позволяет импортировать их.
Наконец, давайте рассмотрим входящий в состав Jet драйвер HTML Import, который является полезным дополнением к HTML Export. Добавьте на форму компонент ADOTable. Настройте строку подключения ConnectionString на использование провайдера Jet 4.0 OLE DB. Присвойте параметру Extended Properties строки подключения значение HTML Import. В качестве имени базы данных укажите имя HTML-файла, который был создан в результате экспорта (чуть ранее), точнее говоря, Customer.htm. Теперь присвойте свойству TableName значение Customer. Откройте таблицу — вы только что импортировали данные из HTML-файла. Имейте в виду, что если вы попытаетесь обновить данные, система выдаст ошибку, так как драйвер предназначен только для импорта. Если вы создали собственный HTMLфайл, в котором содержатся таблицы, и хотите открыть эти таблицы с использованием данного драйвера, вы должны помнить, что имя таблицы — это значение тега caption в HTML-разделе table.

[В начало]

Работа с курсорами

У каждого из наборов данных ADO есть два свойства, которые неразрывно связаны друг с другом и оказывают значительное влияние на ваше приложение. Это свойства CursorLocation и CursorType. Если вы хотите понять принцип функционирования набора данных ADO, вы должны изучить эти два свойства.

[В начало]

Положение курсора (свойство CursorLocation)

Свойство CursorLocation определяет, каким образом осуществляется извлечение и модификация данных. Этому свойству можно присвоить одно из двух значений: clUseClient (курсор на стороне клиента) или clUseServer (курсор на стороне сервера). Выбор значения в большой степени влияет на функциональность, производительность и масштабируемость базы данных.
Клиентский курсор обслуживается механизмом ADO Cursor Engine. Этот механизм является превосходным примером провайдера обслуживания OLE DB: он обеспечивает обслуживание для других провайдеров OLDE DB. Механизм ADO Cursor Engine управляет обработкой данных на стороне клиента. При открытии набора данных все данные результирующего набора перекачиваются с сервера на клиентский компьютер. После этого данные хранятся в памяти, их обновление и обработка осуществляется с использованием ADO Cursor Engine. Этот подход напоминает использование ClientDataSet в приложениях dbExpress. Преимущество состоит в том, что после передачи данных на сторону клиента любые манипуляции с этими данными выполняются значительно быстрее. Кроме того, так как манипуляции выполняются в памяти, механизм ADO Cursor Engine обладает более широкими возможностями, чем любой из курсоров, работающих на стороне сервера. Далее я подробнее рассмотрю эти преимущества, а также другие технологии, основанные на клиентских курсорах (в частности, отключенные и постоянные наборы записей). Курсор на стороне сервера управляется самой системой RDBMS. В клиент-серверной архитектуре, основанной на таких продуктах, как SQL Server, Oracle или InterBase, это означает, что управление курсором осуществляется на удаленном серверном компьютере. Если речь идет о настольной базе данных, такой как Access или Paradox, серверный курсор управляется программным продуктом, обслуживающим базу данных. То есть логически курсор расположен на «сервере», однако физически база данных вместе с курсором располагается на клиентском компьютере. Как правило, серверные курсоры загружаются быстрее, чем клиентские курсоры, так как при открытии набора данных с серверным курсором нет необходимости перемещать все данные на сторону клиента. Благодаря этому серверные курсоры лучше подходят для обслуживания больших наборов данных, то есть тогда, когда клиентский компьютер не обладает объемом памяти, достаточным для хранения всего набора данных. Чтобы понять возможности курсоров обоих типов, лучше всего посмотреть, как они функционируют в той или иной ситуации. Например, можно взять ситуацию блокирования записей. Чуть позднее я более подробно расскажу о блокировании. (Если вы хотите заблокировать запись, вам потребуется серверный курсор, так как система RDBMS должна знать о том, что запись заблокирована.)
Еще одной характеристикой, на которую следует обратить внимание при выборе местоположения курсора, является масштабируемость. Серверные курсоры располагаются на стороне сервера. Чем больше пользователей подключается к базе, тем больше курсоров создается на сервере. С каждым новым курсором нагрузка на сервер возрастает. Таким образом, при увеличении количества пользователей общая производительность системы может существенно понизиться. Используя курсоры на стороне клиента, вы можете существенно повысить масштабируемость вашего приложения. Открытие клиентского курсора обойдется вам дороже, так как в процессе открытия все данные передаются на сторону клиента, однако обслуживание клиентского курсора менее обременительно для сервера, ведь основная связанная с этим нагрузка возлагается на клиентский компьютер.

[В начало]

Тип курсора (свойство CursorType)

Тип курсора во многом определяется местом расположения курсора. Существует пять типов курсоров, один из которых не используется. Неиспользуемый тип называется unspecified (неуказанный). В ADO существует много значений, которые соответствуют неуказанному значению. В Delphi эти значения фактически никогда не используются. Эти значения присутствуют в Delphi только потому, что они присутствуют в ADO. Дело в том, что технология ADO изначально разрабатывалась для таких языков, как Visual Basic и C. В этих языках вы работаете с объектами напрямую, без поддержки вспомогательных механизмов, таких как dbGo. В результате вы можете создать открытый набор записей (в терминологии ADO — recordset), не указывая при этом значения для каждого из свойств. Таким образом, значения некоторых свойств будут не определены. В этом случае свойству присваивается значение unspecified (не указано). Однако в рамках dbGo вы имеете дело с компонентами. Компоненты обладают конструкторами. Конструктор — это функция, которая в обязательном порядке инициализирует каждое из свойств компонента. Когда вы создаете компонент dbGo, каждое из его свойств обладает определенным значением. В итоге отпадает надобность в использовании значения unspecified (не указано).
Тип курсора влияет на то, каким образом происходит чтение и обновление данных. Можно использовать один из четырех типов курсора: Forward-Only (только вперед), Static (статический), Keyset (набор ключей) и Dynamic (динамический). Прежде чем переходить к обсуждению разнообразных комбинаций типов и местоположения курсора, отмечу одно важное обстоятельство: для курсоров, работающих на стороне клиента, можно использовать только один тип: статический курсор. Все остальные типы курсоров могут использоваться только на стороне сервера. Давайте подробнее рассмотрим типы курсоров в порядке возрастания затрат, связанных с их обслуживанием.

  • Forward-only (только вперед). Курсоры этого типа обходятся дешевле всего в смысле затрат. Иными словами, такие курсоры обеспечивают самую высокую производительность. Как следует из имени, курсор Forward-only (только вперед) позволяет вам перемещаться по набору данных в направлении от начала к концу. Курсор читает с сервера количество записей, указанное в свойстве CacheSize (по умолчанию 1), каждый раз, как только он покидает последнюю запись в локальном кэше, он читает с сервера следующую порцию записей. Любая попытка переместиться по направлению к началу набора записей за пределы локального кэша приводит к возникновению ошибки. Это поведение напоминает поведение набора данных в библиотеке dbExpress. Курсор Forwardonly (только вперед) плохо подходит для формирования пользовательского интерфейса, в котором пользователь обладает возможностью контролировать направление перемещения. Вместе с тем, такой курсор вполне подходит для выполнения пакетных операций, формирования отчетов, при построении вебприложений, не сохраняющих информацию о состоянии, — в любой из этих ситуаций вы начинаете с начала набора данных и перемещаетесь по направлению к концу набора данных. По достижении конца набор данных закрывается.

  • Static (статический). При использовании статического курсора набор данных полностью перемещается на сторону клиента, обращение к нему осуществляется при помощи окна размером CacheSize. В результате пользователь получает возможность перемещаться по набору данных в обоих направлениях. Недостаток заключается в том, что данные являются статическими — обновления, добавления и удаления записей, выполняемые другими пользователями, не видны для статического курсора, так как данные курсора уже прочитаны.

  • Keyset (набор ключей). Чтобы понять принцип функционирования этого курсора, разделите слово Keyset на две части: key и set. Key — это ключ, то есть в данном контексте — идентификатор записи. Зачастую имеется в виду первичный ключ. Set — это множество или набор. Получается «набор ключей». При открытии набора данных с сервера читается полный список всех ключей. Например, если набор данных формируется при помощи выражения SELECT FROM CUSTOMER, значит, список ключей можно сформировать при помощи выражения SELECT CUSTID FROM CUSTOMER. Набор ключей хранится на стороне клиента до закрытия курсора. Когда приложение нуждается в данных, провайдер OLE DB читает строки таблицы, используя для этой цели имеющийся у него набор ключей. В результате клиент всегда имеет дело с обновленными данными. Однако набор ключей является статическим в том смысле, что после открытия курсора в этот набор нельзя добавить новые ключи, также ключи нельзя удалить из набора. Иными словами, если другой пользователь добавляет в таблицу новые записи, эти изменения не будут видны для клиента. Удаленные записи становятся недоступными, а любые изменения в первичных ключах (как правило, пользователям запрещается менять первичные ключи) также становятся недоступными.

  • Dynamic (динамический). Это наиболее дорогостоящий курсор. Динамический курсор функционирует приблизительно так же, как курсор набора ключей. Разница заключается в том, что набор ключей заново читается с сервера каждый раз, когда приложение нуждается в данных, отсутствующих в кэше. Так как значение свойства ADODataSet.CacheSize по умолчанию равно 1, запросы на чтение данных возникают достаточно часто. Можно себе представить дополнительную нагрузку, которую данный курсор создает на сервер DBMS и на сеть. Однако при использовании этого курсора клиент знает не только об изменениях данных, но и о добавлениях и удалениях, выполняемых другими клиентами.

[В начало]

Вы не всегда получаете то, о чем просите

Теперь, когда вы знаете о типах и местоположении курсора, я должен предупредить вас о том, что допускается использование далеко не всех комбинаций типов и местоположений курсора. Как правило, это ограничение связано с типом RDBMS и/или провайдером OLE DB. Например, если курсор располагается на стороне клиента, тип курсора может быть только статическим. Вы можете понаблюдать подобное поведение самостоятельно. Добавьте на форму компонент ADODataSet, настройте свойство ConnectionString для подключения к любой базе данных, после этого присвойте свойству ClientLocation значение clUseCursor, а свойству CursorType — значение ctDynamic. Теперь измените значение свойства Active на True и понаблюдайте за свойством CursorType. Значение этого свойства немедленно изменится на ctStatic. Следует сделать важный вывод: вы далеко не всегда получаете именно то, о чем просите. Открыв набор данных, всегда проверяйте значения свойств — некоторые из них могут самопроизвольно изменить свои значения. Для различных провайдеров OLE DB характерны разные изменения свойств. Приведу лишь несколько примеров:

  • провайдер Jet 4.0 OLE DB изменяет большинство типов курсоров на Keyset (набор ключей);

  • провайдер SQL Server OLE DB часто меняет Keyset (набор ключей) и Static (статический) на Dynamic (динамический);

  • провайдер Oracle OLE DB меняет все типы курсоров на Forward-only (только вперед);

  • провайдер ODBC OLE DB может выполнить самые разные изменения типа курсора в зависимости от используемого драйвера ODBC.

[В начало]

Отсутствие счетчика

Когда вы пытаетесь прочитать свойство RecordCount какого-либо набора данных ADO, иногда вы обнаруживаете, что это свойство равно –1. Курсор типа Forwardonly не знает, какое количество записей входит в состав набора данных, пока он не достигнет конца набора. По этой причине свойство RecordCount равно значению – 1. Статический курсор всегда знает, какое количество записей входит в набор данных, так как статический курсор читает все данные набора в момент открытия. Курсор типа Keyset (набор ключей) тоже знает количество записей в наборе, так как в момент открытия набора данных он извлекает из базы данных фиксированный набор ключевых значений. Таким образом, для курсоров Static и Keyset вы можете обратиться к свойству RecordCount и получить точное количество записей в наборе. Динамический курсор не может достоверно знать количество записей, так как каждый раз при чтении данных он заново читает набор ключей, поэтому свойство RecordCount для этого курсора всегда равно –1. Вы можете вообще отказаться от использования свойства RecordCount и вместо этого использовать выражение SELECT COUNT() FROM имя_таблицы. Однако в результате вы получите неточное значение количества записей в таблице базы данных — это значение далеко не всегда совпадает с количеством записей в наборе данных.

[В начало]

Клиентские индексы

Одним из преимуществ курсоров, работающих на стороне клиента, является возможность создания локальных, или клиентских, индексов. Представьте, что у вас есть набор данных ADO с клиентским курсором и что этот набор соединен с таблицей Customer из примера DBDemos. Представьте, что к этому набору подключена сетка DBGrid. Присвойте свойству IndexFieldNames значение CompanyName. Сетка немедленно отобразит записи, упорядочив их в соответствии со значением поля CompanyName. Важно отметить, что для формирования индекса ADO не читает заново данные из источника. Индекс формируется на основе данных, хранящихся в памяти. Благодаря этому, во-первых, индекс формируется достаточно быстро, во-вторых, не создается никакой дополнительной нагрузки на сеть и DBMS. В противном случае одни и те же данные пришлось бы раз за разом передавать через сеть в различном порядке сортировки.
Свойство IndexFieldNames обладает еще кое-какими интересными возможностями. Например, присвойте этому свойству значение Country;CompanyName — вы увидите, что записи сначала отсортированы в соответствии с именем страны, а затем — в соответствии с именем компании. Теперь присвойте свойству IndexField- Names значение CompanyName DESC (ключевое слово DESC должно быть написано заглавными буквами, но не desc или Desc). В результате записи будут отсортированы в порядке убывания значений.
Эта простая, но весьма мощная возможность позволяет вам решить одну из наиболее актуальных проблем, связанных с программированием БД. Пользователи любят задавать неизбежный и неприятный для программистов, но совершенно оправданный вопрос: «Могу ли я щелкнуть на заголовке столбца сетки для того, чтобы отсортировать мои данные?» Существует несколько способов решения этой проблемы. Например, вы можете воспользоваться стандартным (не поддерживающим работу с данными) элементом управления, таким как ListView, который поддерживает встроенный механизм сортировки. Кроме того, вы можете выполнить обработку события OnTitleClick компонента DBGrid и в рамках обработчика заново исполнять SQL-выражение SELECT, добавляя к нему подходящую команду ORDER BY. Однако любое из этих решений нельзя назвать в полной мере удовлетворительным. Если данные кэшируются на стороне клиента (мы уже обсуждали этот подход, когда говорили о компоненте ClientDataSet), вы можете воспользоваться индексом, сформированным в памяти клиентского компьютера. Добавьте следующий обработчик события OnTitleClick для сетки (полный исходный код входит в состав примера ClientIndexes):


procedure Tfrom1.DBGrid1TitleClick(Column: Tcolumn); begin if ADODataSet1.IndexFieldNames = Column.Field.FieldName then ADODataSet1.IndexFieldNames := Column.Field.FieldName + ' DESC' else ADODataSet1.IndexFieldNames := Column.Field.FieldName end;

Этот простой код проверяет, построен ли текущий индекс на основе поля, которое соответствует столбцу сетки, на заголовке которого сделан щелчок. Если да, то на основе этого же поля строится новый индекс, но в нисходящем порядке. Если нет, то на основе столбца формируется новый индекс. Когда пользователь щелкает на заголовке столбца первый раз, записи сортируются в порядке увеличения значений. Когда пользователь щелкает на этом же столбце второй раз, записи сортируются в порядке уменьшения. Вы можете усовершенствовать этот обработчик таким образом, чтобы позволить пользователю щелкать на нескольких заголовках, удерживая нажатой клавишу Ctrl. При этом можно формировать более сложные индексы.

ПРИМЕЧАНИЕ

Все то же самое можно реализовать с использованием компонента ClientDataSet, однако этот компонент не поддерживает ключевого слова DESC, поэтому для сортировки в порядке уменьшения значений вам потребуется написать дополнительный код. Более того, при смене порядка сортировки компонент ClientDataSet будет заново формировать индекс — это ненужная и, возможно, медленная операция.

[В начало]

Клонирование

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

ПРИМЕЧАНИЕ

Компонент ClientDataSet также поддерживает клонирование, однако я не упомянул об этой возможности в главе 13.

Для клонирования набора данных (в ADO — набора записей) используется метод Clone. Клонировать можно любой набор данных ADO, однако в данном примере мы будет использовать компонент ADOTable. В программе DataClone (рис. 15.6) присутствуют два компонента ADOTable — один из них подключен к данным, а второй пуст. Оба набора данных подключены к источнику данных DataSource и сетке. Когда пользователь щелкает на кнопке Clone Dataset (клонировать набор данных), выполняется всего одна строка кода, которая клонирует набор данных:


ADOTable2.Clone(ADOTable1);

Эта строка клонирует набор данных ADOTable1 и размещает полученный клон в наборе данных ADOTable2. Благодаря этому вы получаете два представления одних и тех же данных. Каждый набор обладает собственным указателем на текущую запись и собственной копией информации о состоянии, благодаря этому клон никак не влияет на изначальную копию данных. Подобное поведение делает клоны отличным инструментом работы с набором данных, не влияя при этом на изначальные данные. Еще одна интересная возможность: вы можете создать несколько разных активных записей — у разных клонов активные записи могут быть разными. Подобную функциональность нельзя реализовать в Delphi, используя лишь один набор данных.

СОВЕТ

Набор данных можно клонировать только в случае, если он поддерживает закладки (bookmarks). По этой причине курсоры типа «только вперед» и динамические курсоры не могут быть клонированы. Чтобы определить, поддерживает ли набор записей закладки, вы можете воспользоваться методом Supports (например, ADOTable1.Supports([coBookMark])). Побочный эффект клонирования заключается в том, что закладки, созданные одним из клонов, могут использоваться всеми остальными клонами.

[В начало]

Обработка транзакций

В разделе «Использование транзакций» главы 14 мы с вами говорили о том, что механизм транзакций позволяет разработчикам группировать отдельные операции в отношении БД в единую логически неразрывную процедуру.
Обработка транзакций в ADO осуществляется при помощи компонента ADOConnection, для этого используются методы BeginTrans, CommitTrans и RollbackTrans. Действие этих методов сходно с аналогичными методами dbExpress и BDE. Для изучения механизма транзакций, встроенного в ADO, воспользуемся программой TransProcessing. В состав программы входит компонент ADOConnection, строка подключения которого (свойство ConnectionString) настроена на использование провайдера Jet 4.0 OLE DB и на обращение к файлу dbdemos.mdb. В программе присутствует компонент ADOTable, подключенный к таблице Customer и связанный с компонентами DataSource и DBGrid для отображения данных. Наконец, в программе присутствуют три кнопки, предназначенные для выполнения следующих команд:


ADOConnection1.BeginTrans; ADOConnection1.CommitTrans; ADOConnection1.RollbackTrans;

Используя эту программу, вы можете вносить в базу данных изменения, а затем выполнять откат транзакции, то есть отмену этих изменений. В результате база данных будет восстановлена в состояние, в котором она находилась до начала транзакции. Следует отметить, что обработка транзакций выполняется по-разному в зависимости от базы данных и провайдера OLE DB. Например, если вы подключитесь к Paradox с использованием провайдера ODBC OLE DB, вы получите сообщение об ошибке, указывающее на то, что база данных или провайдер OLE DB не могут начать транзакцию. Чтобы определить уровень поддержки транзакций, можно воспользоваться динамическим свойством Transaction DDL соединения:


if ADOConnection1.Properties['Transaction DDL'].Value > DBPROPVAL_TC_NONE then ADOConnection1.BeginTrans;

Если вы попытаетесь обратиться к этой же базе данных Paradox при помощи провайдера Jet 4.0 OLE DB, никакой ошибки не возникнет, однако из-за ограничений провайдера вы не сможете выполнить откат транзакции.
Еще одно странное отличие проявляет себя при работе с Access: если вы используете провайдер ODBC OLE DB, вы сможете использовать транзакции, однако не сможете использовать вложенные транзакции. Попытка открыть новую транзакцию параллельно с уже существующей активной транзакцией приведет к возникновению ошибки. Однако при использовании механизма Jet вы сможете без проблем использовать вложенные транзакции.

[В начало]

Вложенные транзакции

Используя программу TransProcessing, попробуйте выполнить следующий тест.

  1. Активизируйте транзакцию.

  2. Измените значение поля ContactName записи Around The Horn: вместо Thomas Hardy поставьте Dick Solomon.

  3. Активизируйте еще одну, вложенную транзакцию.

  4. Измените значение поля ContactName записи Bottom-Dollar Markets: вместо Elizabeth Lincoln поставьте Sally Solomon.

  5. Выполните откат внутренней транзакции.

  6. Подтвердите внешнюю транзакцию.

В результате модификации должны быть внесены только в запись Around The Horn. Если же внутренняя транзакция будет подтверждена, а в отношении внешней транзакции вы выполните откат, в результате в базу данных вообще не будет внесено ни одного изменения (даже изменения, сделанные в рамках внутренней транзакции). Именно так работают вложенные транзакции. Существует ограничение: Access поддерживает только пять уровней вложения транзакций.
ODBC не поддерживает вложенных транзакций, а провайдер Jet OLE DB поддерживает до пяти уровней вложения. Провайдер SQL Server OLE DB вообще не поддерживает вложения транзакций. Вы должны иметь в виду, что вложение транзакций может обрабатываться по-разному в зависимости от версии SQL-сервера или драйвера. Необходимую информацию можно получить в документации и при помощи экспериментов. Судя по всему, в большинстве случаев внешняя транзакция определяет, будут ли внесены в базу данных изменения, сделанные в рамках внутренней транзакции.

[В начало]

Атрибуты компонента ADOConnection

Если вы планируете использовать вложенные транзакции, существует еще одно обстоятельство, которое вы должны принимать во внимание. Компонент ADOConnection обладает свойством Attributes, которое определяет, каким образом ведет себя соединение в момент, когда транзакция подтверждается или выполняется ее откат. В свойстве Attributes хранится множество значений TXActAttributes, которое изначально пусто. Перечисление TXActAttributes включает в себя только два значения: xaCommitRetaining и xaAbortRetaining (иногда это значение ошибочно записывают как xaRollbackRetaining, так как с логической точки зрения это более правильное название). Если в свойстве Attributes присутствует атрибут xaCommitRetaining, в момент подтверждения транзакции автоматически открывается новая транзакция. Если в свойстве Attributes присутствует атрибут xaAbortRetaining, в момент отката транзакции автоматически открывается новая транзакция. Таким образом, если вы добавите в свойство Attributes оба этих атрибута, любые действия будут выполняться в рамках транзакции: в момент завершения очередной транзакции будет автоматически активизироваться следующая.
В большинстве случаев программисты предпочитают отказаться от работы в таком режиме и самостоятельно контролировать открытие транзакций, поэтомуданные атрибуты используются нечасто. Следует принимать во внимание особенности использования этих атрибутов совместно с вложенными транзакциями. Если вы создаете вложенную транзакцию и присваиваете свойству Attributes значение [xaCommitRetaining, xaAbortRetaining], внешняя транзакция никогда не будет завершена. Рассмотрим такую последовательность событий.

  1. Начинается внешняя транзакция.

  2. Начинается внутренняя транзакция.

  3. Выполняется подтверждение или откат внутренней транзакции.

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

[В начало]

Типы блокировки

Технология ADO поддерживает четыре различных подхода к блокированию данных для обновления: ltReadOnly, ltPessimistic, ltOptimistic и ltBatchOptimistic (существует также тип ltUnspecified, однако по изложенным ранее причинам этот тип в Delphi не используется). Для настройки режима блокировки используется свойство LockType. В данном разделе я коротко расскажу обо всех четырех способах блокировки. В последующих разделах о каждом из этих способов будет рассказано подробнее.
Значение ltReadOnly указывает на то, что данные предназначены только для чтения, — обновление невозможно. Так как клиент не может выполнить модификацию данных, никакой блокировки не требуется.
Значения ltPessimistic и ltOptimistic обеспечивают пессимистическую и оптимистическую блокировку соответственно. Эти режимы являются эквивалентами аналогичных режимов BDE. Однако по сравнению с BDE технология ADO обеспечивает большую гибкость: выбор режима блокировки остается за вами. Если вы используете BDE, решение об использовании пессимистической или оптимистической блокировки выполняет за вас драйвер BDE. Если вы используете настольную базу данных, такую как Paradox или dBase, значит, драйвер BDE использует пессимистическую блокировку. Если вы используете клиент-серверную базу данных, такую как InterBase, SQLServer или Oracle, драйвер BDE использует оптимистическую блокировку.

[В начало]

Пессимистическая блокировка

В данном контексте терминами «оптимистическая» и «пессимистическая» характеризуется ожидание программиста относительно возможности возникновения конфликтов при обновлении содержащихся в БД данных одновременно несколькими пользователями. Пессимистическая блокировка предполагает, что вероятность возникновения конфликта велика. Иными словами, пользователи в одно и то же время модифицируют содержащиеся в БД данные, и высока вероятность того, что два пользователя в одно и то же время попытаются модифицировать одну и туже запись базы. Чтобы предотвратить подобный конфликт, запись блокируется в момент, когда начинается редактирование. Запись остается заблокированной до тех пор, пока редактирование завершается или отменяется. Если какой-либо другой пользователь попытается отредактировать ту же самую (заблокированную) запись, он не сможет этого сделать: возникнет исключение «Обновление невозможно, запись заблокирована».
Этот подход хорошо знаком программистам, которые ранее работали с настольными базами данных, такими как dBase и Paradox. Преимущество состоит в том, что пользователь знает, что если он начал редактировать запись, то сможет успешно завершить редактирование и внести модификации в базу. Недостаток — в том, что пользователь полностью контролирует блокирование записи. Если пользователь хорошо освоил работу с приложением, редактирование одной записи может занять всего пару секунд, однако в клиент-серверной среде с множеством пользователей даже пара секунд может показаться вечностью. С другой стороны, ничего не подозревающий пользователь может начать редактирование записи и уйти на обед. В этом случае запись останется заблокированной до тех пор, пока он не вернется на свое рабочее место. Если не предпринять каких-либо специальных мер, все это время никто не сможет отредактировать заблокированную запись. Чтобы избежать подобного, зачастую используют таймер: если клавиатура и мышь длительное время остаются в бездействии, программа автоматически разблокирует запись.
Еще одна проблема, связанная с пессимистическим блокированием, заключается в том, что для пессимистического блокирования требуется курсор, работающий на стороне сервера. Ранее мы уже говорили о том, что местоположение курсора влияет на типы доступных курсоров. Сейчас мы видим, что местоположение курсора влияет также на способы блокирования. Позднее в данной главе мы подробнее обсудим преимущества использования курсоров, работающих на стороне клиента. Если вы примете решение воспользоваться этими преимуществами, значит, вы не сможете воспользоваться пессимистической блокировкой.

[В начало]

Обновление данных

Поддержка обновляемых соединений (updatable joins) является одной из основных причин, по которым программисты используют компонент ClientDataSet (или кэшируемые обновления в BDE). Запрос, подразумевающий соединение двух таблиц, возвращает пользователю единую таблицу, и пользователь хочет обладать возможностью модифицировать записи этой таблицы. Рассмотрим следующее SQL-выражение:


SELECT FROM Products, Suppliers WHERE Suppliers.SupNo=Products.SupNo

Этот запрос возвращает список продуктов с указанием поставщиков, которые выполняют поставку этих продуктов. Механизм BDE рассматривает любое SQLсоединение так таблицу, предназначенную только для чтения. Дело в том, что добавление, обновление и удаление записей в объединенной таблице неоднозначно. Например, если пользователь добавляет в объединенную таблицу новую запись, надо ли добавлять в таблицу нового поставщика и новый продукт или можно ограничиться только добавлением продукта? Архитектура ClientDataSet/Provider позволяет вам указать первичную обновляемую таблицу (в этой книге об этом не рассказывается) и выполнить дополнительную настройку SQL-обновлений. Об этом частично рассказано в главе 14, а кроме того, я расскажу об этом в главе 16. Технология ADO поддерживает механизм кэширования обновлений, который называется пакетными обновлениями (batch updates) и функционирует приблизительно так же, как аналогичный механизм BDE. В следующем разделе я более подробно рассмотрю механизм пакетных обновлений ADO. Однако для того, чтобы решить проблему обновления SQL-соединений вы можете обойтись и без помощи этого механизма. Дело в том, что ADO поддерживает обновление SQL-соединений. В программу JoinData добавлен компонент ADODataset, основанный на приведенном ранее SQL-выражении. Если вы запустите программу, вы сможете отредактировать одно из полей и сохранить изменения в базе (для этого достаточно переместиться на другую запись). Никаких ошибок не возникнет, так как ADO успешно выполнит обновление БД. Дело в том, что в отличие от BDE в ADO используется более практический подход. В рамках ADO, когда выполняется соединение нескольких таблиц, каждое поле знает, к какой таблице оно принадлежит. Если вы обновляете поле в таблице Products и публикуете изменения в базе, для обновления формируется SQL-выражение UPDATE, которое обновляет значение поля в таблице Products базы данных. Если помимо поля таблицы Products вы изменяете также поле таблицы Suppliers, значит, генерируются два SQL-выражения UPDATE — по одному для каждой таблицы.
При добавлении новой строки в SQL-соединение механизм ADO ведет себя подобным же образом. Если вы вставляете строку и добавляете значения только для полей таблицы Products, значит, генерируется только одно SQL-выражение INSERT, которое добавляет новую запись в таблицу Products. Если вы вводите значения для полей обеих таблиц, генерируется два SQL-выражения INSERT — по одному для каждой таблицы. Важен порядок, в котором выполняются эти выражения, так как новый продукт может ссылаться на нового поставщика, поэтому информация о поставщике должна добавляться в таблицу Suppliers в первую очередь. Серьезная проблема возникает, если выполняется удаление строки из объединенной таблицы. При попытке выполнить удаление строки объединенной таблицы вы увидите сообщение об ошибке. Конкретный текст сообщения зависит от версии ADO, а также от используемой базы данных. Это сообщение может сбить вас с толку, так как, скорее всего, оно не будет иметь отношения к истинной причине проблемы. Проблема связана с тем, что невозможно удалить запись, на которую ссылаются другие записи. В нашем примере вы, скорее всего, увидите сообщение о том, что невозможно удалить запись о продукте, потому что существуют другие записи, ссылающиеся на эту запись. Однако если вы проведете пару экспериментов, вы обнаружите, что ошибка возникает вне зависимости от того, существуют ли в базе другие записи, ссылающиеся на удаляемый продукт, или таких записей нет. Чтобы понять причину проблемы, необходимо воспользоваться таким же подходом, какой используется при добавлении новых записей в объединенную таблицу. В случае удаления строки объединенной таблицы механизм ADO генерирует два SQL-выражения DELETE: одно для таблицы Suppliers, а второе — для таблицы Products. Выражение DELETE для таблицы Products выполняется успешно, а вот выражение DELETE для таблицы Suppliers дает сбой — поставщик продуктов не может быть удален из таблицы, так как с ним, как правило, связано несколько ссылающихся на него записей о продуктах.

СОВЕТ

Если вы хотите посмотреть, какие именно SQL-выражения генерируются в результате выполнения той или иной команды ADO, имейте в виду, что, используя SQL Server, вы можете просмотреть эти выражения при помощи инструмента SQL Server Profiler.

Даже если вы понимаете, как именно работает этот процесс, полезно взглянуть на проблему глазами пользователя. Я могу поспорить, что когда пользователь удаляет запись из объединенной таблицы, в 99 процентах случаев он намерен удалить только запись о продукте, оставив запись о поставщике без изменений. К счастью, вы можете добиться такого результата, если воспользуетесь специальным динамическим свойством Unique Table. С его помощью вы можете указать, что удаление строки имеет отношение только к таблице Products, но не к таблице Suppliers. Для этого используется следующий код:


ADOQuery1.Properties['Unique Table'].Value := 'Products';

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

[В начало]

Пакетные обновления (Batch updates)

При использовании механизма пакетных обновлений любые изменения, вносимые пользователем, накапливаются в локальной памяти. Позже полный пакет этих изменений за одну операцию может быть внесен в базу данных. Очевидно, что подобный подход обеспечивает выигрыш в производительности, однако существуют и другие преимущества, делающие его удобным. В частности, при использовании механизма пакетных обновлений пользователь может выполнять изменения даже тогда, когда он отключен от базы данных. Эта возможность может употребляться для обеспечения работы пользователя в автономном режиме, при применении технологий наподобие Briefcase (Портфель), а также в веб-приложениях, основанных еще на одном механизме ADO, который называется RDS (Remote Data Services). Вы можете включить пакетные обновления для любого набора данных ADO. Для этого необходимо присвоить свойству LockType значение ltBatchOptimistic перед тем, как набор данных будет открыт. Кроме того, вы должны присвоить свойству CursorLocation значение clUseClient, так как пакетные обновления обрабатываются механизмом ADO Cursor Engine. В результате любые изменения, вносимые пользователем в набор данных, будут сохраняться в области delta (в этой области хранится список изменений). Набор данных будет выглядеть так, как будто данные были изменены, однако на самом деле информация об изменениях хранится в памяти — эти изменения не внесены в базу данных. Чтобы опубликовать изменения в БД (перенести их из памяти в базу данных), необходимо обратиться к методу ApplyBatch (этот метод эквивалентен методу ApplyUpdates механизма BDE):


ADODataSet1.UpdateBatch;

Если вы хотите отменить все накопленное в памяти, воспользуйтесь методом CancelBatch — это аналог метода CancelUpdates. В рамках механизмов пакетных обОбновление новлений ADO, кэшируемых обновлений BDE и механизма кэширования Client- DataSet используется много других методов и свойств со схожими именами. Например, как и в BDE, свойство UpdateStatus набора данных ADO может использоваться для того, чтобы узнать состояние некоторой записи: была ли запись добавлена, модифицирована, удалена или она не подвергалась каким-либо изменениям. Это свойство весьма удобно, если вы хотите выделить модифицированные записи цветом или отобразить их состояние в строке состояния. Существуют некоторые различия в синтаксисе, например вместо вызова RevertRecord в ADO используется вызов CancelBatch(arCurrent);. Одна весьма полезная возможность механизма кэшированных обновлений BDE отсутствует в ADO: механизм слежения за тем, существуют ли неопубликованные в БД обновления. В BDE для этой цели используется свойство UpdatesPending. Это свойство содержит в себе значение True в случае, если в набор данных были внесены изменения, которые еще не опубликованы в базе данных. Это свойство удобно использовать в обработчике события OnCloseQuery:


procedure TForm1.FormCloseQuery( Sender: TObject; var CanClose: Boolean); begin CanClose := True; if ADODataSet1.UpdatesPending then canClose := (MessageDlg('Существуют изменения, которые еще не опубликованы в БД' #13 + 'Завершить приложение в любом случае?', mtConfirmation, [mbYes, mbNo], 0) = mrYes); end;

Однако, обладая необходимыми знаниями и изобретательностью, вы можете написать свою собственную функцию ADOUpdatesPending. Чтобы написать такую функцию, вы должны знать, что наборы данных ADO поддерживают свойство FilterGroup, которое функционирует примерно так же, как фильтр. В отличие от свойства Filter стандартного набора данных, которое фильтрует данные, исходя из некоторого условия, свойство FilterGroup может выполнять фильтрацию, исходя из состояния записи. Существует несколько состояний фильтрации, и одно из них соответствует значению fgPendingRecords. Это состояние соответствует записям, которые были модифицированы, но информация об изменении которых еще не была опубликована в базе данных. Таким образом, чтобы взглянуть на все изменения, которые были сделаны, но не опубликованы, достаточно выполнить две строки кода:


ADODataSet1.FilterGroup := fgPendingRecords; ADODataSet1.Filtered := True;

Естественно, после выполнения этих команд в наборе данных будут присутствовать записи, которые были удалены, при этом поля этих записей будут пустыми, — это не очень удобно, так как вы не сможете понять, какая именно запись была удалена. (Первая версия ADOExpress функционировала иначе: для удаленных записей отображались все значения полей.)
Чтобы решить проблему UpdatesPending, вам потребуется использовать клонирование наборов данных (об этой возможности рассказывалось ранее). Функция ADOUpdatesPending настраивает свойство FilterGroup таким образом, чтобы в наборе данных присутствовала только информация о сделанных, но не опубликованных изменениях. Теперь надо проверить, присутствует ли в наборе данных хотя бы одна запись. Если хотя бы одна запись присутствует, значит, некоторые изменения еще не опубликованы в базе данных. Если после фильтрации набор данных оказался пустым, значит, все сделанные ранее изменения уже опубликованы в БД. Однако если вы попытаетесь проверить количество записей в реальном наборе данных, перенастройка свойства FilterGroup приведет к смещению указателя на текущую запись — пользовательский интерфейс немедленно отреагирует на это. Во избежании этого, воспользуйтесь клоном набора данных:


function ADOUpdatePending(ADODataSet: TCustomADODataSet): boolean; var Clone: TADODataSet; begin Clone := TADODataSet.Create(nil); try Clone.Clone(ADODataSet); Clone.FilterGroup := fgPendingRecords; Clone.Filtered := True; Result := not (Clone.BOF and Clone.EOF); Clone.Close; finally Clone.Free; end; end;

В этой функции вы клонируете изначальный набор данных, настраиваете свойство FilterGroup, а затем проверяете, находится ли указатель одновременно в начале и в конце набора данных. Если это так, значит, набор данных пуст.

[В начало]

Оптимистическая блокировка

Ранее мы с вами рассмотрели использование свойства LockType и обсудили принцип функционирования пессимистической блокировки. В данном разделе мы рассмотрим оптимистическую блокировку. Оптимистическая блокировка не только является предпочтительной для высокопроизводительных систем, помимо этого именно такой тип блокировки используется при выполнении пакетных обновлений.
Оптимистическая блокировка предполагает, что возникновение конфликта между двумя пользователями, пытающимися отредактировать одну и ту же запись одновременно, маловероятно. Отсюда следует, что любому пользователю разрешается редактировать любую из записей в любое время. Последствия конфликтов обрабатываются в момент сохранения изменений в базе данных. Таким образом, конфликты рассматриваются как исключение из правила. Если два пользователя попытаются сохранить изменения одной и той же записи, выполнить это удастся только первому пользователю, второму пользователю будет отказано. Подобное поведение реализуется в приложениях, работающих в рамках модели Briefcase (Портфель), а также в веб-приложениях, в которых отсутствует постоянное соединение с базой данных и поэтому невозможно реализовать пессимистическую блокировку. В отличие от пессимистической, оптимистическая блокировка не требует длительного блокирования ресурсов: запись блокируется только в момент обновления. Таким образом, в среднем потребление ресурсов ниже, а база данных более масштабируема.
Давайте рассмотрим пример. Представьте, что у вас есть компонент ADODataSet, соединенный с таблицей Customer из примера dbdemos.mdb; при этом свойство LockType обладает значением ltBatchOptimistic, а содержимое набора данных отображается в сетке DBGrid. Предположим также, что у вас есть кнопка, при щелчке на которой происходит обращение к методу UpdateBatch. Запустите две копии этой программы (это программа BatchUpdates). Начните редактирование записи в первой копии программы. Для простоты в процессе демонстрации я использую один компьютер, однако все то же самое произойдет и в случае, если редактирование одной и той же записи будет выполняться на двух компьютерах.

  1. Выберите компанию Bottom-Dollar Markets в Канаде и измените ее имя на Bottom-Franc Markets.

  2. Сохраните изменения в базе, для этого просто перейдите к другой записи и щелкните на кнопке пакетного обновления.

  3. Во второй копии программы найдите ту же самую запись и измените имя компании на Bottom-Pound Markets.

  4. Перейдите к другой записи и щелкните на кнопке пакетного обновления. Обновление не сработает.

Как и в случае с другими сообщениями об ошибках ADO, текст сообщения будет зависеть не только от версии ADO, но также от того, насколько точно вы повторили описанную последовательность действий. В ADO 2.6 на экране появится следующее сообщение об ошибке: Row cannot be located for updating. Some values may have been changed since it was last read (Не удается обнаружить строку для обновления. Некоторые значения строки могли быть изменены с момента последнего чтения).
В этом заключается вся суть оптимистической блокировки. Для обновления записи выполняется следующее SQL-выражение:


UPDATE CUSTOMER SET CompanyName="Bottom-Pound Markets" WHERE CustomerID="BOTTM" AND CompanyName="Bottom-Dollar Markets"

Выражение идентифицирует запись, используя первичный ключ и значение поля CompanyName, какими они были в момент чтения записи из базы в память. Ожидается, что в результате выполнения этого выражения будет модифицирована одна запись таблицы. Однако в рассмотренном нами примере в результате выполнения выражения ни одна из записей не модифицируется. Такой исход возможен, только если запись была удалена, изменился первичный ключ или изменяемое поле было изменено кем-то до вас. В любом из этих случаев обновление окончится неудачей.
Если бы второй пользователь попытался изменить значение поля ContactName (но не CompanyName), тогда выражение обновления выглядело бы следующим образом:


UPDATE CUSTOMER SET ContactName="Liz Lincoln" WHERE CustomerID="BOTTM" AND ContactName="Elizabeth Lincoln"

В нашем случае обновление было бы выполнено успешно, так как другой пользователь не изменит ни первичный ключ, ни контактное имя. Это поведение напоминает режим Update Where Changed механизма BDE. Вместо свойства UpdateMode, используемого в BDE, в рамках ADO используется динамическое свойство Update Criteria набора данных. В следующем списке перечислены допустимые значения этого динамического свойства.

Константа

Способ идентификации записей

adCriteriaKey

Только столбцы, входящие в состав первичного ключа

adCriteriaAllCols

Все столбцы

adCriteriaUpdCols

Только столбцы первичного ключа и модифицированные столбцы

adCriteriaTimeStamp

Только столбцы первичного ключа и столбец отметки времени

Не следует думать, что один из этих режимов будет предпочтительным для всего вашего приложения. На практике выбор режима определяется тем, информация какого характера содержится в таблице. Предположим, в таблице Customer присутствуют только три поля: CustomerID (идентификатор), Name (имя) и City (город). Изменение любого из этих полей никак не влияет на значения других полей таблицы, поэтому в подобной ситуации вполне можно использовать режим adCriteriaUpdCols (этот режим используется по умолчанию). Однако если помимо перечисленных полей в таблицу входит еще поле PostalCode (почтовый индекс), тогда обновление этого поля должно быть в обязательном порядке согласовано с обновлением поля City (город). Иными словами, нельзя допустить, чтобы один пользователь модифицировал поле PostalCode и в то же самое время другой пользователь модифицировал поле City без всякого согласования с первым пользователем. В этом случае более безопасным будет режим обновления adCriteriaAllCols. Следует также рассказать о том, как ADO осуществляет обработку ошибок в процессе обновления нескольких записей. В BDE и ClientDataSet для этой цели вы можете воспользоваться событием OnUpdateError, которое позволит вам отреагировать на ошибку, связанную с обновлением записи, и разрешить проблему перед тем, как перейти к следующей записи. В ADO такой возможности нет. Вы можете следить за прогрессом, успехом или неудачей пакетного обновления при помощи событий OnWillChangeRecord и OnRecordChangeComplete, однако вы не можете изменить содержимое обновляемой записи и попытаться заново внести ее в базу, как это возможно в рамках BDE и ClientDataSet. Проблема состоит также в том, что если ошибка возникает в ходе пакетного обновления, процедура обновления не останавливается, а продолжает выполняться. Пакетное обновление выполняется до самого конца, то есть до тех пор, пока все изменения не будут внесены в базу (при этом могут возникнуть другие ошибки). В результате вы можете получить сбивающее с толку или неправильное сообщение об ошибке. Если в ходе пакетного обновления возникло несколько ошибок (не удалось обновить несколько записей), ADO 2.6 отобразит на экране следующее сообщение: Multistep OLE DB operation generated errors. Check each OLE DB status value, if available. No work was done (В ходе выполнения многошаговой операции OLE DB возникли ошибки. Если возможно, проверьте каждое из состояний OLE DB. Никакой работы не было сделано). Проблема заключается в последнем предложении: No work was done. ADO сообщает нам, что ничего не было сделано, однако это не так. Запись, ставшая причиной ошибки, действительно не была обновлена, однако все остальные записи успешно опубликованы в базе данных.

[В начало]

Разрешение конфликтов, связанных с обновлением данных

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


ADODataSet1.FilterGroup := fgConflictingRecords; ADODataSet1.Filtered := True;

Для каждого поля каждой проблемной записи можно сообщить пользователю три ключевых значения: значение поля, которое содержалось в записи в момент, когда эта запись была впервые прочитана из БД; значение поля, которое было присвоено полю этим пользователем; значение, которое содержится в БД на текущий момент (то есть во время попытки обновления). Для этого служат следующие свойства Tfield.

Свойство

Описание

NewValue

Значение, занесенное в поле пользователем

CurValue

Значение, которое обнаружено в базе в момент обновления

OldValue

Значение, которое содержалось в базе в момент первого чтения записи

Те, кто работает с компонентом ClienDataSet, знают о существовании удобного диалогового окна ReconcileErrorForm. Это диалоговое окно формируется автоматически, оно показывает пользователю старые, новые и текущие значения полей проблемной записи и позволяет пользователю выбрать метод решения конфликта. К сожалению, в ADO аналог этого диалогового окна отсутствует. Класс TReconcileErrorForm был разработан специально для компонента ClientDataSet, поэтому его очень сложно адаптировать для использования с наборами данных ADO. Следует также сказать о порядке функционирования упомянутых свойств класса TField. Эти свойства основаны на объектах ADO Fields, на которые они ссылаются. Это означает, что порядок функционирования этих свойств целиком и полностью определяется провайдером OLE DB, который вы используете. Остается надеяться, что используемый вами провайдер корректно поддерживает возможности, в которых вы нуждаетесь. Большинство провайдеров хорошо справляются с этой задачей, однако провайдер Jet OLE DB возвращает одно и то же значение для свойств CurValue и OldValue. Говоря точнее, в качестве текущего значения поля возвращается значение, которое содержалось в поле в момент первого чтения записи из базы данных. Иными словами, Jet не позволяет определить значение, присвоенное полю другим пользователем (если только вы сами не предпримете каких-либо дополнительных действий, чтобы осуществить это). Если вы используете провайдер SQL Server OLEDB, вы сможете обратиться к свойству CurValue только после выполнения метода Resync набора данных, при этом параметр AffectRecords должен быть равен значению adAffectGroup, а параметр ResyncValues — содержать значение adResyncUnderlyingValues. Вот соответствующий код:


adoCustomers.FilterGroup := fgConflictingRecords; adoCustomers.Filtered := true; adoCustomers.Recordset.Resync(adAffectGroup, adResyncUnderlyingValues);

[В начало]

Отключенные наборы записей

Теперь, когда вы знаете о пакетных обновлениях, мы можем приступить к изучению еще одной возможности ADO: отключенных наборах записей. Отключенный набор записей (disconnected recordset) — это набор записей, который отключен от подключения к базе данных. Пользователь может работать с таким набором записей в точности так же, как он работает с обычным, подключенным набором записей. Это весьма впечатляющая возможность: между подключенным и отключенным наборами записей фактически не существует различий. Свойства и возможности фактически идентичны. Чтобы отключить набор записей от подключения, необходимо присвоить свойству CursorLocation значение clUseClient, а свойству LockType — значение ltBatchOptimistic. После этого вы присваиваете свойству Connection значение nil, в результате набор записей становится отключенным:


ADODataSet1.Connection := nil;

После этого набор записей продолжает хранить в себе те же данные и поддерживать те же возможности навигации. Он позволяет добавлять, редактировать и удалять записи. Единственное отличие состоит в том, что вы не можете выполнить пакетное обновление, так как для этого вам потребуется подключиться к серверу БД. Чтобы восстановить соединение (и воспользоваться методом UpdateBatch), необходимо вновь настроить свойство Connection:


ADODataSet1.Connection := ADOConnection1;

Эта возможность также поддерживается в BDE и в других технологиях работы с данными, однако для этого вы должны переключиться на использование Client- DataSet. Прелесть ADO состоит в том, что вы с самого начала можете разработать приложение так, будто бы вы понятия не имеете о возможности отключения набора записей от БД. Вы используете обычные компоненты dbGo самым обычным образом. После этого вы можете без лишних сложностей добавить в вашу программу возможность отключения от БД так, как будто вы только что узнали о существовании этой возможности. При этом вам не потребуется менять основной код вашего приложения.
Отключение набора записей может потребоваться для того, чтобы:

  • минимизировать общее количество параллельных подключений к БД;

  • обеспечить работу приложения в автономном режиме (в соответствии с моделью Briefcase).

О приложениях, работающих в рамках модели Briefcase (Портфель), мы поговорим в одном из следующих разделов.
Большинство бизнес-приложений, работающих в рамках концепции «клиентсервер», открывают таблицы базы данных и поддерживают постоянное подключение к БД в течение всего времени, пока таблица находится в открытом состоянии.
Однако на самом деле подключение необходимо только во время выполнения двух операций: извлечение данных из БД и обновление данных в БД. Представьте, что вы модернизируете вашу программу таким образом, чтобы она выполняла отключение от базы данных сразу же после того, как происходит открытие таблицы и извлечение данных. Как только необходимые данные передаются на клиентский компьютер, соединение разрывается. Вот соответствующий код:


ADODataSet1.Connection := nil; ADOConnection1.Connected := False;

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


ADOConnection1.Connected : = True; ADODataSet1.Connection := ADOConnection1; try ADODataSet1.UpdateBatch; finally ADODataSet1.Connection := nil; ADOConnection1.Connected := False; end;

Если вы будете использовать подобный подход в вашем приложении, среднее количество соединений с БД, открытых в одно и то же время, будет минимальным — соединения будут открываться только на короткое время, когда они действительно нужны. Очевидно, что подобную клиент-серверную структуру существенно проще масштабировать. Сервер сможет обслуживать значительно большее количество клиентов. Существует также недостаток: формирование соединения с некоторыми (но не со всеми) БД требует значительного времени, в подобной ситуации пакетное обновление будет выполняться медленнее.

[В начало]

Накопление соединений (Connection Pooling)

Разговоры о разрыве и повторном открытии соединений наводят нас на мысль, нельзя ли использовать соединения повторно? Этот механизм называется Connection Pooling. При его использовании, когда приложение завершает работу с подключением, подключение не уничтожается, вместо этого оно откладывается в область накопления (пул) и затем может использоваться повторно. Этот процесс выполняется автоматически, конечно, при условии, что используемый вами провайдер OLE DB поддерживает его и этот механизм включен. В этом случае никаких дополнительных действий предпринимать не надо.
Производительность является основной причиной применения подобного механизма. Чтобы установить соединение с базой данных, зачастую требуется время. Если вы имеете дело с настольной базой данных, такой как Access, соединение устанавливается фактически мгновенно. Однако в клиент-серверной среде, например при использовании сервера Oracle, доступ к которому осуществляется через сеть, для формирования соединения может потребоваться несколько секунд. Таким образом, имеет смысл подумать о повторном использовании такого ценного ресурса, как соединение с БД.
При использовании механизма накопления ADO каждый раз, когда приложение «уничтожает» объект ADOConnection, этот объект добавляется в специальный пул (место временного хранения). Если в дальнейшем возникает необходимость создать новое соединение, система автоматически выполняет поиск подходящего соединения в пуле. Если обнаруживается существующее соединение, обладающее строкой подключения, совпадающей с той, в которой нуждается пользователь, это соединение используется повторно. Если подходящего соединения не обнаруживается, происходит его создание. Соединения остаются в пуле до тех пор, пока либо они не будут востребованы, либо приложение не завершит работу, либо не истечет время тайм-аута. По умолчанию продолжительность тайм-аута составляет 60 секунд, однако начиная с версии MDAC 2.5 это значение можно изменить. Для этого необходимо указать продолжительность тайм-аута в ключе реестра HKEY_CLASSES_ROOT\ CLSID\<CLSID-провайдера>\SPTimeout. Процесс накопления и повторного использования подключений выполняется в прозрачном режиме без каких-либо дополнительных действий со стороны разработчика. Аналогичный механизм накопления соединений функционирует в BDE при использовании MTS (Microsoft Transaction Server) и COM+, однако в отличие от BDE, механизм ADO выполняет накопление соединений самостоятельно, без помощи MTS или COM+.
По умолчанию механизм накопления соединений активизирован для всех провайдеров MDAC OLE DB реляционных баз данных (включая SQL Server и Oracle). Важным исключением является провайдер Jet OLE DB. Если вы используете ODBC, вы можете выбрать между накоплением соединений ODBC и накоплением соединений ADO, однако не рекомендуется использовать оба этих механизма одновременно. Начиная с версии MDAC 2.1 по умолчанию накопление соединений ADO включено, а накопление соединений ODBC отключено.

ПРИМЕЧАНИЕ

Вне зависимости от провайдера OLE DB накопление соединений не осуществляется в среде Windows 95.

К сожалению, в составе ADO отсутствуют инструменты, позволяющие следить за содержимым пула соединений. Однако для этой цели вы можете использовать инструмент SQL Server Performance Monitor, который снабдит вас информацией о подключениях к базе данных SQL Server.
Включить или отключить накопление соединений можно либо при помощи реестра, либо при помощи строки подключения. В реестре для этой цели используется параметр OLEDB_SERVICES, который расположен в разделе HKEY_CLASSES_ROOT\ CLSID\<CLSID-провайдера>. В этом параметре хранится битовая маска, при помощи которой вы можете отключить некоторые службы OLE DB, в том числе накопление соединений, журналирование транзакций и другие. Чтобы отключить накопление соединений с использованием строки подключения, добавьте в конце строки подключения последовательность символов ;OLE DB Services=-2. Чтобы включить накопление соединений для провайдера Jet OLE DB, добавьте в конце строки подключения последовательность ;OLE DB Services=-1. В результате будут включены все службы OLE DB.

[В начало]

Сохранение набора записей в постоянной памяти (Persistent Recordset)

Возможность сохранения набора записей на жестком диске является важной составной частью приложений Briefcase (Портфель). Используя эту возможность, вы можете сохранить содержимое набора записей в файл на локальном жестком диске. Позже вы можете загрузить данные из этого файла в набор записей. Помимо прочих преимуществ, эта возможность позволяет разработчикам создавать реальные однозвенные приложения — вы можете установить приложение базы данных, но при этом не устанавливать саму базу данных. Благодаря этому для установки программы требуется очень небольшое пространство на клиентском жестком диске. Чтобы сохранить набор данных на жестком диске, используется метод SaveToFile:


ADODataSet1.SaveToFile('Local.ADTG');

В результате обращения к этому методу данные вместе с областью дельта сохраняются на локальном жестком диске. Чтобы загрузить данные из файла, можно воспользоваться методом LoadFromFile, который принимает единственный параметр — имя загружаемого файла. Формат файла называется ADTG (Advanced Data Table Gram). Этот формат является собственностью компании Microsoft. Это весьма эффективный формат. При желании вы можете сохранить файл в формате XML, для этого необходимо передать методу SaveToFile второй параметр:


ADODataSet1.SaveToFile('Local.XML', pfXML);

Следует иметь в виду, что (в отличие от ClientDataSet) ADO не содержит в себе собственной поддержки формата XML — для генерации XML-кода используется механизм MSXML. Следовательно, пользователь вашего приложения должен будет установить Internet Explorer 5.0 или загрузить MSXML с веб-узла компании Microsoft.
Если вы планируете сохранять данные локально в формате XML, помните о некоторых недостатках этого подхода:

  • сохранение и загрузка файлов XML выполняется медленнее, чем сохранение и загрузка файлов ADTG;

  • XML-файлы (создаваемые механизмом ADO, впрочем, как и любые другие XML-файлы) обладают значительным размером, их размер существенно превышает размер ADTG-файла, содержащего такие же данные (как правило, размер XML-файла в два раза превышает размер аналогичного ADTG-файла);

  • формат XML, генерируемый ADO, является специфическим форматом Microsoft (это характерно для многих других компаний, поддерживающих собственный порядок генерации XML), это означает, что XML-код, генерируемый ADO, не может быть прочитан компонентом ClientDataSet и наоборот (к счастью, эту проблему можно решить, воспользовавшись входящим в состав Delphi компонентом XML Transform, который позволяет выполнять преобразование между различными структурами XML).

Если вы собираетесь использовать механизм сохранения набора записей только в рамках однозвенного приложения и не предполагаете использовать модель Briefcase (Портфель), тогда вы можете воспользоваться компонентом ADODataSet, присвоить его свойству CommandType значение cmdFile, а свойству CommandText — имя файла. Благодаря этому вы избавляетесь от необходимости обращаться к методу LoadFromFile вручную. Однако вам по-прежнему придется обратиться к методу SaveToFile. В приложении Briefcase (Портфель) этот подход будет слишком ограничивающим, так как в таких приложениях набор данных используется в двух режимах.

[В начало]

Модель Briefcase (Портфель)

Теперь, когда вы знакомы с пакетными обновлениями, отключенными наборами записей и сохранением наборов записей в локальных файлах, вы можете приступить к изучению модели Briefcase (Портфель). Основная идея состоит в том, чтобы обеспечить пользователю возможность работать с вашим приложением даже тогда, когда он не имеет возможности подключиться к базе данных, то есть когда пользователь путешествует или находится в офисе у клиента компании. Проблема заключается в том, что в офисе у клиента пользователь не может подключиться к корпоративной сети вашей компании, а значит, не может подключиться к серверу базы данных. Отсюда следует, что данные не могут попасть на пользовательский портативный компьютер и быть обновлены.
Чтобы обеспечить автономную работу пользователя, вам потребуются такие технологии, как отключенные наборы данных и сохранение наборов данных на локальном диске. Представьте себе, что вы разработали клиент-серверное приложение, которое обеспечивает доступ к серверу базы данных и полностью удовлетворяет запросам пользователя. Теперь пользователь требует, чтобы вы обеспечили работу этого приложения в автономном режиме, то есть даже тогда, когда нет возможности подключиться к серверу БД. Для этого вы должны добавить в приложение возможность сохранения данных на пользовательском локальном жестком диске. Иными словами, прежде чем пускаться в путешествие, пользователь должен отдать команду подготовки приложения к работе в автономном режиме. По этой команде каждая таблица сохраняется в локальном файле при помощи метода SaveToFile. В результате на пользовательском жестком диске возникает коллекция файлов ATDG или XML, которые являются отражением содержимого базы данных. После этого пользователь может отключить свой портативный компьютер от сети и продолжить работу с привычным для него приложением в автономном режиме.
Приложение должно автоматически определять, работает ли оно автономно или существует возможность подключения к сети. Для того чтобы определить это, вы можете попытаться подключиться к базе данных и проверить, открылось ли соединение. Также вы можете проверить присутствие локального файла портфеля или воспользоваться собственным специальным флагом. Если приложение работает в автономном режиме, вместо того чтобы подключаться к базе данных, оно должно извлечь данные из локального файла при помощи метода LoadFromFile. Когда требуется опубликовать данные в базе, вместо метода UpdateBatches приложение должно обращаться к методу SaveToFile для каждой из таблиц. Если возможна связь с базой данных через сеть, необходимо присвоить значение True свойству Connected компонента ADOConnection, а также свойству Active каждого из компонентов ADODataSet. Когда пользователь возвращается из путешествия, он должен внести информацию о сделанных им изменениях в базу данных. Для этого необходимо загрузить данные из локальных файлов, подключить наборы данных к базе данных и обратиться к методу UpdateBatch.

СОВЕТ

Полная реализация модели Briefcase (Портфель) содержится в примере BatchUpdates, о котором уже рассказывалось ранее.

[В начало]

Пара слов об ADO.NET

ADO.NET — это часть новой архитектуры.NET, разработанной компанией Microsoft. Архитектура.NET является попыткой компании Microsoft заново перепроектировать средства и инструменты разработки программного обеспечения для того, чтобы сделать их более удобными для создания веб-приложений. ADO.NET является новым развитием ADO, ориентированным на решение проблем, связанных с разработкой веб-систем и устраняющим многие недостатки устаревшей технологии ADO. Проблема ADO состоит в том, что эта технология основана на COM. Для одно- и двухзвенных приложений COM является вполне приемлемой платформой, однако в мире Веб использовать COM в качестве транспортного механизма фактически невозможно. Для COM характерны три основные проблемы, которые ограничивают использование этой технологии в Веб: во-первых, COM функционирует только в среде Windows, во-вторых, передача наборов записей требует маршализации COM, в-третьих, вызовы COM не могут проникать через корпоративные брандмауэры. Технология ADO.NET решает все три проблемы благодаря использованию XML.
Еще одной особенностью ADO.NET является разделение традиционного набора записей ADO на несколько отдельных классов. Вместо того чтобы решать множество проблем, каждый из классов предназначается для решения одной конкретной проблемы. Например, в ADO.NET присутствует класс DataSetReader, который обеспечивает доступ к данным только для чтения в режиме Forward-only (только вперед), при этом данные располагаются на стороне сервера. Благодаря всем этим ограничениям данный класс обеспечивает быстрое чтение результирующего набора данных. Класс DataTable функционирует как отключенный набор записей на стороне клиента. Класс DataRelation обладает общими чертами с провайдером MSDataShape OLE DB. В любом случае знание традиционной технологии ADO окажется чрезвычайно полезным при изучении новой технологии ADO.NET.

[В начало]

Что далее?

В данной главе мы рассмотрели технологию ADO (ActiveX Data Objects) и dbGo — набор компонентов Delphi, предназначенных для доступа к интерфейсам ADO. Вы узнали о том, как работать с MDAC (Microsoft Data Access Components) и различными механизмами обращения к серверам баз данных. Я рассказал вам о преимуществах и недостатках технологии ADO.
В главе 16 речь пойдет о встроенной в Delphi архитектуре DataSnap, которая позволяет разрабатывать трехзвенные клиентские и серверные приложения. Эту задачу можно решить и при помощи ADO, однако данная книга в первую очередь посвящена Delphi, поэтому я расскажу вам о решении проблемы, которое является естественным для среды Delphi. После того как мы с вами завершим рассмотрение DataSnap, мы продолжим изучать встроенную в Delphi архитектуру для работы с данными. В главе 17 мы рассмотрим процесс разработки собственных компонентов, предназначенных для работы с данными.

[В начало]


Источник: http://www.sql.ru/articles/article.aspx?aid=2366



Рекомендуем посмотреть ещё:


Закрыть ... [X]

Макросы в Excel: как работать с макросами, просто скачав их Как на сайте сделать ссылку на документ

Как сделать sql запрос в excel Как сделать sql запрос в excel Как сделать sql запрос в excel Как сделать sql запрос в excel Как сделать sql запрос в excel Как сделать sql запрос в excel Как сделать sql запрос в excel Как сделать sql запрос в excel

ШОКИРУЮЩИЕ НОВОСТИ