Библиотека: А. Горев, С. Макашарипов, Р. Ахаян. Эффективная работа с СУБД

Глава 8
Использование технологии клиент-сервер
8.1. Работа с внешними данными с помощью технологии ODBC
Команды Transact-SQL
Создание представлений
Создание триггеров
8.2. Использование Visual FoxPro для разработки клиентского приложения
Синхронный и асинхронный процессы
Создание внешних представлений
8.3. Использование Access и Visual Basic для разработки клиентского приложения
8.4. Использование ODBC API для доступа к внешним данным
8.5. Remote Data Objects
8.6. Внешнее управление сервером с помощью SQL-DMO

    До сих пор мы вели речь о приложениях, работающих на одном, локальном компьютере. Если речь шла о компьютерной сети, то БД могла располагаться на файл-сервере или нескольких файл-серверах, в качестве которого может использоваться либо специально выделенный компьютер, либо одна из объединенных в сеть наиболее мощных ПЭВМ. Функции файл-сервера заключаются в основном в хранении БД и обеспечении доступа к ним пользователей, работающих на различных компьютерах. Эти функции обеспечиваются, как правило, той же СУБД, которая работает и на компьютерах пользователей.
    При небольших объемах данных эта схема вполне удовлетворяет всем современным требованиям, но с увеличением числа компьютеров в сети или ростом БД начинают возникать проблемы, связанные с резким падением производительности. Это связано с увеличением объема данных, передаваемых по сети, так как вся обработка производится на компьютере пользователя. Если пользователю требуется пара строк из таблицы объемом в сотни тысяч записей, то сначала вся таблица с файл-сервера передается на его компьютер, а затем СУБД отбирает нужные записи. В этом случае длительные перерывы в работе и число выпитых чашек кофе можно сильно сократить, перейдя на технологию клиент-сервер.
    Технология клиент-сервер разделяет приложение на две части, используя лучшие качества обеих сторон. Front-end (клиентская часть) обеспечивает интерактивный, легкий в использовании, обычно графический интерфейс - находится на компьютере пользователя. Back-end (сервер) обеспечивает управление данными, разделение информации, изощренное администрирование и безопасность - находится на специально выделенных компьютерах или даже мейн-фреймах.
    При технологии клиент-сервер клиентское приложение (front-end) формирует запрос к серверу БД (back-end), на котором выполняются все команды. Результаты команд посылаются затем клиенту для использования и просмотра.
    Visual FoxPro, Visual Basic и Access обеспечивают средства для создания клиентских частей в приложениях клиент-сервер, которые сочетают мощность, скорость, графический интерфейс, продвинутые средства построения запросов и отчетов.
    MS SQL Server является на настоящий момент одним из наиболее мощных серверов БД.

8.1. Работа с внешними данными с помощью технологии ODBC


    Важнейшим этапом в построении приложения клиент-сервер является установка связи клиентского приложения с источником данных, находящимся на сервере БД. В настоящий момент различные средства разработки используют несколько технологий обеспечения доступа к данным. Общепризнанным стандартом, как мы уже писали ранее, является технология ODBC.
    В этом параграфе мы рассмотрим основы применения технологии ODBC для установки связи с MS SQL Server.


Открытый доступ к данным - Open Database Connectivity (ODBC) - это общее определение языка и набор протоколов. ODBC позволяет клиентскому приложению, написанному, например, на Access или Visual FoxPro, работать с командами и функциями, поддерживаемыми сервером.

    В качестве сервера может выступать любой сервер БД, имеющий драйвер ODBC (MS SQL Server, Oracle и т. д.), или даже настольная база данных, ведь часто может возникнуть необходимость в совместной обработке данных, хранящихся в формате Paradox, приложениями, написанными и на FoxPro и на Delphi. ODBC находится как бы посредине между приложением, использующем данные, и самими данными, хранящимися в формате, которые мы не можем обработать напрямую в приложении, и используется как средство коммуникации между двумя front-end и back-end сторонами.
    Для использования ODBC с целью получения данных в сети вам необходимы средства коммуникации между машиной, на которой находятся данные, и станцией, где они будут просматриваться, модифицироваться и, возможно, пополняться. Это специальная программа, которая называется Администратор ODBC на рабочей станции, и драйверы, которые могут работать как с приложением, так и с данными на сервере. Технология ODBC обеспечивает возможность хранить данные на разных серверах БД, причем не обязательно в одном формате. В теории подразумевается, что для этого не надо переписывать ни одной строчки в вашем пользовательском приложении, работающем на клиент-ской ПЭВМ. На практике так бывает не всегда, из-за различий в структуре различных уровней построения ODBC.
    ODBC определяет минимальный набор SQL команд и набор функций вызова с двумя уровнями расширений. Технология ODBC включает также механизм для вызова специфических для сервера возможностей, которые не включены в стандарт ODBC. В целом, это дает возможность разработчику решать самостоятельно, какой уровень функциональности ему достаточен для доступа к серверу баз данных. Разработчик выбирает между наименьшей обеспечиваемой ODBC функциональностью или пытается использовать все возможности сервера.
    С минимальным набором функциональности ODBC разработчик может установить связь с источником данных через стандартный интерфейс загрузки, выполнять SQL команды, выбирать данные и получать сообщения об ошибках, если предпринятое действие закончилось неудачей.
    С расширенной функциональностью ODBC разработчик может использовать дополнительные возможности стандарта SQL и функции ODBC, чтобы расширить возможности управления внешней СУБД. Расширения ODBC включают в себя такие дополнительные возможности, как асинхронное выполнение запросов, нестандартные в среде приложения типы данных (Timestamp, Binary), прокручиваемые курсоры, SQL команды для скалярных функций, внешние объединения, хранимые процедуры и способность использовать SQL расширения, характерные для конкретного сервера БД.
    Если вы используете ODBC драйверы для MS SQL Server, разработанные Microsoft в 1996 году, то вам повезло, так как их расширения предоставляют практически все возможности для полноценной работы с сервером. Если же вы работаете с сервером, отличным от MS SQL Server, или с этим сервером, но драйвер ODBC не разработан фирмой Microsoft, то у вас могут возникнуть трудности ввиду не полной поддержки функциональности сервера драйвером. Если у вас есть сомнение в функциональности драйвера, то для получения полной и содержательной информации о нем используйте функции SQLGetInfo(), SQLGetFunction() и SQLGetStmtOption(). Более подробно о них мы расскажем ниже.
    Одна из главных целей создания ODBC - скрыть сложность соединения с сервером и по мере возможности автоматизировать выполнение многочисленных процедур, связанных с получением данных. ODBC требует от разработчика только имени источника данных, при этом функции драйвера, адреса серверов, сети и шлюзы скрыты от пользователя. Возможно, вам как разработчикам захочется знать все тонкости работы ODBC, но учтите, прямая работа с функциями драйвера требует более сложного программирования, что не всегда сопровождается выигрышем в скорости.
    Описание основных компонентов ODBC приведено в табл. 8.1.

Таблица 8.1. Основные компоненты ODBC
КомпонентыОписание
ПриложениеИспользует ODBC для связи с источником данных, отправления и получения данных. Приложение может использовать функцию SQLConnect(), чтобы передать указатель соединения, имя источника данных, идентификатор пользователя и пароль Диспетчеру драйверов.
Диспетчер драйверовПоддерживает связь между приложением и источником данных, обеспечивая информацией приложение и загружая драйверы динамически, по мере необходимости. ODBC позволяет приложению взаимодействовать с источниками данных разных типов посредством драйверов СУБД, используя Администратор ODBC для установки соединения. Диспетчер драйверов и сами драйверы разработаны как библиотеки DLL. Диспетчер драйверов загружает нужную библиотеку, соединяется с сервером с помощью драйвера, который на самом деле и выполняет все вызовы функций из приложения.
ДрайверВыполняет все вызовы ODBC функций, управляет всеми взаимодействиями между приложением и сервером, переводит SQL выражения на синтаксис источника данных. Драйверы обычно поставляются продавцом сервера БД и имеет смысл получить самую последнюю версию, в которой, вероятнее всего, будет наименьшее количество ошибок. Драйвер перехватывает все ошибки программ, переводя их в стандартные ошибки ODBC. Драйвер позволяет узнать информацию об объектах в базе данных, таких как таблицы, поля, индексы и т. п.
Сервер БДХранит и выводит данные в ответ на запросы со стороны ODBC драйвера. Сервер по запросу возвращает данные или код ошибки, которые передаются в приложение.

    Первые разработчики ODBC столкнулись с проблемой - какой из диалектов SQL поддерживать. Если есть сотни SQL продуктов на рынке, то есть и сотни диалектов SQL. В итоге, чтобы никого не обидеть, разработчики ODBC создали еще один диалект, который называется "ODBC SQL", и компонент, который позволяет программам переводить их собственные диалекты на универсальный диалект и наоборот.
    В первых двух главах мы очень много внимания уделили понятию базы данных как централизованного хранилища всех данных, связанных с функционированием предприятия. Наивно было бы надеяться, что все они всегда будут храниться в одном формате и физически находиться на одной ПЭВМ. Об этом давно догадались сообразительные творцы средств разработки приложений для обработки данных и еще до появления ODBC или параллельно с ним снабдили свои продукты самыми разнообразными технологиями и средствами для доступа к внешним данным.
    Поэтому на настоящий момент разработано очень много технологий для доступа к внешним данным. В них немудрено запутаться, и, чтобы этого не произошло, - ни слова о том, что не имеет отношения к рассматриваемым средствам разработки. Кстати, среди них СУБД Access имеет наибольшее число способов взаимодействия с MS SQL Server, поэтому как пример мы хотим кратко описать и перечислить их (см. также рис. 8.1):


Рис. 8.1.
  • Использование связанных таблиц. Всю работу за вас выполняет процессор баз данных Microsoft Access. Самый надежный в плане программирования способ - он не требует вообще никаких дополнительных драйверов. Вы можете заниматься только обработкой данных, связывать таблицы на сервере с локальными таблицами, создавать запросы.
  • Использование SQL pass-through функций. При этом вы обращаетесь напрямую к серверу, на его языке, минуя процессор баз данных Access. Ваш запрос, хотя и проходит через все компоненты ODBC, ни один из них реально не обрабатывает его.
  • Прямое обращение к ODBC API. При этом способе вы также минуете процессор баз данных Access и напрямую используете Диспетчер драйверов и сам драйвер для доступа к базе данных. Таким образом, ваш код должен обеспечить всю функциональность, которую, как правило обеспечивает процессор баз данных Access. Сложность написания кода даже для довольно простых запросов заставляет обращаться к этому методу только тогда, когда действительно необходима значительная скорость выборки данных.
  • Использование Remote Data Objects. Этот четвертый способ является самым дорогим по материальным затратам, так как для его функционирования необходимо, чтобы на вашей рабочей станции была установлена версия Enterprise Visual Basic 4.0 и выше. Технология Remote Data Objects (RDO) - Объекты для доступа к внешним данным - трактует запросы ODBC как объекты. Запросы работают со свойствами этих объектов и используют их методы, чтобы получить данные. В свою очередь, RDO конвертирует все действия в вызовы ODBC API. В принципе то же самое делает процессор баз данных, но во многих случаях RDO работает быстрее. Однако RDO также требует написания многих строчек кода, хотя и не такого сложного, как в случае прямого обращения к ODBC API. Ну и вспомните, что установка Enterprise версии Visual Basic стоит около 1000 долларов.
  • Использование SQL DMO. За счет использования SQL OLE в этом случае не используются ни процессор баз данных, ни ODBC. В данном случае мы работаем с SQL-DMO. В этой технологии мы можем использовать такие продукты, как Visual Basic for Application, Visual Basic (32 bit), Visual FoxPro и C++ (32 bit), для работы с объектами, которые позволяют нам управлять различными SQL серверами в сети. Этот способ работает только под Windows NT и Windows 95. При этом на рабочей станции вам необходимо иметь клиентскую часть SQL Server. Программирование администрирования SQL Server значительно упрощается благодаря легкому в изучении и использовании объектному языку. Легко можно выполнять все запросы как выборки, так и управления.

    После того как вы подсоединитесь к внешнему источнику данных, вы можете задействовать набор SQL - pass through функций. С помощью них вы можете выполнять SQL команды, которые обеспечивают следующие операции:

  1. Выполнение хранимым процедур.
  2. Выполнение запросов на языке сервера.
  3. Создание новых баз данных, таблиц и индексов на сервере.
  4. Создание и выполнение триггеров, значений по умолчанию, правил проверки ввода и хранимых процедур.
  5. Поддержание бюджетов пользователей или выполнение других задач Системного Администратора.
  6. Выполнение множественных операций добавления и обновления в одном наборе команд.

    Для того чтобы получить доступ ко всем этим возможностям, вам необходимо наличие Администратора ODBC на вашей рабочей станции. Как правило, большинство приложений фирмы Microsoft устанавливают это приложение, если вы при установке указали, что это необходимо. Нужные драйверы, как правило, поставляются с продуктом, доступ к данным которого вы хотите иметь. Помимо этого их выпускают еще третьи фирмы. Можно написать драйвер и самостоятельно, используя ODBC SDK. После того как вы решили все эти проблемы, можете приступать к созданию хранилищ данных на выбранном сервере баз данных.

Команды Transact-SQL

    Теперь, когда мы подошли к вопросу создания приложений клиент-сервер, настала пора продолжить изучение следующего набора SQL команд. Помните, что все они доступны из любого средства разработки приложений, которое либо поддерживает ODBC, либо имеет возможность вызывать функции из внешних API.
    У нас нет возможности описать все диалекты и нет желания описывать стандартный SQL. Поэтому мы остановимся на MS SQL Server. То есть синтаксис приводимых команд и примеры их использования относятся к MS SQL Server.
    Одна из самых сложных команд в Transact-SQL - команда CREATE TABLE имеет много опций и позволяет построить настоящую схему данных. Мы рассматривали эту команду в предыдущем разделе, но синтаксис этой команды в Transact-SQL требует более подробного ее рассмотрения в данном диалекте SQL.
    Приводим синтаксис этой команды, а в табл. 8.2 описание ее аргументов.

CREATE TABLE [database.[owner].]tablename
({colname datatype [NULL | NOT NULL | IDENTITY[(seed, increment)]]
[constraint [ constraint [...constraint]]]
| [[,] constraint]} [[,] {nextcolname | nextconstraint}...])
[ON segmentname]
Таблица 8.2. Аргументы команды CREATE TABLE
АргументНазначение
DatabaseБаза данных, которая будет содержать таблицу. Если этот параметр опущен, то базой данных по умолчанию будет последняя, открытая с помощью команды USE.
OwnerВладелец новой таблицы, если не указан, то владельцем будет считаться текущий пользователь, который запускает команду.
TablenameИмя вновь создаваемой таблицы. Должно удовлетворять правилам SQL для идентификаторов.
ColnameНазвание колонки в таблице. Должно удовлетворять правилам SQL для идентификаторов.
DatatypeТип данных, поддерживаемый в SQL. Может быть как встроенным, так и пользовательским типом.
SeedНачальное значение колонки типа IDENTITY.
IncrementРазница между последовательными значениями в колонке типа IDENTITY.
ConstraintОграничения уровня поля или колонки.
    Пример использования команды:
CREATE TABLE modelSQL
(key_auto smallint IDENTITY(1,1),
model_name varchar(25) NOT NULL,
model_prc money)

    В этом примере используются следующие опции команды CREATE TABLE:
    IDENTITY - указывает, что SQL Server автоматически прибавит единицу (аргумент Increment), причем отсчет начнется с 1 (Seed).
    NOT NULL - указывает, что поле должно иметь значения в каждой новой записи. Это устанавливается по умолчанию. Если нужно разрешить полю не иметь значения, то в поле необходимо указать NULL.
    Каждой колонке должен быть присвоен тип данных, которые она может хранить. В табл. 8.3 мы приведем типы данных, которые поддерживаются в MS SQL Server для колонок.

Таблица 8.3. Допустимые типы данных для MS SQL Server
Тип данныхОписание
bynary(n)Данные бинарного типа длиной ровно n бит. Не может принимать значения типа NULL.
varbynary(n) Данные двоичного типа длиной до n бит. Может принимать значения типа NULL.
char(n) Данные символьного типа длиной ровно n символов. Не может принимать значения типа NULL.
varchar(n) Данные символьного типа длиной до n символов. Может принимать значения типа NULL.
datetimeТип дата, который может принимать значения между 1 января 1753 и 31 декабря 9999 с точностью в 3.33 миллисекунд.
small-datetimeТип дата, который может принимать значения между 1 января 1900 и 06 июня 2079 с точностью в 1 минуту.
decimal(p,s) Десятичное число, которое может иметь всего до p знаков и до s знаков после запятой. Значение p должно быть не больше, чем 38, а s не больше, чем p.
numeric(p,s) Десятичное число, которое может иметь всего до p знаков и до s знаков после запятой. Значение p должно быть не больше, чем 38, а s не больше, чем p.
float(n) Число с плавающей запятой с количеством знаков после запятой не больше 15. Позволяет достигнуть точности 10 в 38 степени.
realЧисло, которое может иметь до 7 знаков после запятой.
intЦелое число, принимающее значения между -2 147 483 648 и 2 147 483 647.
smallintЦелое число, принимающее значения между -32768 и 32767.
tinyintЦелое число, принимающее значения между 0 и 255.
moneyНеокругленное число с плавающей запятой от -922 337 203 685 477.5808 до 922 337 203 685 477.5808.
smallmoneyНеокругленное число с плавающей запятой от -214 746.3648 до 214 746.3647.
bit1 или 0.
TimestampУникальное значение, которое генерирует SQL, каждый раз, когда запись редактируется. Каждая таблица может иметь только одно поле типа Timestamp.
textПоле, принимающее значения символьного типа длиной до 2 Мб.
imageПоле двоичного типа длиной до 2 Мб.

    Помимо приведенного в табл. 8.3 набора типов данных, можно создавать свои типы данных. Для этого используется системная хранимая процедура sp_addtype.
    В синтаксисе команды CREATE TABLE есть слово constraint (ограничение). Разрешено пять типов ограничений:

  1. Первичный ключ (Primary Key) таблицы.
  2. Уникальность (Unique) таблицы.
  3. Ссылка (Foreign Key) таблицы.
  4. Значение по умолчанию (Default) колонки.
  5. Правило проверки уровня колонки (CHECK).

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

[CONSTRAINT constraintname]
     PRIMARY KEY [CLUSTERED | NONCLUSTERED]
(colname [, colname2 [..., colname16]])
[ON segmentname]

    Первичный ключ может проиндексировать до 16 колонок в таблице. В таблице может быть только один первичный ключ. Первичный ключ может относиться к типу CLUSTERED или NONCLUSTERED. Тип CLUSTERED создает объект, в котором физический порядок записей такой же, как и индексный. Естественно, что это увеличивает скорость поиска записи. Только один индекс типа CLUSTERED может присутствовать в таблице. Обратите внимание, что ключевое слово CONSTRAINT опционное. Вы можете именовать ограничение, а можете не именовать. Если вы создадите ограничение с именем, то его имя будет появляться в системных сообщениях в случае, если будет предпринята попытка нарушить наложенное ограничение.
    Например:

CREATE TABLE Auto_Store.YourReadness.FirstTable
(sqlserverstns CONSTRAINT pk_SQL PRIMARY KEY CLUSTERED,
access_users)
    или можно так:
CREATE TABLE Auto_Store.YourReadness.FirstTable
(sqlserverstns PRIMARY KEY CLUSTERED,
access_users)

    Как уже указывалось, в таблице может быть только один первичный ключ, но с помощью ключевого слова UNIQUE можно подготовить кандидатов на первичный ключ и при необходимости с помощью команды ALTER TABLE переустановить первичный ключ. Для создания ключей-кандидатов используется следующий синтаксис:

[CONSTRAINT constraintname]
     UNIQUE [CLUSTERED | NONCLUSTERED]
(colname [, colname2 [..., colname16]])
[ON segmentname]
    Например:
CREATE TABLE Auto_Store.YourReadness.FirstTable
(sqlserverstns CONSTRAINT pk_SQL PRIMARY KEY CLUSTERED,
access_users CONSTRAINT uk_ac_users UNIQUE)

    Учтите, что вы можете иметь только один индекс типа CLUSTERED, то есть в нашем случае второй индекс обязательно будет NONCLUSTERED.
    Следующее ограничение служит для построения ссылочной целостности, то есть для сохранения связей между двумя таблицами по выражению, которое мы укажем с помощью ключевого слова FOREIGN KEY. При этом те же поля должны быть указаны после ключевого слова REFERENCES, где также должна быть указана таблица, с которой организуется связь. Дополнительно необходимо, чтобы в родительской таблице обязательно присутствовал первичный или альтернативный ключ по тем же полям. Синтаксис этого ограничения:

[CONSTRAINT constraintname]
     [FOREIGN KEY (colname [, colname2 [..., colname16]])]
REFERENCES [owner.]reftable [(refcol [, refcol2
     [..., refcol16]])]

    Исходя из вышесказанного, понятно, что в ключ ссылки (FOREIGN KEY) может входить не больше 16 полей. Надо отметить, что ссылки с помощью FOREIGN KEY и REFERENCES мы можем установить только для таблиц, которые находятся в одной базе данных. В следующем примере обратите внимание на изменение в записи ограничения при построения ключа по нескольким полям. Пример построения ссылочной целостности по одному полю:

CREATE TABLE autostore.yourreadness.pat_table
(tree char(20) PRIMARY KEY,
leaf char(10))
CREATE TABLE autostore.yourreadness.child_table
(wood char(20) REFERENCES pat_table(tree))

    В данном примере нет необходимости указывать ключевое слово FOREIGN KEY, так как связь устанавливается по одному полю.
    Пример построения ссылочной целостности по нескольким полям:

CREATE TABLE autostore.yourreadness.pat_table
(tree char(20),
branch money,
leaf char(10),
CONSTRAINT tochild PRIMARY KEY (tree,branch) )
CREATE TABLE autostore.yourreadness.child_table
(tree char(20),
branch money,
childs varchar(25),
FOREIGN KEY (tree,branch) REFERENCES pat_table(tree,branch)

    Следует учитывать, что ключевое слово FOREIGN KEY не создает индекса. Поэтому для лучшей производительности имеет смысл создавать для дочерней таблицы индекс по выражению, которое будет использоваться в ссылке.
    Ограничение Default устанавливает значение по умолчанию для колонки. Оно имеет следующий синтаксис:

[CONSTRAINT constraintname]
     DEFAULT {constantexpression | niladic-function | NULL}
[FOR colname]

    Здесь следует отметить, что можно использовать либо выражение в виде константы, либо функцию (встроенную или пользовательскую), которая не требует аргументов. Совершенно очевидно, что для колонки типа Timestamp значение по умолчанию устанавливать нельзя, так же как и для поля IDENTITY (счетчик). Если вы записываете данное ограничение как отдельное выражение, необходимо использовать ключевое слово FOR colname.
    Рассмотрим пример использования ограничения Constraint. В данном примере создается таблица, колонка accept_date которой будет принимать значение текущей даты при добавлении новой записи в таблицу, - безусловно, только в том случае, если не будет указано явное значение.

CREATE TABLE autostore.yourreadness.child_table
(tree char(20),
branch money,
childs varchar(25),
accept_date datetime DEFAULT getdate()
FOREIGN KEY (tree,branch) REFERENCES pat_table(tree,branch)

    Ограничение Check лимитирует список значений, которые мы можем ввести в колонку. Оно имеет следующий синтаксис:

[CONSTRAINT constraintname]
CHECK [NOT FOR REPLICATION] (expression)

    В случае, если вы используете опцию NOT FOR REPLICATION, данное правило проверки уровня поля не будет срабатывать при операции репликации таблицы, в которой оно используется.
    Рассмотрим пример использования ограничения Check. В следующем примере параметр Check ограничивает ввод в колонке accept_date значениями, не превышающими дату следующего за текущим дня.

CREATE TABLE autostore.yourreadness.child_table
(tree char(20),
branch money,
childs varchar(25),
accept_date datetime CHECK accept_date << getdate()+1
FOREIGN KEY (tree,branch) REFERENCES pat_table(tree,branch)

Создание представлений

    Представления служат для вывода информации из одной или нескольких таблиц путем использование запроса. Синтаксис создания представления существенно короче, чем у команды CREATE TABLE.

CREATE VIEW [owner.]viewname
[(columnname [, columnname]...)]
[WITH ENCRYPTION]
AS selectstatement [WITH CHECK OPTION]

    Здесь аргумент owner определяет владельца этой таблицы. Viewname - название представления. Далее, по выбору, можно указать свои собственные названия колонок, перечислив новые наименования в скобках. Если использовать предложение WITH ENCRYPTION, то можно затруднить пользователям возможность узнать, откуда представление получает данные. Предложение WITH CHECK OPTION позволяет в случае модификации записи посредством представления гарантировать, что запись будет доступна через представление, после того как изменения будут записаны на диск. После ключевого слова AS записывается выражение команды SQL-SELECT.
    Рассмотрим пример создания представления:

CREATE VIEW yourreadness.somekindofview
AS SELECT tree FROM Сhild_table

Создание триггеров

    Триггеры создаются командой CREATE TRIGGER, имеющей следующий синтаксис:

CREATE TRIGGER [owner.]trigger_name
ON [owner.]table_name
FOR {INSERT, UPDATE, DELETE}
[WITH ENCRYPTION]
AS sql_statements

    Есть возможность использовать альтернативный синтаксис, который выглядит так:

CREATE TRIGGER [owner.]trigger_name
ON [owner.]table_name
FOR {INSERT, UPDATE}
[WITH ENCRYPTION]
AS
IF UPDATE (column_name)
[{AND | OR} UPDATE (column_name)...] sql_statements

    Коротко о триггерах можно сказать как об особого рода хранимых процедурах, которые автоматически срабатывают во время операций модификации, удаления или добавления записей. Триггеры используются, как правило, для создания бизнес-правил и поддержки ссылочной целостности данных. Естественно, что непомерное использование триггеров замедляет работу.
    Предложение WITH ENCRIPTION, так же как и в случае с представлениями, служит для того, чтобы скрыть от постороннего глаза SQL выражение, которое он запускает.
    Существуют определенные ограничения на использование триггеров. Нельзя использовать их в представлениях. Рекомендуется не использовать SQL выражения, которые возвращают наборы данных.

8.2. Использование Visual FoxPro для разработки клиентского приложения

    Итак, в предыдущем параграфе мы освоили необходимый минимальный набор команд, которого достаточно, чтобы начать работать с данными на SQL Server. Настала пора обсудить вопросы построения клиентской части, при этом вспомнив набор из шести операций, которые нам доступны из приложения front-end. В итоге получается, что из клиентской части нам доступны все операции. На всякий случай не забудьте про возможность недостаточного совершенства имеющегося драйвера ODBC.
    В этом параграфе мы обсудим вопросы создания приложения типа клиент-сервер с помощью Visual FoxPro.
    Visual FoxPro предоставляет следующие средства для работы с данными, имеющими другой формат:

  1. Конструктор соединения (Connection Designer). Альтернативно можно использовать команду CREATE CONNECTION. В этом случае для многих установок надо использовать функцию DBSETPROP().
  2. Конструктор представления (View Designer). Можно использовать команду CREATE SQL VIEW. В этом случае свойства полученного представления необходимо изменять с помощью функции DBSETPROP().
  3. Набор функций SQL pass-through позволяет контролировать работу сервера с помощью диалекта SQL самого сервера. Главным образом используется для получения выборки данных для дальнейшей обработки в клиентской части приложения. В то же время вам становятся доступны практически все команды и возможности сервера.

    Конструктор соединения используется для создания и модификации соединений, хранимых в базе данных. Активизируется Конструктор соединения, так же как и другие дизайнеры в Visual FoxPro, тремя способами:

  1. В меню File выполнить команду New, затем выбрать Connection.
  2. Из командного окна с помощью команды CREATE CONNECTION.
  3. Из Project Manager, выбрав Connection, New.

    Соединения хранятся только в базах данных, поэтому необходимо, чтобы во время создания соединения была открыта база данных, лучше та, в которой это соединение будет использоваться.
    После запуска вам необходимо последовательно заполнить текстовые поля, которые называются Data Source, UserID и Password. Здесь есть один момент, который необходимо отметить. Data Source уже должен быть создан посредством Администратора ODBC. Как уже указывалось, обычно после установки Visual FoxPro, Администратор ODBC уже присутствует на вашем компьютере. Запустите Администратор ODBC и создайте Data Source с использованием ODBC драйвера, в нашем случае это SQL Server (рис. 8.2). С помощью кнопки Drivers вы можете проверить наличие имеющихся на компьютере драйверов ODBC. Для создания Data Source необходимо нажать на кнопку Add и в появившемся диалоговом окне заполнить необходимые поля. Имена DataSource старайтесь давать осмысленные, чтобы в дальнейшем помнить, какой Data Source за что отвечает. Далее заполните имя сервера и в диалоге Options укажите имя базы данных, к которой вы будете подсоединяться. В примерах мы будем подсоединяться к базе данных Pubs, которая поставляется как образец с MS SQL Server. В зависимости от используемого драйвера диалоговые окна, открывающиеся после нажатия кнопки Add и выбора нужного драйвера, несколько различаются, но, как правило, главное, что нам нужно - это имя базы данных, доступ к таблицам которой мы хотим иметь, путь или имя папки, в которой хранятся таблицы, если приложение не работает с базой данных как с контейнером таблиц.


Рис. 8.2. Выбор необходимого ODBC драйвера

    Создав Data Source, мы имеем все для подготовки соединения и записи его определения в текущую базу данных. Выберите из комбинированного списка с заголовком "Data Source" ваш Data Source (извините за тавтологию), затем введите ваше пользовательское имя в текстовое поле UserID и пароль в текстовое поле PassWord. Сверху от трех полей, которые мы заполнили, находятся две кнопки выбора, правая называется "Connection String". Если вам больше нравится вводить Connection String, то выберите эту кнопку выбора, - в таком случае вы берете на себя не очень обременительную, но все-таки обязанность написать строку соединения самостоятельно, не делегировав эти права Visual FoxPro. Вам нужно набрать что-либо подобное (рис. 8.3):


Рис. 8.3.
DSN=IGOTOSQL;UserID=IAMGENIUS;_
Password=JesusChristSuperStar;Database=Pubs

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

  1. Окно будет появляться, если не будет указано пользовательское имя в строке соединения.
  2. Окно будет появляться всегда.
  3. Окно не будет появляться никогда.

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

CREATE CONNECTION mysqlconnect ;
DATASOURCE "igotosql" ;
USERID "Iamgenius" PASSWORD "JesusChristSuperStar"

    Как уже было упомянуто, SQL pass-thtough - это средство по созданию SQL команд и передачи их в базу данных SQL. Первой мы рассмотрим функцию SQLCONNECT(), которая устанавливает соединение с источником данных. С помощью этой функции можно создать стандартное соединение (Standard Connection), строку соединения (Connect string) и поименованное соединение (Name Connection).
    Ниже приводятся примеры для создания каждого типа соединения.

Стандартное соединение
hndl=SQLCONNECT('igotosql','iamgenius')
Строка соединения
hndl=SQLCONNECT('igotosql','iamgenius', 'JesusChristSuperStar','pubs')
Поименованное соединение
hndl=SQLCONNECT('mysqlconnect')

    После создания соединения мы можем установить его свойства. Это возможно практически для всех свойств, кроме двух, связанных с внутренними указателями ODBC: ODBChdbc и ODBChstmt. Свойства можно устанавливать с помощью Конструктора соединения или с помощью функции SQLSETPROP(). Для проверки текущих установок свойств используется функция SQLGETPROP().
    Функция SQLSETPROP() имеет следующий синтаксис:

SQLSETPROP(nConnectionHandle, cSetting, eExpression)

    Функция SQLSETPROP() устанавливает свойства активного соединения. Надо использовать эту функцию для установки свойств на уровне соединения. Чтобы установить эти свойства на уровне окружения Visual FoxPro и сделать их значениями по умолчанию, нужно использовать вместо указателя соединения (аргумент nConnectionHandle) значение 0.
    Функция SQLSETPROP() возвращает 1, если установка свойства завершилась успешно, -1, если случилась ошибка на уровне соединения, или -2, если ошибка случилась на уровне окружения. Свойство ConnectTimeOut может быть установлено только на уровне Visual FoxPro. Все остальные свойства можно устанавливать как на уровне Visual FoxPro, так и на уровне соединения. Установки, сделанные на уровне Visual FoxPro, являются значениями по умолчанию для всех последующих вновь создаваемых соединений.
    Функция SQLGETPROP() служит для чтения свойств текущего соединения. Если аргументом вместо указателя на соединение будет значение 0, то мы получим текущие установки по умолчанию для текущего окружения Visual FoxPro.

Синхронный и асинхронный процессы

    Когда вы делаете запрос к внешнему источнику данных, Visual FoxPro выполняет запрос синхронно. (Синхронный процесс - это процесс по умолчанию для Visual FoxPro.) Visual FoxPro не возвращает контроль приложению, пока SQL выражение не выполнится полностью. Программа останавливает свою работу и ждет, пока не обработается весь запрос и данные не вернутся к клиенту. Тем не менее при использовании SQL pass-through вы можете сделать процесс асинхронным, то есть программа может выполнять свои следующие команды, а выборка будет идти в фоновом режиме, заполняя курсор набором данных. Например:

myhandle=SQLCONNECT("mysqlconnect")
lA=SQLGETPROP("myhandle", "Asynchronous")
IF !lA=SQLSETPROP("myhandle","Asynchronous",.T.)
ENDIF

    Свойство BatchMode определяет, как будут выбираться множественные наборы данных, когда используется функция SQLEXEC(). Значение по умолчанию для свойства BatchMode - истина. При этом не возвращается никаких результатов от функции SQLEXEC(), пока функция не завершит свое выполнение.
    Например, если функция SQLEXEC() содержит два выражения SQL SELECT, никаких результатов не будет возвращено, если оба процесса запущены в режиме BatchMode, установленным в истину.
    Если режим не пакетный, то результаты возвращаются в паре с функцией SQLMORERESULTS(). Например:

myhandle=SQLCONNECT("mysqlconnect")
=SQLSETPROP(myhandle,"MatchMode",.T.)
=SQLEXEC(myhandle,"SELECT * FROM account",;
"SELECT * FROM Country","Results")
mores=0
DO WHILE mores << 2
    mores=SQLMORERESULTS(myhandle)
ENDDO

    Сочетание значений двух свойств дает нам четыре комбинации возможных режимов:

CинхронныйАсинхронный
Synchronous BatchAsynchronous Batch
Synchronous NonBatchAsynchronous Nonbatch

    Синхронный пакетный режим (Synchronous Batch Mode) - выражения, отправленные на сервер, не будут возвращать контроль программе, пока все наборы данных не будут выбраны.
    Асинхронный пакетный режим (Asynchronous Batch Mode) - выполнение в данном режиме будет возвращать 0 при каждом обращении функции, породившей процесс, пока не будут возвращены все наборы результатов.
    Синхронный не пакетный режим (Synchronous Nonbatch Mode) - выражения, выполняемые в этом режиме, выберут первый набор и вернут 1. Далее должна вызываться функция SQLMORERESULTS(), повторяясь, пока не будут выбраны все наборы данных. Если необходимы разные названия для полученных курсоров, новое имя можно указать при обращении к функции SQLMORERESULTS(). Функция SQLMORERESULTS() вернет 2, когда все наборы данных будут выбраны.
    Асинхронный не пакетный режим (Asynchronous NonBatch Mode) - для того чтобы вернуть все наборы результатов, необходимо после того, как функция, вызвавшая процесс, вернет 1, вызывать функцию SQLMORERESULTS(), пока она не вернет 2.
    Основные свойства соединений перечислены в табл. 8.4.

Таблица 8.4 Основные свойства соединений
СвойствоОписание
Connect-TimeOutУказывает время ожидания (в секундах), после которого должно возвратиться сообщение об ошибке соединения. Если указать 0, то время ожидания неопределенно и сообщение об ошибке не будет получено никогда. Это свойство может принимать значение от 0 до 600. Значение по умолчанию - 15 секунд. Доступно для чтения и записи.
Idle-TimeoutУказывает промежуток времени, после которого активное соединение деактивизируется. При этом соединение не должно использоваться в течение этого промежутка времени. Значение по умолчанию - 0 (интервал бесконечен). Доступно для чтения и записи.
Query-TimeOutУказывает время ожидания перед возвратом сообщения об ошибке, в случае неудачного выполнения запроса соединения. Если указать значение 0, то сообщение об ошибке никогда не будет возвращено. Может принимать значение от 0 до 600. Доступно для чтения-записи.
TransactionsСодержит числовое значение, которое определяет, как соединение управляет транзакциями внешней таблицы. Установка может принимать следующие значения:
1 или DB_TRANSAUTO (из VISUAL FOXPRO.H) - является значением по умолчанию; транзакции для внешней таблицы обрабатываются автоматически;
2 или DB_TRANSMANUAL (из VISUAL FOXPRO.H) - транзакции обрабатываются вручную через функции SQLCOMMIT() и SQLROLLBACK().
WaitTimeВремя в миллисекундах, задающее интервал, с которым Visual FoxPro проверяет, закончилось ли выполнение операторов SQL. Значение по умолчанию - 100 миллисекунд. Доступно для чтения и записи.

    Функция SQLSTRINGCONNECT() позволяет устанавливать соединение с помощью строки соединения. Таким образом, вы можете обойтись без хранения определения соединения в базе данных. Параметры строки могут отличаться в зависимости от используемого драйвера. Описание параметров приводится в документации для используемого драйвера. Например:

h=SQLSTRINGCONNECT('dsn=MyAutostoreSQL;uid=sa; pwd=Immanager')
=SQLEXEC(h,"SELECT * FROM Account","FoxAccount")

    Если вы создадите соединение, которое будет, к примеру, иметь название C1 и строку соединения, которую мы использовали в первой строчке последнего примера, то можно будет использовать следующую процедуру, итогом которой будет совершенно аналогичный результат.

CREATE CONNECTION c1;
CONNSTRING 'dsn=MyAutostoreSQL;uid=sa;pwd=Immanager'
h=SQLCONNECT('c1')
=SQLEXEC(h,"SELECT * FROM Account","FoxAccount")

    Каждую операцию по соединению с источником данных необходимо завершать отсоединением. В противном случае на каком-то этапе количество активных соединений превысит возможности вашего приложения. Для этого используется функция SQLDISCONNECT(). Например:

CREATE CONNECTION c1;
CONNSTRING 'dsn=MyAutostoreSQL;uid=sa;pwd=Immanager'
h=SQLCONNECT('c1')
=SQLEXEC(h,"SELECT * FROM Account","FoxAccount")
=SQLDISCONNECT(h)

    При этом следует отметить следующий факт. Получив курсор с помощью функции SQLEXEC() и выполнив операцию SQLDISCONNECT(), вы не заметите никаких изменений при работе с курсором. Здесь стоит глубже вникнуть в суть процесса. При выполнении функции SQLEXEC() мы получаем курсор в памяти, который по большому счету никак не связан с данными на сервере. Здесь мы вплотную соприкасаемся с понятием модифицируемый курсор. Сейчас необходимо вспомнить функцию CURSORSETPROP(), о которой мы много говорили в предыдущей главе.
    Получив курсор, для установки необходимых вам свойств используйте функцию CURSORSETPROP(), чтобы изменения, которые вы проводите над данными в курсоре, отображались на сервере.
    Если в следующей процедуре отключить связь до закрытия курсора, удалив строчку с единственной командой USE, все равно нельзя будет в дальнейшем проводить обновления в этом наборе данных.

CREATE CONNECTION c1;
CONNSTRING 'dsn=MyAutostoreSQL;uid=sa;pwd=Iammanager'
h=SQLCONNECT('c1')
h=SQLCONNECT("cn1")
=SQLEXEC(h,"SELECT * FROM Account","FoxAccount")
=CURSORSETPROP('tables','Account')
=CURSORSETPROP('KeyFieldList','field1')
=CURSORSETPROP('updatablefieldlist','field2')
=CURSORSETPROP('updatenamelist',;
'field1 mytable.field1,field2 mytable.field2')
=CURSORSETPROP('sendupdates',.T.)
BROWSE
USE
=SQLDISCONNECT(h)

    Курсор, полученный с помощью функции SQLEXEC(), имеет оптимистическую буферизацию записи. С помощью функции CURSORGETPROP() вы можете установить нужный вам тип буферизации. Помимо этого следует обратить внимание на тип обработки транзакций. Если свойство Transactions равно 1, то у вас нет практически никакого контроля над транзакциями, так как они являются автоматическими. Если установить это свойство равным 2, то вы сможете управлять процессом транзакций. Например, при изменении содержимого нескольких записей, в зависимости от сложившихся обстоятельств, вы сможете либо отменить все изменения, либо записать их на диск.
    Для этого используются функции SQLCOMMIT() и SQLROLLBACK(). Параметром обеих функций является указатель вашего соединения. При этом учтите, что если вы не используете эти функции и просто закрываете курсор, то изменения автоматически запишутся на диск, естественно, если они не вступают в конфликт с какими-либо условиями или обстоятельствами, обычно появляющимися при работе в многопользовательском режиме.
    В следующем примере в зависимости от дня недели изменения либо записываются на диск, либо происходит откат. При этом предварительно устанавливается требуемое значение свойства Transactions для активного соединения.

CREATE CONNECTION cn1 CONNSTRING 'dsn=myteach'
h=SQLCONNECT("cn1")
=SQLEXEC(h,"SELECT * FROM Account","FoxAccount")
=SQLSETPROP(h,"Transactions",2)
=CURSORSETPROP('tables','Account')
=CURSORSETPROP('KeyFieldList','account,key_customer')
=CURSORSETPROP('updatablefieldlist','summa')
=CURSORSETPROP('updatenamelist',;
'account account.account,key_customer account ; key_customer,;
summa account.summa')
=CURSORSETPROP('sendupdates',.T.)
REPLACE ALL field2 WITH 27
IF DAY(DATE())=2
     SQLROLLBACK(h)
ELSE
     SQLCOMMIT(h)
ENDIF
USE
=SQLDISCONNECT(h)

Создание внешних представлений

    В главе 7 обсуждались представления и работа с ними. Далее мы продолжим разговор о них, но будем работать с данными либо внешнего формата, либо с данными смешанного характера, то есть исходные таблицы могут быть как формата Visual FoxPro, так и любого внешнего формата. Представьте, например, ситуацию такого рода. Ваша организация работала с данными с помощью приложений, написанных с использованием FoxPro 2.6 или более ранних версий. Но вот принято решение о переходе на версию Visual FoxPro 3.0 или выше. Можно поступить следующим образом. Ждать, пока программисты перепишут все приложения с использованием новой версии, потом, когда новая версия будет готова, отладить ее, подготовить соответствующее аппаратное обеспечение и начать работать. А можно поступить по-другому. Перейти частично на новую версию, а файлы данных хранить в формате FoxPro 2.6, там где их еще используют старые приложения, постепенно переводя их в новый формат по мере обновления техники и перевода отдельных частей приложений на новую версию.
    Рассмотрим синтаксис команды CREATE SQL VIEW для создания внешнего представления.

CREATE SQL VIEW [ViewName ] [REMOTE]
[CONNECTION ConnectionName [SHARE]
| CONNECTION DataSourceName]
[AS SQLSELECTStatement]

    Для создания внешнего представления надо указывать ключевое слово REMOTE. Далее вы можете с помощью ключевого слова CONNECTION создать представление либо с помощью соединения, либо с помощью источника данных. Таким образом, во втором случае вы минуете соединение. Хорошо это или плохо? Это надо решать для конкретной задачи индивидуально. В случае использования соединения вы получаете более полный контроль над процессом получения данных, так как у вас, исходя из вышеизложенного, есть функции, с помощью которых вы легко можете управлять свойствами соединения. Представление помимо этого может иметь соединение, разделенное между несколькими представлениями. Таким образом, меняя свойства одного соединения, вы получаете контроль над несколькими наборами внешних данных.
    Перед тем как вы начнете создавать внешнее представление, лучше уже иметь готовое определение соединения в текущей базе данных или подходящий набор источников данных, полученных с помощью ODBC, хотя это и не является необходимым требованием. Поэтому выберите один из способов построения внешних запросов. Если вы работаете с использованием Project Manager, то выберите нужную базу данных и, установив курсор на пункте Remote Views, как это показано на рис. 8.4, нажмите кнопку New.


Рис. 8.4.

    После этого появится диалоговое окно, в котором вам будет предложен выбор среди имеющихся в текущей базе данных соединений или среди источников данных, установленных на вашем компьютере. При этом вы можете создать новое соединение и затем снова вернуться в текущее диалоговое окно. С источниками данных дело обстоит хуже, но в конце концов мы работаем в многозадачной среде. Загрузите ODBC, создайте нужный источник данных, естественно при наличии на компьютере необходимого драйвера, и снова переключитесь в Visual FoxPro.
    В зависимости от того, к данным какого формата вы хотите обратиться, будут доступны либо таблицы из конкретного каталога, либо из конкретной базы данных. В случае с SQL Server и Microsoft Access это будут таблицы из баз данных, которые вы укажете в источнике данных.
    После того как вы выберете необходимое вам соединение или источник данных и нажмете кнопку OK, станет доступен Конструктор представлений вместе с диалогом по выбору таблиц из внешней базы данных, к которой вы присоединились. В остальном различий с Конструктором локальных представлений нет.
    Если необходимо смешивать локальные данные с внешними, то надо построить по крайней мере два представления. Первое - это представление, которое будет состоять из данных на сервере, а второе будет состоять из данных из локальной таблицы или таблиц и полученного на первом этапе представления. Для того что ваши изменения после модификации данных в полученном представлении второго уровня отображались на сервере, необходимо использовать функцию TABLEUPDATE() дважды - вначале в представлении второго уровня, затем в представлении первого уровня. Другой способ - это последовательно закрыть представление второго уровня, а затем первого.
    Выше были описаны два способа работы с внешними данными, SQL pass-through и внешние представления. SQL pass-through требует большего объема программирования, но в то же время, используя эту технологию, вы практически не ограничены в своих возможностях по управлению внешними данными. Внешние представления легче использовать, значительно меньше объем программирования, но с их помощью можно только модифицировать данные: редактировать, удалять и добавлять записи.
    Любой набор данных или курсор, который вы получите, можно просматривать с помощью окна Browse или объекта Grid. Говоря другими словами, вся эта технология тесно интегрирована. Объекты Grid или Browse сами по себе можно считать средствами управлениями данными, по гибкости не имеющими аналогов среди других популярных систем. Можете проверить, что скорость доступа к данным уменьшается незначительно, даже если эти объекты активны. Все замедление отнесите на счет вашего видеоадаптера, а объекты достаточно быстро отображают искомую информацию.
    Но все-таки у вас могут возникнуть потребности выбирать данные еще быстрее, по крайней мере будет казаться, что вы можете этого добиться, если залезете в глубь процесса технологии ODBC или какой-либо другой, в случае если ее функции будут доступны с помощью какого-нибудь API. Тогда вспомните, что Visual FoxPro поддерживает вызовы DLL, что, впрочем, делали и его недавние предшественники, правда, посредством немного более сложных манипуляций.
    Поэтому в следующем разделе при обсуждении вопросов взаимодействия Access и Visual Basic с внешними данными все, что касается доступа к данным через ODBC API, RDO (объекты доступа к внешним данным), SQL-DMO, в равной мере относится и к Visual FoxPro.

8.3. Использование Access и Visual Basic для разработки клиентского приложения

    Access и Visual Basic предоставляют разнообразные средства для работы с внешними данными.
    В этом параграфе мы кратко расскажем об этих средствах, снабдив изложение небольшими примерами, которые вы можете развить до более высокой степени функциональности.
    Технологии, доступные как в обоих продуктах, так и в каждом по отдельности, будут описываться в зависимости от степени сложности, а не отдельно для каждого пакета. Это делается специально, так как переход от одной среды разработки к другой достаточно легок в связи с тем, что в основе этих продуктов лежит один язык.
    При работе в Microsoft Access самый простой способ обработки информации с SQL Server - использование присоединенных таблиц. Для этого используйте команду Внешние данные из меню Файл. Из двух опций Импорт и Связь с таблицами нас интересует вторая, так как после импорта способы передачи обновленной информации на сервер не так легки, как во втором случае. После того как вы выберете команду Связь с таблицами, вы попадете в диалоговое окно Связь. Главный элемент на первом этапе для нас - это список Тип файла. В этом списке перечисляются все типы файлов, с которыми работает процессор баз данных Microsoft Access, достаточно впечатляющий по количеству поддерживаемых СУБД различных версий. Но нас интересует элемент, который расширяет этот список еще значительнее. Как вы уже догадались, этот элемент называется базы данных ODBC (рис. 8.5).


Рис. 8.5. Диалоговое окно Связь в Access 7.0

    После выбора этого элемента перед вами возникнет следующее диалоговое окно, в котором вы увидите список всех источников данных (Data Source), доступных на компьютере. Если нужного источника данных в списке нет, то вы с помощью кнопки New легко можете перейти в Администратор ODBC и создать требуемый источник (рис. 8.6).


Рис. 8.6.

    После выбора источника данных в следующем диалоговом окне вам останется только выбрать из списка доступных таблиц те, которые вы хотите присоединить. При этом обратите внимание, что вы можете выбрать несколько или даже все имеющиеся таблицы. На этом процесс присоединения закончен. В случае отсутствия в присоединяемой таблице уникального индекса вам будет предложено выбрать это поле самостоятельно. Это делается для того, чтобы процессор баз данных Jet Access мог однозначно передавать изменения в данных на сервер. Поэтому тщательно следите, чтобы поле, определяющее уникальность записи, всегда присутствовало в ваших таблицах. Теперь вы можете работать с внешней таблицей так же, как и с таблицей самого Access. Вам будут недоступны только операции по модификации структур таблиц непосредственно визуальными средствами Access. Обратите внимание, что в предыдущем предложении сказано "непосредственно визуальными средствами". Потому что способы изменить структуру таблиц на сервере у вас еще остаются.
    Среди дополнений (Add-In) Visual Basic присутствует Data Manager. Если на компьютере установлен Visual Basic, то в качестве упражнения предлагаем проделать аналогичную манипуляцию с присоединением внешних таблиц к базе данных с помощью Data Manager. В случае затруднений обратите внимание на рис. 8.7-8.9.


Рис. 8.7.

Рис. 8.8.

Рис. 8.9.

    Чем хороши присоединенные таблицы? Во-первых, не требуется никакого программирования для того, чтобы работать с ними. Во-вторых, они модифицируемы, то есть все изменения, которые вы будете проводить с записями, будут отражаться на сервере. Исключением является случай, когда Access однозначно не сможет определить запись на сервере, в которой надо провести обновление данных в колонках.
    Чем плохи присоединенные таблицы? Только тем, что в некоторых случаях вам будет недоставать скорости обработки. Какой есть выход из положения? Использовать другие методы для получения набора данных.
    Но сначала закончим с присоединенными таблицами. Помимо визуального способа, есть и программный. Для этого мы должны использовать DAO - набор объектов для работы с данными. Вы уже не раз встречались на страницах этой книги с конструкцией следующего типа:

Dim MyDb As Database
Dim mytableDef As TableDef
Set MyDb = DBEngine.Workspaces(0).Databases(0)
Set MytableDef =_ CreateTabledef("JustProgrammaticalyMadeTable")

    Данная конструкция создает таблицу в текущей базе данных. Отметим, что этот пример не завершает построения таблицы, так как мы не создали полей и не добавили таблицу в коллекцию таблиц текущей базы данных.
    Следующий пример создает присоединенную таблицу формата Visual Fox-Pro 3.0. Необходимо лишь добавить две строки, которые устанавливают свойства Connect и SourceTableName для вновь создаваемого объекта. В данном примере создается присоединенная таблица AttachTable. Для этого используется источник данных vfp34. На вашем компьютере название таблицы и источника данных (Data Source) могут быть иными.

Public Sub TableCREATE()
     Dim myDb As DATABASE, mytdef As TableDef
     Set myDb = DBEngine.Workspaces(0)("Autostore.mdb")
     Set mytdef = myDb.CreateTableDef("AttachTable")
     mytdef.Connect = "ODBC;dsn=vfp34"
     mytdef.SourceTableName = "country"
     myDb.TableDefs.Append mytdef
End Sub

    Данную процедуру можно запустить как в Access, так и в Visual Basic или Microsoft Excel. Единственное отличие последних в том, что необходимо проследить за доступностью объектов DAO приложению. После выполнения этого или подобного кода в базе данных появится значок, а если вы откроете присоединенную таблицу в режиме Конструктора, то, воспользовавшись командой Свойства меню Вид, сможете прочитать в строке Описание строку Соединения.
    Не забывайте, что физически присоединенные таблицы не находятся в вашей базе данных, и требуется определенное время для связи с ней, поэтому при работе с таблицами, хранящими большое количество данных, используйте другие, более быстрые методы выборки данных, к примеру, параметрические запросы.
    Обратим внимание, что с присоединенными таблицами нельзя использовать метод Seek. Поэтому здесь нужно придумать другие способы оптимизации поиска, самым лучшим из которых является, извините за назойливость, параметрический запрос.
    Перед тем как использовать транзакции, необходимо выяснить, поддерживаются ли операции такого рода в источнике данных. Для баз данных ODBC нельзя использовать вложенные транзакции.
    До сих пор мы говорили о присоединенной таблице, но можно и напрямую открыть таблицу. Это доступно только с помощью языка программирования Basic.

Dim CurrentDatabase As Database
Dim MySet As Recordset
'Открываем внешнюю базу данных формата FoxPro
Set CurrentDatabase = DBEngine.Workspaces(0).OpenDatabase_
("C:\FOXPRO\DATA\", False, False, "FoxPro 2.5")
'Открываем таблицу Customer
Set MySet = CurrentDatabase.OpenRecordset("Customer")

    В приведенном примере в начале делается текущей база данных внешнего формата (в нашем случае, так как FoxPro 2.х не поддерживает понятие базы данных как контейнера таблиц, это каталог C:\FOXPRO\DATA\), а затем напрямую открывается таблица покупателей в этом каталоге. Понятно, что никакого значка для данной таблицы в текущей базе данных не появится и работать с этими данными мы сможем только программным способом. Еще одним недостатком данного метода доступа к внешним данным является более медленная, по сравнению с присоединенной таблицей, скорость работы.
    Помимо присоединения уже существующих таблиц, хранящихся в источнике данных, вы можете создавать новые таблицы, которые станут присоединенными.
    Ниже приводится простейший пример создания таблицы формата FoxPro 2.6.

Dim CurrentDatabase As Database
Dim MyTableDef As TableDef
Set CurrentDatabase =_ DBEngine.Workspaces(0).OpenDatabase("C:\DATA", False,_ False, "FoxPro 2.6")
Set MyTableDef =_ CurrentDatabase.CreateTableDef("FromAccess")
MyTableDef.Fields.Append MyTableDef.CreateField("Field1", DB_TEXT, 15)
CurrentDatabase.Tabledefs.Append MyTableDef

    Следующим способом для работы с данными на сервере, который мы рассмотрим, будет SQL pass-through. Выборка данных при использования SQL pass-through проходит быстрее, так как запрос сразу отправляется на сервер, минуя процессор баз данных Jet. Но полученные запросы являются не модифицируемыми, то есть изменения в них не передаются на сервер. Следовательно, вам нужно использовать какие-то другие способы для обновления данных на сервере, например, использовать запросы модификации. Важным аргументом в пользу использования SQL pass-through запросов является, как уже было сказано выше, более высокая скорость выполнения, но, помимо этого, с помощью SQL pass-through вам доступны не только команды выборки, но и команды определения данных, возможность запускать хранимые процедуры. В итоге вы получаете, в зависимости от данных вам прав, довольно значительные возможности по управлению данными на сервере. По большому счету, визуально запросы pass-through не создаются, но какие-то элементы автоматизации присутствуют. Для того чтобы создать запрос с помощью Конструктора Запросов, перейдите на вкладку Запросы в контейнере базы данных. Затем в меню Запрос выберите команду Запрос SQL и опцию К серверу, которая в английской версии называется Pass-Through. После выбора этого пункта меню больше не будет доступен режим конструктора, то есть SQL команды придется набирать вручную. Не будет доступен даже Построитель. Единственное, что вы можете выбрать - это свойства вашего запроса, среди которых имя источника данных (Data Source). Чтобы получить доступ к диалогу свойств, выберите команду Свойства в меню Вид (рис. 8.10).


Рис. 8.10.

    Среди других свойств следует обратить внимание на Возврат записей. Если вы собираетесь использовать запросы определения данных, то установите значение этого свойства равным "Нет".
    В Visual Basic визуальное создание pass-through запросов не доступно при работе с Data Manager.
    Для того чтобы создавать запросы pass-through программно, используются объекты QueryDef.
    Далее будет разобран небольшой пример, созданный с помощью Visual Basic и использующий pass-through запросы для работы с внешними данными. Приложение состоит из одной формы, которая представлена на рис. 8.11.


Рис. 8.11.

    Данная форма при загрузке использует SQL pass-through запрос для создания набора данных, который мы можем просматривать и редактировать.

Private Sub Form_Load()
     Set Db = Workspaces(0).OpenDatabase("C:\SPORT\TOTEACH.MDB")
'Проверка наличия в базе данных
'запроса с именем "My QD1", и если запрос присутствует, то
'удаляем его.
     For I = 0 To Db.QueryDefs.Count - 1
If Db.QueryDefs(I).Name = "My QD1" Then
     Db.QueryDefs.Delete "My QD1"
     Exit For
End If
     Next I
' Мы создаем запрос или, на языке, DAO объект QueryDef и
' устанавливаем строку соединения.
     Set Qd1 = Db.CreateQueryDef("My QD1")
     Qd1.Connect = "odbc;dsn=vfp34;"
' Записываем SQL запрос и устанавливаем свойство
' ReturnsRecord (Возврат записей) равным Истине, так как
' наш запрос должен возвращать записи
     Qd1.SQL = "SELECT * FROM Account"
     Qd1.ReturnsRecords = True
' В этой части процедуры мы устанавливаем соответствие
' между полями набора данных, предварительно создав его,
' и объектами формы, а именно двумя текстовыми полями
     Set Rs = Db.OpenRecordset("My Qd1",_dbOpenSnapshot)
     Me.Text1 = Rs!account
     Me.Text2 = Rs!summa
     vtext1 = Rs!account
     vtext2 = Rs!summa
     Me.Text3 = Rs.RecordCount
End Sub

    Как видно из примера, основное отличие pass-through запросов от остальных запросов - это наличие непустого свойства Connect и установка свойства ReturnRecords.
    Если вы отредактируете какое-либо из полей формы, то сможете записать изменения на сервер с помощью pass-through запроса. Такой сложный путь необходим ввиду того, что выборки, получаемые в результате выполнения pass-through запросов, не модифицируемы.

Private Sub Command3_Click()
     For I = 0 To Db.QueryDefs.Count - 1
If Db.QueryDefs(I).Name = "My QD2" Then
     Db.QueryDefs.Delete "My QD2"
     Exit For
End If
Next I
Set Qd2 = Db.CreateQueryDef("My QD2")
Qd2.Connect = "odbc;dsn=vfp34;"
Qd2.SQL = "Update account set account=" &_Str(Me.Text1) & ",_summa = " & Str(Me.Text2) &
" where account ="_ & Str(vtext1) & " _and summa = " & Str(vtext2)
Qd2.ReturnsRecords = False
Qd2.Execute
Set Rs = Db.OpenRecordset("My Qd1",_dbOpenSnapshot)
Rs.FindNext "account=" & Str(Me.Text1) &_ " and summa=" & Str(Me.Text2)
Me.Text3 = Rs.RecordCount
End Sub

    Обратите внимание, что свойство ReturnsRecords устанавливается равным False и на обращение к методу Execute объекта QueryDef.
    Но основным предназначением использования pass-through запросов все же считается не выборка данных, а запросы определения данных и выполнение хранимых процедур на сервере.
    В следующем примере создается таблица на внешнем по отношению к Access источнике данных (Data Source)

Set Qd3= Db.CreateQueryDef("My QD3")
Qd3.Connect = "odbc;dsn=vfp34;"
Qd3.SQL = "CREATE TABLE madebyaccess(field1 c(20))
Qd3.ReturnsRecords = False
Qd3.Execute

8.4. Использование ODBC API для доступа к внешним данным

    ODBC API - прикладной интерфейс программиста для доступа к функциям ODBC. Структура ODBC была изложена в начале этой главы.
    В этом параграфе мы познакомимся с некоторыми функциями ODBC более подробно и рассмотрим примеры использования этих функций для доступа к данным на сервере.
    Кодирование с помощью ODBC значительно сложнее, но выигрыш в скорости может быть существенным. Здесь следует отметить, что наибольший выигрыш в производительности достигается при доступе к базам данным ODBC. Если же вы попытаетесь с помощью данного метода улучшить показатели доступа к данным, к которым Access обращается с помощью ISAM - последовательного индексного доступа к данным, то результат будет не столь хорош, как хотелось бы, а иногда и совсем плох.
    Для того чтобы воспользоваться функциями ODBC API, их необходимо подключить с помощью команды Declare.

' ODBC API объявления
Declare Function oSQLAllocEnv Lib "odbc32.dll" _
Alias "SQLAllocEnv" (phenv As Long) As Integer
Declare Function oSQLAllocConnect Lib "odbc32.dll" _
Alias "SQLAllocConnect" (ByVal henv As Long, phdbc As _ Long) As Integer
Declare Function oSQLConnect Lib "odbc32.dll" _
Alias "SQLConnect" (ByVal hdbc As Long, ByVal szDSN As _ String, _
ByVal cbDSN As Integer, ByVal szUID As String, _
ByVal cbUID As Integer, ByVal szAuthStr As String, _
ByVal cbAuthStr As Integer) As Integer
Declare Function oSQLAllocStmt Lib "odbc32.dll" _
Alias "SQLAllocStmt" (ByVal hdbc As Long, pHstmt As _ Long) As Integer

    Четыре функции, которые приведены выше, служат для установки указателя для окружения, соединения, утверждения и установления связи с приложением - сервером. Установка указателя с перечисленными объектами - необходимая процедура для дальнейших действий, которые вы планируете совершить, используя соединение, построенное с помощью ODBC API. Причем вызов этих функций должен происходить в том порядке, в каком они перечислены выше. Далее приводится пример использования этих функций для построения процедуры, которая соединит вас с источником данных. Причем вы не связаны рамками Access или Visual Basic, подойдет любой продукт, который поддерживает доступ к функциям, хранящимся в динамически подсоединяемых библиотеках DLL.
    Перед использованием этой функции необходимо определить структуру следующего типа (в языках, которые не могут создавать типов, придумайте что-нибудь другое, выход всегда есть):

Type ORecordSet
     lngHenv As Long ' Указатель окружения
     lngHdbc As Long ' Указатель соединения
     lngHstmt As Long ' Указатель утверждения
     lngHstmtUpdate As Long ' Указатель используемый для
' изменений и удалений
     intColumns As Integer ' Возвращаемые колонки
     lngRows As Long ' Возвращаемые записи
     lngRowCount As Long ' Число записей для операций
' со многими записями
     lngRowSetSize As Long ' Размер набора записей
' в курсоре
     lngMaxRows As Long ' Максимальное число
' возвращаемых записей
     lngMaxWidth As Long ' Максимальная ширина поля
     lngCursor As Long ' Тип курсора
     lngConcur As Long ' Тип совпадения
     EOF As Boolean ' Указывает конец файла
     BOF As Boolean ' Указывает начало файла
     lngFirstRow As Long ' Первая запись в курсоре
     lngCurrentRow As Long ' Текущая запись в курсоре
     lngLastRow As Long ' Последняя запись в курсоре
     lngLastSetRow As Long ' Последняя запись в наборе
' данных
     strCursorName As String ' Имя курсора
     intArrayPos As Integer ' Позиция этого типа
' в массиве
     intRowStatus() As Integer ' Расширенная информация
' о наборе записей
     strColLabels() As String ' Текст метки колонки
     lngColLabels() As Long ' Длина метки колонки
     lngDisplaySize() As Long ' Ширина вывода колонки
End Type

    Информация о наборе данных или создание переменной типа, который объявлен выше - oRecordSet:

Private mtypODBC As OrecordSet
    Помимо этого понадобятся следующие константы:
' Возможные значения, которые будет возвращать вызов ODBC
Public Const SQL_INVALID_HANDLE = -2
Public Const SQL_ERROR = -1
Public Const SQL_SUCCESS = 0
Public Const SQL_SUCCESS_WITH_INFO = 1
Public Const SQL_STILL_EXECUTING = 2
Public Const SQL_NEED_DATA = 99
Public Const SQL_NO_DATA_FOUND = 100

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

Function rODBCConnect(strServer As String, strUID As String,
     _strPassword As String) As Integer
     Dim intRet As Integer
     Dim strConnOut As String * 256
     Dim intConnOut As Integer
     Dim typOBad As ORecordSet
     Dim i As Integer
     On Error GoTo rODBCConnectErr
     rODBCConnect = True
     ' Установка указателя окружения
     intRet = oSQLAllocEnv(mtypODBC.lngHenv)
     If intRet <<>> SQL_SUCCESS Then
Call rODBCErrorInfo(mtypODBC.lngHenv, SQL_NULL_HDBC, _
SQL_NULL_HSTMT)
     End If
     If intRet << SQL_SUCCESS Then
rODBCConnect = False
GoTo rODBCConnectExit
     End If
     ' Установка указателя соединения
     intRet = oSQLAllocConnect(ByVal mtypODBC.lngHenv, _
mtypODBC.lngHdbc)
     If intRet <<>> SQL_SUCCESS Then
Call rODBCErrorInfo(mtypODBC.lngHenv, _
mtypODBC.lngHdbc, SQL_NULL_HSTMT)
     End If
     If intRet << SQL_SUCCESS Then
rODBCConnect = False
GoTo rODBCConnectExit
     End If
' Соединения с указанным драйвером
     intRet = oSQLConnect(ByVal mtypODBC.lngHdbc, strServer, _
Len(strServer), strUID, Len(strUID), strPassword, _
Len(strPassword))
     If intRet <<>> SQL_SUCCESS Then
Call rODBCErrorInfo(mtypODBC.lngHenv, _
     mtypODBC.lngHdbc, SQL_NULL_HSTMT)
     End If
     If intRet << SQL_SUCCESS Then
rODBCConnect = False
GoTo rODBCConnectExit
     End If
     rODBCConnectExit:
     Exit Function
rODBCConnectErr:
     MsgBox Err.Number & ": " & Err.Description, _
vbCritical, "rODBCConnect()"
typOBad.intArrayPos = -1
rODBCConnect = False
     Resume rODBCConnectExit
End Function

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

intRet = oSQLAllocStmt(ByVal mtypODBC.lngHdbc,_
mtypODBC.lngHstmt)

    К счастью, эту функцию надо выполнять только один раз, так как возвращаемый указатель может использоваться любым количеством соединений. Однако с некоторыми серверами, такими как Microsoft SQL Server, вы должны завершить запрос с указателем утверждением, перед тем как начать выполнение другого запроса. В силу этого, вы не можете разместить указатель другого утверждения без открытия другого соединения к серверу или же вам необходимо подождать, пока текущее выражение не завершит работу. В зависимости от драйвера вы можете иметь возможность выполнять несколько выражений на одном соединении. Используйте функцию SQLGetInfo() для того, чтобы определить, способен ли ваш драйвер выполнять несколько утверждений на одном соединении.
    Таким образом, мы подошли к процессу выполнения SQL выражения. Для этого мы используем объявление следующей функции:

Declare Function oSQLExecDirect Lib "odbc32.dll" _
Alias "SQLExecDirect" (ByVal hstmt As Long, _
ByVal szSqlStr As String, ByVal cbSqlStr As Integer) As_ Integer

    Теперь мы объявили все функции для открытия доступа к набору данных, хранящихся на сервере. Эта функция может выглядеть следующим образом:

Function rODBCOpenRecordset( _strSQL As String, lngType As Long) As Boolean
' Открывает набор данных на источнике данных ODBC
' Вначале необходимо вызвать ODBCConnect для установки
' соединения
' Возвращает true при успешном завершении, false при
' любой ошибке
     Dim intRet As Integer
     Dim i As Integer
     On Error GoTo rODBCOpenRecordsetErr
     rODBCOpenRecordset = True
     ' Проверка наличия соединения
     If mtypODBC.lngHdbc = SQL_NULL_HDBC Then
MsgBox "Нет открытых дескрипторов. Нет открытых соединений для открытия наборов данных. " & _
"Вызовите вначале rODBCOpenRecordset", vbCritical, _
"rODBCOpenRecordset()"
rODBCOpenRecordset = False
GoTo rODBCOpenRecordsetExit
     End If
     ' Размещает SQL выражение для использования
     ' в других функциях
     intRet = oSQLAllocStmt(ByVal mtypODBC.lngHdbc, _
mtypODBC.lngHstmt)
     If intRet <<>> SQL_SUCCESS Then
Call rODBCErrorInfo(mtypODBC.lngHenv, _
     mtypODBC.lngHdbc, mtypODBC.lngHstmt)
     End If
     If intRet << SQL_SUCCESS Then
rODBCOpenRecordset = False
GoTo rODBCOpenRecordsetExit
     End If
     ' Устанавливает некоторые значения в глобальной
     ' структуре
     mtypODBC.BOF = False
     mtypODBC.EOF = False
     mtypODBC.lngFirstRow = 0
     mtypODBC.lngCurrentRow = 0
     mtypODBC.lngLastRow = 0
     mtypODBC.lngCursor = SQL_CURSOR_DYNAMIC
     If lngType = dbOpenDynaset Then
mtypODBC.lngConcur = SQL_CONCUR_VALUES
     ElseIf lngType = dbOpenSnapshot Then
mtypODBC.lngConcur = SQL_CONCUR_READ_ONLY
     Else
"rODBCOpenRecordset()"
rODBCOpenRecordset = False
GoTo rODBCOpenRecordsetExit
     End If
     mtypODBC.lngRowSetSize = odbcRowsetSize
     'Устанавливает максимальное количество записей
     mtypODBC.lngMaxRows = odbcMaxRows
     'Устанавливает максимальную ширину поля в этом
     ' выражении
     mtypODBC.lngMaxWidth = odbcMaxWidth
intRet = oSQLSetStmtOption(ByVal_mtypODBC.lngHstmt, _
SQL_ROWSET_SIZE, mtypODBC.lngRowSetSize)
     If intRet <<>> SQL_SUCCESS Then
Debug.Print "Сообщение: не можем установить размер набора"
Call rODBCErrorInfo(mtypODBC.lngHenv, _
     mtypODBC.lngHdbc, mtypODBC.lngHstmt)
     End If
     ' Устанавливает тип курсора для этого выражения
     intRet = oSQLSetStmtOption(ByVal mtypODBC.lngHstmt, _
SQL_CURSOR_TYPE, mtypODBC.lngCursor)
     If intRet <<>> SQL_SUCCESS Then
Debug.Print "Сообщение: не можем установить тип курсора"
Call rODBCErrorInfo(mtypODBC.lngHenv, _
     mtypODBC.lngHdbc, mtypODBC.lngHstmt)
     End If
     ' Устанавливает тип согласования для этого выражения
     intRet = oSQLSetStmtOption(ByVal mtypODBC.lngHstmt, _
SQL_CONCURRENCY, mtypODBC.lngConcur)
     If intRet <<>> SQL_SUCCESS Then
     Call rODBCErrorInfo(mtypODBC.lngHenv, _
     mtypODBC.lngHdbc, mtypODBC.lngHstmt)
     End If
     intRet = oSQLSetStmtOption(ByVal mtypODBC.lngHstmt, _
SQL_MAX_ROWS, mtypODBC.lngMaxRows)
     If intRet <<>> SQL_SUCCESS Then
     Call rODBCErrorInfo(mtypODBC.lngHenv, _
     mtypODBC.lngHdbc, mtypODBC.lngHstmt)
     End If
     'Присваивает имя курсора выражения
     intRet = oSQLSetCursorName(mtypODBC.lngHstmt, _
"C1", 2)
     If intRet <<>> SQL_SUCCESS Then
Call rODBCErrorInfo(mtypODBC.lngHenv, _
mtypODBC.lngHdbc, mtypODBC.lngHstmt)
     End If
     mtypODBC.strCursorName = rODBCGetCursorName()
     ' Выполнение SQL выражения
     intRet = oSQLExecDirect(ByVal mtypODBC.lngHstmt, _
strSQL, Len(strSQL))
     If intRet <<>> SQL_SUCCESS Then
Call rODBCErrorInfo(mtypODBC.lngHenv, _
mtypODBC.lngHdbc, mtypODBC.lngHstmt)
     End If
     If intRet <<>> SQL_SUCCESS And intRet <<>> SQL_SUCCESS_WITH_INFO Then
rODBCOpenRecordset = False
GoTo rODBCOpenRecordsetExit
     End If
     'Выводит информацию о колонке
     intRet = rODBCGetColumnInfo()
     If Not intRet Then
rODBCOpenRecordset = False
GoTo rODBCOpenRecordsetExit
     End If
rODBCOpenRecordsetExit:
     Exit Function
rODBCOpenRecordsetErr:
     MsgBox Err.Number & ": " & Err.Description, _
vbCritical, "rODBCOpenRecordset()"
     rODBCOpenRecordset = False
     Resume rODBCOpenRecordsetExit
End Function

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

Declare Function oSQLFreeStmt Lib "odbc32.dll" _
Alias "SQLFreeStmt" (ByVal hstmt As Long, ByVal fOption_As Integer) As Integer
rc = oSQLFreeStmt (mtypODBC.lngHstmt, SQL_CLOSE)

    Следующие функции служат для освобождения указателя соединения и окружения:

Declare Function oSQLFreeConnect Lib "odbc32.dll" _
Alias "SQLFreeConnect" (ByVal hdbc As Long) As Integer
Declare Function oSQLFreeEnv Lib "odbc32.dll" _
Alias "SQLFreeEnv" (ByVal henv As Long) As Integer

    Как правило, любой сеанс работы с использованием ODBC API должен заканчиваться вызовом этих функций. Вызывая их из Access или Visual Basic, вы можете оформить это примерно таким образом, как показано в следующем примере ниже описании функции:

Function rODBCDisconnect()
     ' Отсоединение от источника данных (Data Source)
     Dim intRet As Integer
     On Error GoTo rODBCDisconnectErr
     intRet = oSQLDisconnect(ByVal mtypODBC.lngHdbc)
     If intRet <<>> SQL_SUCCESS Then
Call rODBCErrorInfo(mtypODBC.lngHenv,_
mtypODBC.lngHdbc, SQL_NULL_HSTMT)
     End If
     intRet = oSQLFreeConnect(ByVal mtypODBC.lngHdbc)
     If intRet <<>> SQL_SUCCESS Then
Call rODBCErrorInfo(mtypODBC.lngHenv, _
mtypODBC.lngHdbc, SQL_NULL_HSTMT)
     End If
     intRet = oSQLFreeEnv(ByVal mtypODBC.lngHenv)
     If intRet <<>> SQL_SUCCESS Then
Call rODBCErrorInfo(mtypODBC.lngHenv, _
     SQL_NULL_HDBC, SQL_NULL_HSTMT)
     End If
     rODBCDisconnectExit:
     Exit Function
rODBCDisconnectErr:
     MsgBox Err.Number & ": " & Err.Description,_ vbCritical, "rODBCDisconnect()"
     rODBCDisconnect = False
     Resume rODBCDisconnectExit
End Function

    Как видно из вышеизложенного, функции ODBC API можно разделить на несколько типов, в зависимости от задач, для которых они предназначены.
    В табл. 8.6 перечислены базисные функции или функции ядра ODBC с пояснениями их назначения (см. также рис. 8.12). При этом функции объединены по признаку их назначения.


Рис. 8.12.
Таблица 8.6. Базисные функции ODBC API
НазначениеФункцияОписание
Соединение с источником данныхSQLAllocEnvПолучает указатель окружения. Одно окружение может служить для создания нескольких соединений.
^SQLAlloc- ConnectПолучает указатель соединения.
^SQLConnectСоединяется с указанным драйвером, используя имя источника данных, идентификатор пользователя и пароль.
Подготовка SQL запросовSQLAllocStmtРазмещает указатель выражения.
^SQLPrepareПодготавливает SQL выражение для дальнейшего использования.
^SQLGet-CursorNameВозвращает имя, связанное с указателем выражения.
^SQLSet-CursorNameУстанавливает имя курсора.
Выполнение запросовSQLExecuteВыполняет заранее подготовленный запрос.
^SQLExec-DirectВыполняет запрос.
Выборка результатов и информации о результатахSQLRow-CountВозвращает количество записей, задействованных в операциях вставки, удаления, модификации.
^SQLNum-ResultColВозвращает количество колонок в выбранном наборе данных.
^SQLDescribe-ColОписывает колонку в выбранном наборе данных.
^SQLCol-AttributesОписывает атрибуты колонки в выбранном наборе данных.
^SQLBindColПрисваивает место в памяти для колонки в выбранном наборе данных и указывает ее тип данных.
^SQLFetchВозвращает несколько наборов данных.
Окончание работы выраженияSQLFreeStmtЗаканчивает процесс работы выражения.
^SQLCancelПрерывает работу выражения.
^SQLTransactЗавершает или откатывает транзакцию.
Окончание работы соединенияSQL-DisconnectЗакрывает транзакцию.
^SQLFreeEnvУдаляет указатель окружения.
^SQLFree-ConnectУдаляет указатель соединения.

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

Declare Function oSQLRowCount Lib "odbc32.dll" _
Alias "SQLRowCount" (ByVal hstmt As Long, pcrow As Long)_As Integer
Declare Function oSQLBindCol Lib "odbc32.dll" _
Alias "SQLBindCol" (ByVal hstmt As Long, ByVal icol As_ Integer, _
ByVal fCType As Integer, rgbValue As Any, _
ByVal cbValueMax As Long, pcbValue As Long) As Integer
Declare Function oSQLColAttributes Lib "odbc32.dll" _
Alias "SQLColAttributes" (ByVal hstmt As Long, ByVal_ icol As Integer, _
ByVal fCType As Integer, rgbDesc As Any, ByVal cbDescMax_ As Integer, _
pcbDesc As Integer, pfDesc As Long) As Integer
Declare Function oSQLDescribeCol Lib "odbc32.dll" _
Alias "SQLDescribeCol" (ByVal hstmt As Long, _
ByVal icol As Integer, ByVal szColName As String, _
ByVal cbColNameMax As Integer, pcbColName As Integer, _
pfSQLType As Integer, pcbColDef As Long, _
pibScale As Integer, pfNullable As Integer) As Integer
Declare Function oSQLDisconnect Lib "odbc32.dll" _
Alias "SQLDisconnect" (ByVal hdbc As Long) As Integer
Declare Function oSQLDriverConnect Lib "odbc32.dll" _
Alias "SQLDriverConnect" (ByVal hdbc As Long, _
ByVal Hwnd As Long, ByVal szConnStrIn As String, _
ByVal cbConnStrIn As Integer, _
ByVal szConnStrOut As String, ByVal cbConnStrOutMax As_ Integer, _
pcbConnStrOut As Integer, ByVal fDriverCompletion As Integer) As Integer
Declare Function oSQLError Lib "odbc32.dll" _
Alias "SQLError" (ByVal henv As Long, ByVal hdbc As_ Long, _
ByVal hstmt As Long, ByVal szSqlState As String, _
pfNativeError As Long, ByVal szErrorMessage As String, _
ByVal cbErrorMsgMax As Integer, pcErrorMsg As Integer)_ As Integer

    Объявленная выше последняя функция служит для обработки ошибок, которые могут возникнуть и возникают при использовании ODBC API. ODBC поддерживает стандартную модель обработки ошибок. Каждая функция ODBC возвращает некий код, одним из которых может быть SQL_ERROR. Чтобы получить больше информации об ошибке, приложение вызывает функцию SQLError().
    Драйвер хранит информацию об ошибке в структурах henv, hdbc и hstmt и возвращает эту информацию приложению, когда приложение вызывает SQLError(). Каждая функция может вызвать ноль или больше ошибок.
    Приложение обычно вызывает функцию SQLError(), когда предыдущий вызов ODBC функции возвращает SQL_ERROR или SQL_SUCCESS_WITH_INFO. Приложение может тем не менее вызвать SQLError() после вызова любой ODBC функции.
    Функция SQLError() возвращает следующую информацию:

  • SQLSTATE - стандартный идентификатор ошибки.
  • Native Error Code - код ошибки, свойственный данному источнику данных.
  • Error Message Text - описание ошибки.

    Ошибки сохраняются для текущего указателя до тех пор, пока данный указатель не будет использован в вызове следующей функции. К примеру, ошибки на hstmt для текущей функции очищаются, как только другая функция будет выполнена с использованием такого же указателя. Ошибки, хранимые для данного указателя, никогда не очищаются в результате вызова функции с использованием указателя другого, хотя и родственного типа. Например, ошибки на hdbc не очищаются, когда вызов делается к родственной hstmt.
    Функция SQLError() возвращает ошибку из структуры, связанной с самым правым ненулевым аргументом указателя. Приложение запрашивает информацию об ошибке в следующем порядке:

  • Чтобы получить ошибки, связанные с окружением, приложение передает соответствующий henv и включает SQL_NULL_HDBC и SQL_NULL_HSTMT в hdbc и hstmt соответственно. Драйвер возвращает статус ошибки ODBC функции, вызываемой самой последней с тем же самым henv.
  • Для вывода ошибок, связанных с соединением, приложение передает соответствующий hdbc плюс hstmt, равный SQL_NULL_HSTMT. В таком случае драйвер игнорирует аргумент henv. Драйвер возвращает статус ошибки функции ODBC, вызванной самой последней с hdbc.
  • Для отслеживания ошибок, связанных с выражением, приложение передает соответствующий указатель hstmt. Если вызов SQLError() содержит правильный указатель hstmt, драйвер игнорирует аргументы hdbc и henv. Драйвер возвращает статус ошибки самой последней функции ODBC, вызванной с указателем hstmt.
  • Для отслеживания нескольких ошибок, вызываемых функцией, приложение обращается к SQLError() несколько раз. Для каждой ошибки драйвер возвращает SQL_SUCCESS и удаляет эту ошибку из списка доступных ошибок.

    Когда отсутствует дополнительная информация по самому правому ненулевому указателю, функция SQLError() возвращает SQL_NO_DATA_FOUND.

Declare Function oSQLExtendedFetch Lib "odbc32.dll" _
Alias "SQLExtendedFetch" (ByVal hstmt As Long, _
ByVal fFetchType As Long, ByVal irow As Integer, _
pcrow As Long, rgfRowStatus As Integer) As Integer
Declare Function oSQLFetch Lib "odbc32.dll" _
Alias "SQLFetch" (ByVal hstmt As Long) As Integer
Declare Function oSQLGetCursorName Lib "odbc32.dll" _
Alias "SQLGetCursorName" (ByVal hstmt As Long, _
ByVal szCursor As String, ByVal cbCursorMax As Integer, _
pcbCursor As Integer) As Integer
Declare Function oSQLGetData Lib "odbc32.dll" _
Alias "SQLGetData" (ByVal hstmt As Long, ByVal icol As_ Integer, _
ByVal fCType As Integer, rgbValue As Any, _
ByVal cbValueMax As Long, pcbValue As Long) As Integer
Declare Function oSQLGetInfo Lib "odbc32.dll" _
Alias "SQLGetInfo" (ByVal hdbc As Long, _
ByVal fInfoType As Integer, ByRef rgbInfoValue As Any, _
ByVal cbInfoMax As Integer, cbInfoOut As Integer) As_ Integer
Declare Function oSQLGetStmtOption Lib "odbc32.dll" _
Alias "SQLGetStmtOption" (ByVal hstmt As Long, _
ByVal fOption As Integer, pvparam As Any) As Integer
Declare Function oSQLNumResultCols Lib "odbc32.dll" _
Alias "SQLNumResultCols" (ByVal hstmt As Long, pccol As_ Integer) As Integer
Declare Function oSQLSetCursorName Lib "odbc32.dll" _
Alias "SQLSetCursorName" (ByVal hstmt As Long, _
ByVal szCursor As String, ByVal cbCursor As Integer) As_ Integer
Declare Function oSQLSetPos Lib "odbc32.dll" _
Alias "SQLSetPos" (ByVal hstmt As Long, _
ByVal irow As Integer, ByVal fOption As Integer, _
ByVal fLock As Integer) As Integer
Declare Function oSQLSetStmtOption Lib "odbc32.dll" _
Alias "SQLSetStmtOption" (ByVal hstmt As Long, _
ByVal fOption As Integer, ByVal bparam As Long) As_ Integer

8.5. Remote Data Objects

    Технология Remote Data Objects (RDO) - объекты для доступа к внешним данным - доступна, если на компьютере установлена версия Visual Basic 4.0 Entreprise. Очень мало, а в некоторых случаях и совсем не уступающая по скорости доступа к данным, по отношению к прямому использованию ODBC API, данная технология значительно проще в использовании. Особенно тем, кто знаком с DAO - набором объектов для доступа к данным.
    В этом параграфе мы рассмотрим практические вопросы использования технологии RDO в приложении клиент-сервер.
    RDO представляет собой тонкую прослойку кода над ODBC API и менеджером драйверов, которая устанавливает соединения, создает наборы данных и курсоры, выполняет другие сложные процедуры, требуя минимума ресурсов рабочей станции, так как все процессы происходят на сервере.
    Есть определенные различия при визуальной работе с RDO в Visual Basic и Access. В Visual Basic у вас есть возможность воспользоваться элементом управления ActiveX - RemoteControl, который еще в большей степени скрывает сложность работы с внешними данными. Вам достаточно установить лишь несколько свойств этого объекта, и вы получаете доступ к необходимой информации.
    Используя RDO или объект RemoteControl, вы обходите процессор данных. При этом вы можете иметь доступ к внешним данным любого формата, хотя лучше использовать метод для доступа к данным на сервере таких баз данных, как MS SQL Server или Oracle. При работе с RDO вы можете использовать как синхронный, так и асинхронные процессы, поэтому при большой выборке данных с сервера ваше приложение не будет блокировано на период получения данных.
    В табл. 8.7 приводятся эквиваленты объектов DAO для объектов RDO.
    RDO оперирует понятиями "строка", а не "запись" и "колонка", а не "поле". Так же как DAO, все объекты в RDO содержатся в коллекциях, исключение составляет только rdoEngine. Иерархия объектов RDO приведена на рис. 8.13. Когда RDO инициализируется в первый раз, то создается экземпляр объекта rdoEngine и коллекция rdoEnvirinments, состоящая из одного объекта rdoEnvironments(0).


Рис. 8.13.

    Для установки связи с источником данных ODBC и необходимой базой данных необходимо создать объект rdoConnection. Если у вас нет необходимого источника данных, то вы можете создать его с помощью метода rdoRegisterDataSource или с помощью диалоговых средств Администратора ODBC.

Таблица 8.7. Соответствие объектов RDO объектам DAO
Объект RDOЭквивалентный объект DAO
rdoEngineDBEngine
ОтсутствуетUser, Group
rdoEnvironmentWorkspace
rdoConnectionDatabase
rdoTableTableDef
ОтсутствуетIndex
rdoResultsetRecordset
Forward-only - typeПохоже на Forward-Only Snapshot
rdoColumnField
rdoPreparedStatementQueryDef

    Существует два подхода к выполнению запросов и созданию результирующего набора данных:

  • Использование запроса, который будет применяться только один раз. В этом случае воспользуйтесь методами OpenResulset или Execute, выполняющими SQL запрос для существующего объекта rdoConnection и создающими объект rdoResultset или выполняющими запрос действия.
  • Использование запроса, который будет применяться неоднократно и дополнительно может иметь параметры. Воспользуйтесь методом CreatePreparedStatement для создания объекта rdoPreparedStatement, который может использоваться в любое время, когда он снова понадобится и для которого можно изменять значения параметров перед каждым повторным использованием. После того как объект rdoPreparedStatement будет создан, используйте методы OpenResultset и Execute с объектом rdoPreparedStatement, для того чтобы создать объект rdoResulset или выполнить запрос действия. Для того чтобы изменить параметры, используйте установки объекта rdoParameter.

    Используя аргументы метода OpenResultset или свойства объекта rdoPreparedStatement, вы можете установить тип курсора и другие атрибуты объекта rdoResultset.
    Перед тем как начать работать с RDO, необходимо, используя команду References меню Tools, подключить объекты RDO к вашему проекту. В списке доступных объектов они так и будут называться - Microsoft Remote Data Object. Если вы работаете в Access, то достаточно наличия этих объектов на компьютере, но, как указывалось выше, для этого необходимо установить Visual Basic Entreprise Edition. После того как эти условия выполнены, вы можете свободно обращаться ко всем методам и свойствам этих объектов.
    Самым верхним в иерархии объектов является rdoEngine. Вспомните об объекте dbEngine из DAO. С помощью следующей строчки вы уже обращаетесь к RDO:

rdoEngine.rdoDefaultCursorDriver = rdUseOdbc

    Объект rdoEngine имеет следующие свойства:
    - rdoDefaultCursorDriver - может принимать три значения, которые являются предопределенными константами:

КонстантаЗначениеОписание
rdUseIfNeeded0ODBC драйвер будет использовать соответствующий тип курсора. Курсоры сервера будут использоваться, если они доступны.
rdUseODBC1В данном случае RDO использует библиотеку курсоров ODBC. Наиболее подходящий путь при работе с выборками небольшого объема.
RdUseServer2Драйвер ODBC использует курсоры сервера. Подходит для большинства операций при обработке больших массивов данных, но приводит к резкому возрастанию сетевого трафика.

    Курсором является логический набор данных, управляемый источником данных или Диспетчером ODBC.
    - rdoDefaultErrorTreshold - данное свойство устанавливает или возвращает установку по умолчанию для свойства ErrorTreshold объекта rdoPreparedStatement. Суть использования данного свойства заключается в том, что каждая ошибка имеет свойство Number. Если свойство Number больше значения свойства ErrorTreshold, то ошибка не генерируется, в противном случае генерируется перехватываемая ошибка, которая либо заканчивает работу приложения, либо обрабатывается, если это предусмотрено в коде или системе. Если установить это свойство равным -1, то, соответственно, никакого объекта для отсечения перехватываемых ошибок не будет.
    - rdoDefaultLoginTimeout - возвращает или устанавливает количество секунд, в течение которых ODBC драйвер прекратит попытку установить соединение с источником данных и возвратит перехватываемую ошибку. Значение по умолчанию - 15 с. Если это значение равно 0, то попытка установить соединение с источником данных, используя текущую процедуру регистрации, будут продолжаться неопределенное время.
    Свойства rdoDefaultPassword и rdoDefaultUser устанавливают пароль и пользователя по умолчанию для всякого вновь создающегося объекта rdoEnvironment.
    Объект rdoEngine имеет два метода. Метод rdoRegisterDataSource служит для занесения в Регистр Windows информации о вновь создаваемом источнике данных ODBC. Таким образом вы можете создавать источники данных, минуя диалоговое окно Администратора ODBC. Это очень удобно при распространении вашего приложения. Хотя есть и другие способы внести информацию о новом источнике данных в Регистр Windows.
    Приведем синтаксис этого метода:

rdoRegisterDataSource cDataSourceName, cDriverName, lSilent, cAttributes
    где
  • cDataSourceName - имя, которое вы хотите присвоить источнику данных. В дальнейшем остальные методы, которым необходимо связаться с базой данных, для которой вы создаете источник данных, будут использовать это имя. Например, метод OpenConnection.
  • cDriverName - строковое выражение, являющееся именем драйвера, зарегистрированным в Регистре Windows. При этом обращаем внимание, что драйвер уже должен быть установлен.
  • lSilent - логическое выражение, которое указывает, будет ли появляться диалог ODBC, в котором вы укажете специфичную для данного драйвера информацию. Если вы укажете это значение равным True, то вам надо будет указать всю необходимую информацию в аргументе cAttributes.
  • cAttributes - строковое выражение, в котором вы указываете дополнительную информацию для драйвера. Для каждого драйвера эта информация специфична. Параметры, которые необходимо описать, вы можете посмотреть в Регистре Windows, найдя описание источника данных, которые устанавливает связь с базой данных подобного формата. Например так, как это показано на рис. 8.14.

    Для работы с внешними данными RDO использует объект окружения (rdoEnvironment). При этом одно окружение в коллекции создается автоматически. Этого окружения вполне достаточно для работы с данными, так как с помощью него вы можете создать сколько угодно соединений и одновременно редактировать таблицы из нескольких баз данных, даже разного формата. Но иногда необходимо обрабатывать более сложные ситуации, в которых надо вести несколько транзакций одновременно. Тогда имеет смысл создавать дополнительные объекты rdoEnvironment. Здесь необходимо отметить, что ODBC не поддерживает возможности вложенных транзакций. Выход из положения можно найти, если приложение, с данными которого вы работаете, поддерживает вложенные транзакции. Тогда вы можете использовать SQL выражения, которые будут передаваться на сервер и создавать сложные вложенные и пересекающиеся транзакции.
    Объект rdoEnvironment создается с помощью метода rdoCreateEnvironment и имеет следующий синтаксис:

set Variable = rdoCreateEnvironment(Name, User, Password)
    где
  • Variable - объектная переменная, которая ссылается на объект rdoEnvironment.
  • Name - строковая переменная, которая становится уникальным именем объекта Environment.
  • User - имя пользователя объекта rdoEnvironment.
  • Password - пароль пользователя.

    Приведенная ниже программа демонстрирует установку и чтение некоторых из вышеприведенных свойств и использование методов rdoRegisterDataSource и rdoCreateEnvironment.

Const rServerDSN = "ToAutostore"
Const rServerUser = "UID=sa;DATABASE=autostore"
Dim grdfEnv As rdo.rdoEnvironment
Dom grdfConn As rdo.rdoConnection
Dim sqlAttr As String
strAttr = "Description=Connect to autostore" & _
     Chr$(13) & "OemToAnsi=No" & _
     Chr$(13) & "Address=\\MAINSERVER\AUTOSTORE\" _ & Chr$(13) & _
     "Database=Autostore"
rdoEngine.rdoRegisterDatasource rServerDSN, _ "SQL Server", True, sqlAttr
If grdfEnv Is Nothing Then
     rdoEngine.rdoDefaultCursorDriver = rdUseOdbc
     Set grdfEnv = rdoEngine.rdoCreateEnvironment _
     ("", "", "")
     Set grdfConn = grdfEnv.OpenConnection _
     (rServerDSN, rdDriverNoPrompt, False, _
     rServerUser)
     grdfConn.QueryTimeout = 0
End If

    Коллекция rdoEnvironments содержит в себе все объекты rdoEnvironment объекта rdoEngine. Она имеет всего один метод Item, который устанавливает доступ к конкретному объекту в зависимости от его порядкового номера в коллекции. То есть вы можете обращаться к первому объекту в коллекции с помощью метода Item, например:

Set grdfConn = rdoEnvironments.Item(0).grdfEnv.OpenConnection _
(rServerDSN, rdDriverNoPrompt, False, _
rServerUser)
    В то же время следующий пример эквивалентен предыдущему:
Set grdfConn = rdoEnvironments(0).grdfEnv.OpenConnection _
(rServerDSN, rdDriverNoPrompt, False, _
rServerUser)

    Коллекция rdoEnvironments имеет свойство Count, которое используется только для чтения и возвращает количество объектов в коллекции.
    Объект rdoEnvironment содержит три метода для работы с транзакциями. Это методы BeginTrans, CommitTrans и RollBackTrans. Эти методы очень похожи на соответствующие методы объекта Workspace из DAO. Но имеет смысл напомнить еще раз, что нельзя использовать вложенные транзакции. В то же время вы можете управлять транзакциями на сервере с помощью SQL команд BEGIN TRANSACTION, COMMIT TRANSACTION, ROLLBACK TRANSACTION, передавая их на сервер с помощью запросов действия.
    BEGINTRANS начинает транзакцию.
    COMMITTRANS прекращает текущую транзакцию и сохраняет все изменения.
    ROLLBACKTRANS заканчивает текущую транзакцию и восстанавливает базы данных в текущем объекте rdoEnvironment в то состояние, в котором они находились до начала транзакции.
    Объект rdoEnvironment имеет метод Close. При работе с этим методом необходимо помнить, что вы не можете закрыть окружение по умолчанию, то есть объект rdoEnvironments(0). Если текущий объект rdoEnvironment содержит открытые соединения, которые используются объектами rdoResulset, то после закрытия объекта rdoEnvironment все текущие транзакции прекращаются и базы данных возвращаются в первоначальное состояние.
    Метод OpenConnection служит для создания и открытия соединений, которые являются единственной коллекцией, содержащейся в объекте rdoEnvironment и объекты которой используются для доступа к данным в базе данных, указанной в источнике данных. Этот метод имеет следующий синтаксис:

Set Connection = Environment.OpenConnection(DataSourceName[,
Prompt[, Readonly[, Connect]]])

    Connection - объектная переменная, которая ссылается на объект rdoConnection.
    Environment - выражение, которое обозначает существующее окружение.
    DataSourceName - строковое выражение, обозначающее имя источника данных, так как оно зарегистрировано в Реестре Windows. При этом если не указать его, то есть ввести пустую строку или указать несуществующий источник данных, то будет выведено диалоговое окно ODBC для выбора источника данных из списка существующих. Тем не менее, если аргумент Prompt имеет значение rdDriverNoPrompt, будет сгенерирована перехватываемая ошибка и никакого диалогового окна выведено не будет. В то же время можно ввести имя источника данных в аргументе Connect в параметре DataSourceName.
    Prompt может принимать одно из четырех значений предопределенных констант. В зависимости от этого значения диспетчер ODBC драйверов требует от пользователя ввода DataSourceName (имени источника данных), имени пользователя и пароля.

Значение аргументаОписание
rdDriverNoPromptДиспетчер драйверов использует информацию из аргументов DataSourceName и Connect для построения строки соединения. Если необходимая информация не будет обеспечена, то метод OpenConnection возвратит ошибку.
RdDriverPromptДиспетчер драйверов выводит диалог ODBC и строит строку соединения по информации, введенной пользователем в этом диалоге.
rdDriverCompleteДиспетчер драйверов выводит диалог ODBC только в случае недостатка информации в аргументах DataSourceName и Connect.
rdDriverCompleteRequiredДиспетчер драйверов ведет себя так же, как в случае, когда данный аргумент равен rdDriverComplete, за исключением того, что в диалоге ODBC отключены элементы, предоставляющие информацию, которая больше не нужна.
  • Readonly - определяет, будет ли соединение открыто для доступа только для чтения или для чтения и записи.
  • Connect - строковое выражение, используемое для открытия баз данных. Эта строка составляет ODBC аргументы соединения и зависит от конкретного используемого драйвера.

    Иногда могут возникнуть причины, по которым вы не сможете установить соединение, среди них такие, как отсутствие прав доступа к источнику данных, неправильное соединение сети, отсутствие или отключение источника данных. Некоторые серверы баз данных имеют ограничение на количество соединений по разным причинам, в том числе и ограничение по ресурсам.
    Создав объект rdoConnection, вы можете использовать его для

  • Создания объектов rdoResultset или rdoPreparedStatement, используя методы OpenResultset или CreatePreparedStatement, в зависимости от решаемых задач.
  • Доступа к таблицам базы данных и колонкам каждой таблицы, используя объекты rdoTable и rdoColumn в коллекциях rdoConnections(0).rdoTables и rdoTables(n).rdoColumns.
  • Начала, завершения или отката транзакции, используя методы Begin-Trans, CommitTrans и RollbackTrans.
  • Отсоединения от источника данных и освобождения ресурсов, используя метод Close.

    Объект rdoEnvironment имеет несколько свойств, которые перечислены в следующей таблице.

СвойствоОписание
CursorDriverСлужит для установки и чтения значения, устанавливающего тип курсора, который будет создан. Может иметь три значения: rdUsedIfNeeded, rdUseODBC, rdUseServer.
hEnvВозвращает значение указателя ODBC окружения, которое можно использовать для вызова ODBC API функций.
LoginTimeOutПри использовании баз данных ODBC возможны задержки из-за сетевого трафика или напряженного использования источника данных ODBC. Чтобы избежать неопределенно долгого ожидания, можно установить время, после которого диспетчер драйверов ODBC сгенерирует ошибку.
NameИмя объекта.
PasswordВозвращает пароль, используемый во время создания объекта rdoEnvironment.
UserNameВозвращает или устанавливает имя пользователя объекта rdoEnvironment.

    Следующая коллекция объектов, которая входит в объект rdoEngine, называется rdoErrors и служит для обработки ошибок при работе с RDO.
    Всякий раз, когда диспетчер ODBC пытается выполнить запрос RDO, может возникнуть ошибка. Подобные ошибки могут иметь различные причины и вызывать различные последствия для вашей программы, вплоть до полного прекращения процесса выполнения запроса или даже вашей процедуры или функции. Как только ошибки возникли, информация о них помещается в коллекцию rdoErrors. Далее появляется возможность исследовать каждый объект rdoError коллекции rdoErrors на предмет причины возникновения ошибки и выполнения дальнейших действий. Visual Basic также создает перехватываемую ошибку во время возникновения ошибочных ситуаций. Поэтому имеет смысл использовать конструкцию

On Error Goto <<метка>>

    для обработки информации, содержащейся в объектах коллекции rdoError и для принятия последующих решений. Два свойства - rdoDefaultErrorTreshold и ErrorTreshold - позволяют понизить или повысить ограничения на ситуации, которые могут вызвать фатальные ошибки.
    Добавим в предыдущий пример строчки, которые будут заниматься обработкой ошибок:

Function rdfInitialize() As Boolean
Const rServerDSN = "ToAutostore"
Const rServerUser = "UID=sa;DATABASE=autostore"
Dim errX As rdo.rdoError
Dim grdfEnv As rdo.rdoEnvironment
Dim grdfConn As rdo.rdoConnection
Dim sqlAttr As String
strAttr = "Description=Connect to autostore" & _
     Chr$(13) & "OemToAnsi=No" & _
     Chr$(13) & "Address=\\MAINSERVER\AUTOSTORE\" _ & Chr$(13) & _
     "Database=Autostore"
rdoEngine.rdoRegisterDatasource rServerDSN,_ "SQL Server", _
True, sqlAttr
If grdfEnv Is Nothing Then
     rdoEngine.rdoDefaultCursorDriver = rdUseOdbc
     Set grdfEnv = rdoEngine.rdoCreateEnvironment _
     ("", "", "")
     Set grdfConn = grdfEnv.OpenConnection _
     (rServerDSN, rdDriverNoPrompt, False, _
     rServerUser)
     grdfConn.QueryTimeout = 0
End If
rdfInitializeExit:
     DoCmd.Hourglass False
     Exit Function
rdfInitializeErr:
For Each errX In rdoEngine.Errors
     MsgBox "Ошибка " & errX.Number & " вызвана " _
& errX.Source & ": " & errX.Description, _
vbCritical, "rdfInitialize()"
Next errX
     rdfInitialize = False
Resume rdfInitializeExit
End Function

    Коллекция rdoErrors имеет два метода. Метод Clear служит для удаления всех объектов rdoError из коллекции. Метод Item предназначен для доступа к объектам rdoError по их индексу. Впрочем, следующие строчки эквивалентны:

myRdoError=rdoEngine.rdoErrors.Item(1)
myRdoError=rdoEngine.rdoErrors(1)

    Свойство Count возвращает количество объектов rdoError в коллекции.
    Следующая таблица описывает свойства объекта rdoError.

СвойствоОписание
DescriptionВозвращает строковое выражение, содержащее описание ошибки.
HelpContextЕсли в свойстве HelpFile указан файл помощи Microsoft Windows, свойство HelpContext используется для того, чтобы автоматически выводить раздел помощи, который он идентифицирует.
Help-FileУказывает полный путь к файлу помощи.
SourceВозвращает строковое выражение. При возникновении ошибки во время операций ODBC объект rdoError добавляется в коллекцию rdoErrors. Если ошибка возникла внутри RDO, возвращаемое значение начинается с выражения "MSRDO32". Объектный класс, который явился инициатором ошибки, также может быть добавлен к значению свойства Source.
SQLRet-CodeВозвращает код ошибки последней операции RDO. Значение имеет тип Long и может равняться одной из следующих констант:
rdSQLSuccess - операция завершилась успешно;
rdSQLSuccessWithInfo - операция завершилась успешно и доступна дополнительная информация;
rdSQLNoDataFound - нет никаких доступных дополнительных данных;
rdSQLError - ошибка, случившаяся во время выполнения операции;
rdSQLInvalidHandle - предоставленный указатель указан неверно.
SQL-StateСимвольная строка, возвращаемая свойством SQLState, состоит из двухсимвольного значения класса, за которым следует трехсимвольное значение подкласса. Значение класса "01" указывает на предупреждение и сопровождается кодом, возвращаемым rdSQLSuccessWithInfo.

    Следующим объектом в иерархии RDO является rdoConnection, который представляет собой физическое соединение с сервером. Объект rdoConnection имеет методы для работы с транзакциями, но в данном случае налицо явный пример полиморфизма, так как здесь диапазон действия этих методов ограничивается ODBC процессами, использующими указатель текущего соединения. То есть, если вы применяете параллельные транзакции, каждая их которых использует свое соединение или объект rdoConnection, то можете завершить каждую транзакцию отдельно, применяя методы CommitTrans каждого объекта по очереди. Если необходимо завершить все транзакции в текущем окружении, то используйте метод CommitTrans (или RollbackTrans) объекта rdoEnvironment.
    С помощью объекта rdoConnection мы имеем доступ к трем объектам, находящимся на последних уровнях иерархии RDO. Объект rdoTable предоставляет таблицы и представления (View), хранящиеся в базе данных. Объект rdoTable позволяет нам узнать много полезной информации о таблице или представлении на сервере. С его помощью можно получить сведения о типе таблицы, количестве записей и возможности модификацииь ее данных, что является достаточно важным свойством, так как представления на сервере очень часто не позволяют изменять содержимое строк и колонок, которые они порождают. В то же время для объекта rdoTable нет методов для перемещения по записям, поэтому достаточно затруднительно вывести информацию о записи, которая не является первой. Объекты rdoTable входят в коллекцию rdoTables. При этом мы можем обращаться к любому ее объекту по его имени, то есть следующим образом:

Set myrdEnv =rdoEngine.rdoEnvironment(0)
Set myrdConn=myrdEnv.OpenConnection("Toautostore", _ rdDriverNoPrompt, False)
Set myrdTable = myrdConn.rdoTables("account")
    Последнюю строчку можно переписать следующим образом:
Set myrdTable = myrdConn.rdoTables!account

    Если вы знаете порядковый номер объекта rdoTable в коллекции rdoTables, то возможно и такое обращение:

Set myrdEnv =rdoEngine.rdoEnvironment(0)
Set myrdConn=myrdEnv.OpenConnection("Toautostore", _ rdDriverNoPrompt, False)
Set myrdTable = myrdConn.rdoTables(2)

    Вторым объектом на этом уровне иерархии, который мы рассмотрим, будет объект rdoResults. При разработке интерфейса конечного пользователя именно этот объект является самым главным. Следующий код показывает простейший пример получения и использования этого объекта.
    Создайте в Visual Basic 4.0 Entreprise Edition форму, в которой разместите текстовое поле с названием text1.

Private Sub Form_Load()
     'Создается переменная объекта окружения -
     'соответствует Workspace
     Dim RDE As rdo.rdoEnvironment
     ' Создается переменная соединения - объекта,
     ' служащего для скрытия
     ' сложной работы функций ODBC API,
     ' и который позволяет нам подсоединятся
     ' к данным любого формата при наличии,
     ' естественно, соответствующего
     ' драйвера
     Dim rdConn As rdo.rdoConnection
     ' Ниже создаются объекты таблицы,
     ' колонки, набора данных, ошибки
     Dim rdoTab As rdo.rdoTable
     Dim rdoCol As rdo.rdoColumn
     Dim rdoRst As rdo.rdoResultset
     Dim errX As rdoError
     ' Свойство rdoDefaultCursorDriver определяет, какой
     ' курсор будет
     ' использоваться для перемещения по данным -
     ' курсор ODBC или сервера
     ' в нашем случае мы выбрали курсор
     ' сервера, так как при работе с данными
     ' большого набора это лучший выбор
     rdoEngine.rdoDefaultCursorDriver = rdUseServer
     'Инициализация переменной окружения
     Set RDE = rdoEngine.rdoCreateEnvironment("", "", "")
     'Инициализация переменной соединения
     Set rdConn = RDE.OpenConnection("myteach",_rdDriverNoPrompt, False)
     'Инициализация обработчика ошибок
     On Error GoTo rdfInitializeErr
     ' Данная строчка помещена из методических
     ' соображений,
     ' потому что вы вполне можете
     ' ее опустить и написать следующую строчку как Set rdorst=
     'rdConn.OpenResultset("MyTable",rdOpenDynamic,rdConcurValues).
     'Мы просто хотим указать, что при обращении
     'в методе OpenResultset к
     'объекту rdoTable требуется его имя.
     'Впрочем, об этом сказано в Справке.
     Set rdoTab = rdConn.rdoTables("MyTable")
     'Инициализация переменной набора данных
     Set rdoRst = rdConn.OpenResultset(rdoTab.Name, _
     rdOpenDynamic, rdConcurValues)
     'Инициализация переменной колонки
     Set rdoCol = rdoRst.rdoColumns(1)
     rdoRst.Edit
     rdoCol.Value = 27 {dt>     rdoRst.Update
     Me!text1 = rdoCol.Value
     Exit Sub
rdfInitializeErr:
     If Err.Number = rdoEngine.rdoErrors(0).Number And _
rdoEngine.rdoErrors.Count >> 1 Then
For Each errX In rdoEngine.rdoErrors
     MsgBox "Error " & errX.Number & " вызвана " _
     & errX.Source & ": " & errX.Description,_
     vbCritical, "rdfInitialize()"
Next errX
     Else
MsgBox "Error " & Err.Number & " вызвана " _
     & Err.Source & ": " & Err.Description, _
vbCritical, "rdfInitialize()"
     End If
End Sub

    Объект rdoResultset можно создать с помощью метода OpenResulset, который применяется к объектам rdoConnection, rdoTable и rdoPreparedStatement (о последнем объекте речь впереди). В силу этого метод OpenResultset имеет два вида синтаксиса, один из которых относится к объекту rdoConnection

Set Variable = Connection.OpenResultset(Source[,
Type[, Locktype[, Options]]])
    другой к объектам rdoTable и rdoPrepraredStatement
Set Variable = Object.OpenResultset([ Type[,
Locktype [, Options]]])
    Пример первого варианта синтаксиса можно найти в предыдущей процедуре
Set rdoRst = rdConn.OpenResultset(rdoTab.Name, _
rdOpenDynamic, rdConcurValues)

    Первым аргументом является источник данных, которым может быть объект rdoTable, как показано в предыдущем примере, объект rdoPreparedStatement или SQL выражение.
    Таким образом, мы можем создать совершенно одинаковый набор данных, используя различные варианты синтаксиса и объекты в качестве аргументов. Например, используем SQL выражение:

Set rdoRst=rdConn.OpenResulset("SELECT * FROM MYTABLE", _
rdOpenDynamic, rdConcurValues)

    При использовании второго варианта синтаксиса аргумент Source (источник данных) отсутствует, так как объект, к которому вы применяете метод, сам по себе является источником данных.

Set rdoTab = rdConn.rdoTables("MyTable")
Set rdorst=rdoTab.OpenResultset(rdOpenDynamic,rdConcurValues)

    Если сравнить два последних фрагмента, становится видно, что первый вариант более гибок, так как, добавив условие с помощью ключевого слова WHERE, мы можем значительно сократить выборку и соответственно значительно уменьшить нагрузку на ресурсы, выиграв при этом в скорости выполнения.
    Теперь самое время поговорить об объекте rdoPreparedStatement, после чего мы продолжим разговор о методе OpenResulset.
    Объект rdoPreparedStatement создается с помощью метода объекта rdoConnection CreatePreparedStatement, который имеет следующий синтаксис:

Set Prepstmt = Connection.CreatePreparedStatement(Name, Sqlstring)

    Аргумент Name - это имя вновь создаваемого объекта rdoPreparedStatement. Следующий аргумент - Sqlstring - является правильным выражением SQL. Оба аргумента обязательны, но их можно заменить пустой строкой (""). Объект, созданный с помощью этого метода, автоматически добавляется к коллекции rdoPreparedStatements. При этом если вы не снабдили его именем, то есть использовали пустую строку, то обращаться к нему можно с помощью переменной объекта prepstmt или по порядковому номеру в коллекции - rdoPreparedStatements(2).
    У вновь созданного объекта rdoPreparedStatement имеется коллекция rdoParameters, с помощью которой вы можете передавать параметры в аргумент Source метода OpenResultset. Например:

SQLStr="SELECT * FROM Account WHERE account = ? AND_ summa >> ?"
Set myprepst=myrdConn. CreatePreparedStatement("FromAccount",SQLStr)
'Далее мы можем подставить значения параметров
myprepst.rdoParameters(0)=104
myprepst.rdoParameters(1)=25000
Set myrdRst=myprepst._ OpenResulset(rdOpenDynamic,rdConcurValues)

    Вернемся к методу OpenResultset, а точнее к его аргументу Type, который может иметь одно из четырех значений типа Integer.

ЗначениеПолучаемый тип набора данных
rdOpen-ForwardOnlyОбъект rdoResultset, в котором поиск записей может производиться только сверху вниз (от первой к последней записи). Указатель записи нельзя вернуть назад к первой записи, одновременно доступна только одна запись. Наборы данных такого типа используются для быстрого выбора и обработки данных
rdOpenStaticПорядок и значения в наборе данных статического курсора фиксируются при его открытии. Изменения, добавления и удаления, произведенные другими пользователями, не будут появляться до закрытия и последующего открытия курсора.
RdOpen-KeysetРезультат запроса может иметь изменяемые строки. Допускается перемещаться между записями. Этот набор данных вы можете использовать, чтобы добавлять, изменять или удалять данные из соответствующей таблицы или таблиц. Членство этого набора данных фиксировано.
RdOpen-DynamicРезультат запроса может иметь изменяемые строки. Допускается перемещаться между записями. Этот набор данных вы можете использовать, чтобы добавлять, изменять или удалять данные из соответствующей таблицы или таблиц. Членство этого набора данных не фиксировано.

    Выбирайте значение аргумента в зависимости от требуемой функциональности. Если вам не нужно редактировать данные на сервере, то имеет смысл выбирать значение rdOpenForwardOnly. То есть если вы строите отчет за какой-то период, то вам нет смысла отслеживать изменения, которые произойдут в течение следующего промежутка времени. Если же программа помогает продавать железнодорожные билеты, то ей постоянно нужны свежие данные о количестве свободных мест. Наиболее надежным в этом плане является динамический тип набора данных, но с ним и тяжелее всего работать.
    Следующий аргумент LockType служит для контроля за блокировками страниц, которые содержат редактируемую или записываемую на диск запись. Может принимать значение типа Integer, равное одной из следующих констант:

ЗначениеТип разрешения проблем одновременного доступа
rdConcurLockПессимистический тип, то есть страница блокируется, как только вы выполнили метод Edit. В это время другие пользователи редактировать данные на этой странице не могут.
RdConcur-ReadOnlyКурсор открывается только для чтения, следовательно, никакие блокировки не требуются
rdConcur-RowverОптимистическая блокировка, то есть запись блокируется только во время выполнения метода Update. При этом поиск записи в исходном наборе происходит по идентификатору записи.
rdConcurValuesОптимистическая блокировка, то есть запись блокируется только во время выполнения метода Update. При этом поиск записи в исходном наборе происходит по значениям строки.

    Последний аргумент Option служит для установки асинхронного режима выборки данных. При этом режиме программа может продолжать выполняться, не ожидая завершения выборки всех данных.
    Последними объектами в иерархии RDO является коллекция колонок - rdoColumns. С помощью объекта rdoColumn вы получаете доступ непосредственно к значениям таблиц в конкретных строках. При этом для перемещения по записям кроме оговоренных выше методов используются методы MoveFirst, MoveLast, MoveNext и MovePrevious, очень похожие на те, которыми вы пользовались при программировании с помощью DAO.

8.6. Внешнее управление сервером с помощью SQL-DMO

    Данный параграф относится только к Microsoft SQL Server. В нем мы опишем методы управления сервером с помощью пользовательского приложения.
    В клиентской части настольной базой данных может использоваться приложение, которое способно выступать в качестве OLE контроллера. Тем, кто прочитает десятую главу данной книги или хорошо знает Visual Basic, будет очень легко освоиться с набором объектов, который предоставляет DMO. Для того чтобы использовать данную технологию с рабочей станции, необходимо установить на рабочей станции клиентскую версию Microsoft SQL Server. Установить ее очень легко. Например, при установке SQL Server на рабочей станции, которая работает под управлением Windows 95, программа установки сама определит тип операционной системы и, соответственно, предложит для использования на данном компьютере клиентскую часть сервера. При этом вам будут доступны многие компоненты SQL Server, например Entreprise Manager или Books On-line.
    В любом случае для работы с SQL-DMO на ПЭВМ, работающей под управлением Windows 95 или Windows NT, должны быть установлены следующие файлы:

  • SQLOLE.HLP - файл контекстной помощи для работы с SQL-DMO;
  • SQLOLE.REG - файл регистра;
  • SQLOLE65.DLL - in-process сервер SQL-DMO и программные компоненты;
  • SQLOLE65.TLB - библиотека объектов, используемая программой контроллером OLE Automation;
  • SQLOLE65.SQL - файл поддержки языка transact-SQL для создания хранимых процедур.

DMO - Distributed Management Object - это распределенные объекты управления. Главное назначение данной технологии - управление сервером с любой рабочей станции в сети.

    DMO может использоваться и для выполнения запросов. Правда, при этом вы не можете получить для использования в своем приложении курсоры и, соответственно, у вас нет средств навигации по таблицам. Запросы могут быть запросами выборки и запросами действия, или, иначе, запросами определения данных.
    Несмотря на то, что мы не можем получить удобный курсор с методами для навигации и отображения данных, которые бы хранились в нем, использовать SQL-DMO для доступа к конкретным данным можно. Для этого необходимо использовать объект QueryResults, который отображает данные запроса выборки и имеет методы для работы с колонками и строками.
    На рис. 8.15 приведена иерархия объектов SQL-DMO и их коллекций, а в табл. 8.8 содержится краткое описание этих объетов.


Рис. 8.15.
Таблица 8.8. Назначение объектов SQL-DMO
ОбъектОписание
AlertСодержит всю информацию, которая описывает предупреждения SQL Server, появляющиеся при наступлении определенных событий.
AlertSystemУправляет процессом выполнения предупреждений SQL Server.
ApplicationОбеспечивает доступ и управление средой приложения, в том числе коллекцией объектов SQL Server, которые связаны с используемыми в системе отдельными серверами.
ArticleСодержит информацию о статье, помещенной в публикацию. Статья - это таблица, представление или их часть, сихронизируемые по содержанию на внешнем и локальном серверах. С помощью свойств TableName и DestinationTableName задаются таблицы или представления, участвующие в синхронизации. Свойства InsertCommand, UpdateCommand и DeleteCommand позволяют задать операторы transact-SQL, которые будут выполняться при вставке, обновлении или удалении данных. Свойства ReplicateAllColumns и ReplicationFilterProcName позволяют ограничить по колонкам или строкам данные, помещаемые в статью.
BackupИспользуется для выполнения резервного копирования или восстановления БД или журнала транзакций.
BulkCopyСодержит информацию для копирования данных из таблиц или представлений в файл данных (текстовый файл) или из такого файла в таблицу SQL Server. Позволяет установить границы копирования и разделительные символы. Для копирования используются методы ExportData или ImportData объектов Table или View.
CheckСодержит информацию об ограничениях целостности, которые могут содержать одну или более колонок. С помощью свойства ExcludeReplication эти ограничения могут отключаться во время синхронизации данных.
ColumnПозволяет получить информацию об имеющихся в таблице колонках, типе хранящихся в них данных, значениях по умолчанию, допустимости значений NULL и т. д.
ConfigurationПозволяет получить информацию о конфигурации сервера. Включает только коллекцию объектов ConfigValue, каждый из которых имеет минимальное, максимальное и текущее значение. После изменения параметров конфигурации получить новые значения можно с помощью метода ReconfigureCurrentValue или ReconfigureWithOverride.
ConfigValueСохраняет минимальное, максимальное и текущее значение параметров конфигурации сервера.
DatabaseКонтейнер базы данных, который используется для ссылки на все содержащиеся в данной БД объекты, такие как Table, StoredProcedure и т. д. Служит также для выполнения запросов с помощью метода ExecuteWithResult.
DBObjectСодержит информацию, применимую ко всем объектам БД и позволяет использовать один и тот же код для управления несколькими объектами, такими как Table, View, Rule и т. д.
DBOptionСодержит все опции для БД, которые может установить ее владелец. Например, возможность подписки на данные из этой БД, права доступа и т. п.
DefaultЗначение по умолчанию, хранящееся для колонки или типа данных.
DeviceПредоставляет возможность получить информацию о имеющихся устройствах сервера (имя, физическое расположение, объем и т. п.). С помощью этого объекта вы можете полностью управлять процессом зеркальной записи данных и изменениями, выполняемыми параллельно на двух физических дисках.
DRIDefaultСодержит ограничения целостности по умолчанию для колонок.
ExecutiveСодержит информацию о выполнении плановых задач и предупреждений.
GroupСодержит информацию о группе пользователей.
History-FilterИспользуется для определения или очистки списка заданий с помощью методов EnumHistory и PurgeHistory объектов SQLServer и Task.
IndexИндекс для указанной таблицы. Позволяет получить информацию об имеющихся индексах, перестроить индекс, удалить, создать новый или скопировать существующий индекс для другой таблицы.
IntegratedSecurityОпределяет, как система доступа и обеспечения секретности SQL Server будет интегрироваться с аналогичными системами для бюджетов пользователей и групп пользователей Windows NT.
KeyСодержит информацию о первичном, уникальном или внешнем ключах таблицы. Каждый ключ может включать одну или более колонок таблицы.
LanguageСодержит полную информацию о каждом из языков, поддерживаемых SQL Server.
LoginИдентификатор пользователя и пароль для соединения с сервером. Этот объект имеет глобальный характер в целом для SQL Server; ему соответствует объект User в каждой БД.
NamesЭта коллекция служит для хранения имен объектов в виде строк символов.
OperatorОписывает условия работы оператора, в том числе его почтовый адрес, необходимость получения предупреждений, режим работы и т. д.
PermissionСодержит информацию о пользовательских объектах БД или привилегиях команд.
PublicationСодержит информацию о публикации (наборе статей), используемой для синхронизации данных. Метод AddFullSubscriber позволяет указать сервер, на котором данные синхронизируются по всем статьям в наборе.
QueryResultsСодержит данные, полученные при выполнении запроса. Данные могут быть получены путем выполнения метода ExecuteWithResults объектов SQLServer, Database и RemoteServer. Данные размещаются в памяти и не могут редактироваться.
RegisteredServerСодержит имя и данные о регистрации SQL серверов, входящих в объект ServerGroup для использования в SQL Enterprise Manager. Этот объект отображается в Server Manager и запоминается в Регистре.
RegistryСодержит всю информацию, размещенную в Регистре Windows о SQL Server.
Remote-LoginИспользуется при соединении внешнего сервера с локальным SQL Server и содержит необходимую для этого соединения информацию.
Remote-ServerВнешний SQL Server, который становится известным для локального сервера.
RuleСодержит информацию о допустимых данных для колонок или типов данных.
Server-GroupГруппа серверов, имеющая уникальное имя.
SQLServerОбъект для связи с используемым сервером. Каждый объект SQLServer может инициализировать одно соединение с выбранным сервером, используя метод Connect.
Stored-ProcedureПоименованный набор команд Transact-SQL, который запоминается в БД. Помимо команд в свойстве Text может храниться имя и путь к библиотеке DLL, реализующей расширенный вариант хранимых процедур.
Subscriber-InfoСодержит всю необходимую информацию для обновления данных на внешнем сервере, включая дату начала и конца подписки (свойства ActiveStartDate и ActiveEndDate), интервалы ежедневного, еженедельного и ежемесячного обновления и т. д.
SubscriptionСодержит информацию, необходимую для синхронизации данных.
System-DatatypeСодержит информацию о системных типах данных SQL Server.
TableПредставляет набор строк в БД (таблицу). Позволяет изменять свойства таблицы, добавлять новые колонки, проверять ограничения целостности и т. д.
TaskСодержит информацию о плановых заданиях.
TransactionLogСодержит информацию о процедуре регистрации транзакций. Позволяет зарезервировать пространство для журнала регистрации и указать необходимость его ведения на отдельном устройстве.
TransferСодержит информацию для пересылки данных и объектов из БД источника (объект Database) в другую БД, расположенную на внешнем сервере.
TriggerТриггер для указанной таблицы. Позволяет получить информацию о хранящихся триггерах или создать новый с помощью метода Script. Тип триггера определяется свойством Type.
UserСодержит информацию о пользователе БД. Каждый пользователь ассоциируется с одной регистрацией пользователя для работы с БД.
User-Defined-DatatypeОпределяет пользовательский тип данных.
ViewПредставление, хранящееся в БД. В свойстве Text хранится команда SELECT Transact-SQL для выполнения запроса. Данные в представлении не могут редактироваться.

    На вершине иерархии объектов DMO находится объект Application, который обеспечивает нас методами и свойствами для управления средой приложения.
    Важной особенностью использования объектов SQL-DMO является наличие наряду с самими объектами, их коллекций. За счет этого работа с объектами в Visual Basic существенно упрощается. Коллекция включает группу объектов одного типа. Имя коллекции образуется как множественное число от имени объекта. К коллекции можно обратиться из объекта, являющегося родительским в иерархии. Если объект не имеет коллекции, то используется коллекция Properties, которая имеет по одному объекту Property для каждого свойства. С ее помощью можно легко установить имена и значения всех свойств объекта, не зная их заранее. Например:

For Each oProperty in oSQLServer.Properties
     Debug.Print oProperty.Name & " = " oProperty.Value
Next

    В Visual FoxPro возможность использования таких языковых конструкций появится только в следующей версии. При использовании версии 3.0 работы немного прибавится. Например, для получения всех имен БД, имеющихся на сервере, в Visual Basic достаточно такого фрагмента кода:

For Each oDatabase in oSQLServer.Databases
     Debug.Print oDatabase.Name
Next
    В Visual FoxPro код будет чуть длиннее:
nDBNum = oSQLServer.Databases.Count
FOR nItem = 1 TO nDBNum
     ? oSQLServer.oDatabases(nItem).Name
NEXT

    Для добавления нового объекта в коллекцию используется метод Add. Для удаления объекта из коллекции - метод Remove.
    По сравнению с другими рассмотренными ранее объектными моделями, модель объектов SQL-DMO имеет особый тип коллекции, - список (list), доступный только для чтения. Возможности получения списков весьма широки и охватывают почти все объекты DMO, но необходимо учитывать, что в списке вы получите данные, фиксирующие текущее состояние системы и никак не изменяющиеся при дальнейших действиях.
    Для того чтобы начать работу с SQL сервером посредством DMO, если вы используете Access или Visual Basic, необходимо создать объект SQLServer:

Global MyServer As New SQLOLE.SQLServer

    Если вы используете Visual FoxPro, то необходимо воспользоваться функцией CREATEOBJECT():

oMyServer = CREATEOBJECT("SQLOLE.SQLServer")

    В данный момент этот объект представляет собой абстрактную ссылку. Для придания ему конкретности необходимо подсоединиться к существующему серверу, естественно, если вам как пользователю даны права или вы знаете пароль администратора.
    Вы можете сделать это, использовав метод Connect объекта SQLServer, например, следующим образом:

oMyServer.Connect(Servername:="Autoserver",_ Login="Me",Password="You")

    Можно пойти более сложным путем, но при этом вы будете знать причину возникновения возможной ошибки. Используйте следующую или подобную ей функцию:

Function SQLLogin(strServerName As String, _strUID As String, strPWD As String) As Boolean
' Подключение к указанному SQL серверу, используя
' предложенный идентификатор пользователя и пароль.
' Устанавливает глобальный объект типа SQLServer
' Возвращает истину при удачном завершении,
' в противном случае - ложь.
On Error GoTo SQLLoginErr
     Const erSQLAlreadyLoggedIn = -2147211004
     SQLLogin = True
     DoCmd.Hourglass True
     mySQLServer.Connect strServerName, strUID, _strPWD
SQLLoginExit:
     DoCmd.Hourglass False
     Exit Function
     SQLLoginErr:
     Select Case Err.Number
Case erSQLAlreadyLoggedIn
     SQLLogin = True
Resume Next
Case Else
MsgBox "Error " & Err.Number & ": " & _
     Err.Description, vbCritical, "SQLLogin()"
     End Select
     SQLLogin = False
     Resume SQLLoginExit
End Function

    В простейших случаях можно безболезненно пользоваться первой конструкцией. Необходимо помнить, что после завершения работы программы с использованием SQL-DMO вам обязательно надо отсоединиться от сервера. Для этого используйте метод Disconnect объекта SQLServer:

oMySqlServer.Disconnect

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

Function SQLMakeNewDatabase(strName As String, _strDeviceName As String, lngSize As Long)
' Создаем новую БД на текущем SQL Server
On Error GoTo SQLMakeNewDatabaseErr
     SQLMakeNewDatabase = True
     Dim objNewDatabase As New SQLOLE.Database
     If Not gobjSQLServer Is Nothing Then
DoCmd.Hourglass True
With objNewDatabase
.Name = strName
End With
' БД будет располагаться на устройстве по
' умолчанию
gobjSQLServer.Databases.Add objNewDatabase
' Увеличиваем размер указанного устройства
With objNewDatabase
.ExtendOnDevices (strDeviceName & "=" &_ CStr(lngSize))
.Shrink (lngSize)
End With
     Else
SQLMakeNewDatabase = False
     End If
SQLMakeNewDatabaseExit:
     DoCmd.Hourglass False
     Exit Function
SQLMakeNewDatabaseErr:
     MsgBox "Error " & Err.Number & ": " & _Err.Description, _vbCritical, "SQLMakeNewDatabase()"
     SQLMakeNewDatabase = False
     Resume SQLMakeNewDatabaseExit
End Function

    Следующий фрагмент кода показывает, как можно добавить новую колонку во все существующие таблицы.

Dim oTable As Table
Dim oCol As Column
Set db = ss.Databases("pubs")
For Each oTable In db.Tables
     ' Выполняем для каждой таблицы в коллекции
     Set oCol = New Column
     oCol.AllowNulls = True
     oCol.Datatype = "varchar"
     oCol.Length = 30
     oCol.Name = "NewCol"
     oTable.BeginAlter ' Начинаем процесс изменения
     oTable.InsertColumn oCol, ""
     oTable.DoAlter ' Завершаем процесс изменения
Next oTable

    Вероятно, вы заметили, что перед добавлением колонки мы применяем метод BeginAlter, а для выполнения всех изменений необходимо воспользоваться методом DoAlter. При этом на сервере реально будут совершены все действия, записанные после применения метода BeginAlter. Этот процесс напоминает транзакции и должен использоваться при выполнении действий со следующими объектами:

  • Alert
  • AlertSystem
  • Article
  • Executive
  • Operator
  • Publication
  • RemoteServer
  • SubscriberInfo
  • Subscription
  • Table
  • Task

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

Глава 7 || Содержание || Глава 9