Использование библиотеки ADO (Microsoft ActiveX Data Object). Провайдер данных Oledb

22.04.2019 Мобильный интернет

1. Введение

Очевидно, что доступ к данным является важнейшим требованием при разработке современных бизнес-приложений. Технология ODBC обеспечивает доступ к реляционным базам данных и это первый шаг на пути решения этой проблемы. Однако, когда разработчики хотят включить в свои проекты нереляционные источники данных или работать в средах, подобных Интернет, они сталкиваются с дилеммой - либо разрабатывать собственные парадигмы доступа к данным, либо работать на уровне API, что несовместимо с новыми средами. ActiveX объекты доступа к данным (ADO) решают эту дилемму и обеспечивают единую модель, которая работает со всеми источниками данных в различных средах. Таким образом ADO обеспечивает последовательный, высокопроизводительный доступ к данным, с которыми вы можете создавать клиентские программы для работы с БД или бизнес-объекты среднего уровня, использующие приложения, инструментарий, язык или, даже, Интернет-смотрелку (естественно, Експлорер). ADO - это единый интерфейс доступа к данным, который вам необходим для создания одно- и многоуровневых приложений архитектуры клиент/сервер и Web-ориентированных информационных систем.

2. Обзор ADO

Технология ADO была впервые применена в Microsoft Internet Information Server как интерфейс доступа к БД. Использование ADO позволяет минимизировать сетевой траффик в ключевых Internet-сценариях и уменьшить количество промежуточных уровней между клиентским приложением и источником данных. ADO легко использовать, так как он (или они (объекты) или она (технология)) применяет привычную систему вызовов - интерфейс Автоматизации OLE, доступный сегодня в большинстве средств разработки приложений. Из-за легкости применения и изучения популярность ADO будет расти и в итоге ADO вытеснит технологии RDO и DAO, которые в настоящее время применяются очень широко. Технология ADO во многом подобна RDO и DAO, например, она использует те же соглашения языка. ADO также поддерживает аналогичную семантику и поэтому может быть легко освоена разработчиками ПО.
ADO является интерфейсом программного уровня к OLE DB, новейшей и мощнейшей (сильно сказано) парадигме доступа к данным от MS. OLE DB обеспечивает высокопроизводительный доступ ко многим источникам данных. ADO и OLE DB вместе представляют собой основу стратегии Универсального доступа к данным (Universal Data Access). OLE DB дает возможность универсального доступа ко многим данным и представляет разработчикам возможность сделать это достаточно легко. Так как ADO находится на вершине OLE DB, то применение ADO имеет все преемущества Универсального доступа к данным, которое обеспечивает OLE DB.

3. Обзор OLE DB

OLE DB - это открытая спецификация, разработанная на основе успеха спецификации ODBC и обеспечивает открытый стандарт доступа ко всем видам данным в системах масштаба предприятия. OLE DB - это ядро технологии поддерживающей Универсальный доступ к данным. В отличие от технологии ODBC, которая была создана для доступа к реляционным БД, технология OLE DB разработана для реляционных и нереляционных источников данных, таких как хранилища почты (mail stores), текстов и графики для Web, службы каталогов (directory services), IMS и VSAM хранилищ данных на мэйнфреймах.
Компоненты OLE DB состоят из провайдеров данных (data providers), которые представляют свои данные, потребителей данных (data consumers), которые используют данные, и сервисных компонент (service components), которые обрабатывают и транспортируют данные (например, процессор запросов и механизм курсоров). OLE DB включает в себя мост с ODBC, чтобы дать возможность разработчикам использовать ODBC-драйвера реляционных БД, широко распространенные в настоящее время.
OLE DB провайдеры
Существует два типа OLE DB приложений: потребители и провайдеры. Потребителями могут быть любые приложения, которые используют OLE DB интерфейсы. Например, Visual C++ приложение, которое использует OLE DB интерфейсы для связи с сервером БД - это OLE DB потребитель. Объектная модель ADO, которое использует OLE DB интерфейсы, - это тоже OLE DB потребитель. Любое приложение, которое использует ADO, косвенно использует OLE DB интерфейсы через объекты ADO.
OLE DB провайдер осуществляет OLE DB интерфейсы, поэтому, OLE DB провайдер дает возможность потребителям иметь доступ к данным единообразным способом через ряд документированных интерфейсов. В этом смысле OLE DB провайдер подобен ODBC драйверу, который обеспечивает универсальный механизм доступа к реляционным БД, но только для нереляционных типов данных. Более того, OLE DB провайдер встроен в вершину OLE COM интерфейсов, что придает ему большую гибкость, а ODBC драйвер встроен в вершину C API спецификации.
Microsoft OLE DB SDK version 1.1 поставляет два OLE DB провайдера: ODBC провайдер и провайдер текстов. Провайдер текстов служит примером, который демонстрирует подробную реализацию OLE DB провайдера. ODBC провайдер - это OLE DB провайдер для ODBC драйверов. Этот провайдер предоставляет механизм для потребителей, чтобы использовать существующие ODBC драйверы без необходимости срочной замены существующих ODBC драйверов на новые OLE DB провайдеры. Больше информации про OLE DB и OLE DB провайдеры можно посмотреть на https://www.microsoft.com/data в разделе OLE DB.
ODBC Провайдеры
ODBC провадер устанавливает соответствие между OLE DB интерфейсами и ODBC API. С ODBC провадером OLE DB потребители могут связываться с сервером БД через существующие ODBC драйверы. Потребитель вызывает OLE DB интерфейс через ODBC провайдера. ODBC провайдер вазывает соответствующие ODBC API иннтерфейсы и посылает запросы к ODBC драйверу. Целью разработки ODBC провайдера является осуществление всей функциональности менеджера ODBC драйвера. Поэтому теперь нет необходимости в менеджере ODBC драйвера. Однако при использовании ODBC провайдером версии 1.1 менеджер ODBC драйвера все еще требуется для поддержки связи с ODBC приложениями.

4. Объектная модель ADO

Объектная модель ADO определяет набор (коллекцию) программируемых объектов, которые могут использоваться с Visual Basic, Visual C++, VBScripting, Java на любой платформе, которая поддерживает COM и Автоматизацию OLE. Объектная модель ADO разработана для выполнения большинства особенностей OLE DB.
ADO содержит семь объектов:
* Connection
* Command
* Parameter
* Recordset
* Field
* Property
* Eror
и четыре набора объектов (коллекции):
* Fields
* Properties
* Parameters
* Errors

Коллекция Properties и объект Property доступны через объекты Connection, Recordset и Command. Коллекция Properties и объект Property содержат свойства, которые могут быть доступны только для чтения или для чтения-записи.
Объекты Connection, Recordset и Command являются ключевыми объектами в объектной модели ADO. ADO приложение может использовать объект Connection для установки соединения с сервером БД, объект Command - для выдаче команды к БД, таких как запросы, обновления и т.п. и объект Recordset - для просмотра и манипулирования данными. Командный язык, используемый с объектом Command, зависит от провайдера для БД. В случае реляционных баз данных в качестве командного языка выступает SQL.
Объект Command может не использоваться в случае, если OLE DB провайдер не обеспечивает выполнение интерфейса Icommand. Как как OLE DB провайдер может находиться на вершине реляционных или нереляционных БД, то традиционные SQL-операторы для запроса данных могут быть недоступны для нереляционной БД и поэтому объект Command не потребуется. Если объект Command содержит параметры, то информацию о них можно просмотреть или определить через набор объектов Parameters и объект Parameter. Объект Parameter описывает информацию о параметрах для объекта Command.
В ADO версий 1.0 и 1.5 все объекты могут быть созданы, за исключением объектов Error, Field и Property. К набору объектов Errors и объекту Error можно получить доступ через объект Connection после того как случилась ошибка провайдера. К набору объектов Fields и объекту Field можно получить доступ через объект Recordset после того как в объекте Recordset появятся какие-либо данные. Информация о метаданных объекта Recordset может быть просмотрена через набор объектов Fields и объект Field.

5. Объект Connection

Объект Connection позволяет установливать сеансы связи с источниками данных. Объект Connection обеспечивает механизм для инициализации и установления соединения, выполнения запросов и использования транкзаций.
Основной OLE DB провайдер, используемый для соединения, не ограничивает использование других ODBC провайдеров. Другие провайдеры так же могут использоваться для соединения. Провайдер определяется посредством установки свойства Provider. Если это свойство не определено, то по умолчанию будет использован провайдер MSDASQL.
Метод Open объекта Connection используется для установки соединения. С ODBC провайдером, приложение ADO может использовать механизм ODBC соединения для подключения к серверу БД. ODBC позволяет приложениям устанавливать соединение через источники данных ODBC или явно определять источник данных (DSN-Less connection).
Перед соединением приложение может установить строку соединения, тайм-аут соединения, БД по умолчанию и атрибуты соединения. Объект Connection так же позволяет установить свойство CommandTimeout для всех командных объектов, связвнных с данным соединением. Запросы могу выполняться с использованием метода Execute.
Через объект Connection можно управлять транкзациями. Для этого у него есть методы BeginTrans, CommitTrans и RollbackTrans.
Следующий пример показывает использование ODBC провайдера - по умолчанию OLE DB провайдера в ADO, для подсоединения к SQL Server:

Cn.ConnectionTimeout = 100
" DSN connection
#Cn.Open "pubs", "sa"
" DSN-Less connection for SQL Server
" Cn.Open "Driver={SQL Server};Server=Server1;Uid=sa;Pwd=;Database=pubs"
Cn.Close

В примере сначала устанавливается тайм-аут в 100 сек, затем открываетсясоединение используя ODBC источник данных - pubs, который указывает на SQL Server. Для SQL Server требуется указать идентификатор пользователя (user ID), поэтому sa - это второй параметр метода Open. Пароля нет, поэтому и третьего параметра тоже нет.
Этот пример так же содержит закоментированную строку, которая показывает как присоединиться к SQL Server без источника данных ODBC. Для связи с SQL Server, называемом Server1, используется ODBC драйвер для SQL Server {SQL Server}. Идентификатор пользователя - sa и пароля нет. БД по умолчанию для этого соединения - pubs.
Следующий пример показывает использование свойства Provider для определения альтернативного OLE DB провайдера.
Dim Cn As New ADODB.Connection
Dim rs As ADODB.Recordset
Cn.Provider = "sampprov"
Cn.Open "data source=c:\sdks\oledbsdk\samples\sampclnt\"
Set rs = Cn.Execute("customer.csv")

В этом примере определяется OLE DB провайдер - sampprov. Вместе с ODBC провайдером OLE DB SDK поставляется с текстовым провайдером. Провайдер текста позволяет приложению получать данные из текстового файла. В этом примере соединение устанавливается определением каталога источника данных c:\sdks\oledbsdk\samples\sampleclnt\ и данными в файле customer.csv, получаемыми в результате выполнения метода Execute.
Следующий пример демонстрирует использование методов BeginTrans, CommitTrans и RollbackTrans:
Dim Cn As New ADODB.Connection

" Open connection.
Cn.Open "pubs", "sa"
" Open titles table.
rs.Open "Select * From titles", Cn, adOpenDynamic, adLockPessimistic
Cn.BeginTrans
"
Cn.CommitTrans
" or rollback
" cn.RollbackTrans
Cn.Close

После установки соединения этот пример начинает транкзацию. Измененние данных в этой транкзации может быть подтверждено или произведен откат.
Набор объектов Errors объект Error
Набор объектов Errors и объект Error позволяют получить информацию о случившейся ошибке провайдера. Ошибки могут генерироваться вызовами методов или свойств объектов Connection, Command или Recordset, но всегда получаются из объекта Connection. Набор объектов Errors не существует сам по себе. Он зависит от объекта Connection и ошибок, лежащих ниже ADO (OLE DB провайдеры, ODBC драйверы и источники данных), которые помещаются в набор объектов Errors. Недопустимые значения свойств или ADO интерфейсов не добавляют объекты Error в набор объектов Errors.
В наборе объектов Errors также запоминаются предупреждения (warnings). Предупреждения, в отличие от ошибок, не вызывают остановки выполнения кода. Объект Error позволяет получить описание и источник ошибки. При работе с ODBC провайдером из Error так же доступна информация об SQLSTATE и собственных ошибках БД (database-native error).
Следующий пример показывает получение SQLSTATE, информации о собственной ошибке и описание ошибки при использовании ODBC провайдера.
Dim Cn As New ADODB.Connection
Dim Errs1 As ADODB.Errors
Dim rs As New ADODB.Recordset
Dim i As Integer
Dim StrTmp
On Error GoTo AdoError
Cn.Open "pubs", "sa"
Set rs = Cn.Execute("Select * From TableDoesnotExist")
Done:
" Close all open objects.
Cn.Close
" Destroy anything not destroyed yet.
Set Cn = Nothing
" We"re outta here.
Exit Sub
AdoError:
Dim errLoop As Error
Dim strError As String

" each Error object.
Set Errs1 = Cn.Errors
For Each errLoop In Errs1
Debug.Print errLoop.SQLState


Next
GoTo Done
Соединение с SQL Server устанавливается через источник данных - pubs. Запрос select * from TableDoesnotExist выполняется с помощью метода Execute объекта Connection. Так как в запросе выполняется попытка получить записи из несуществующей таблицы, то возникает ошибка. После возникновения ошибки выполнение программы переходит на метку AdoError. Далее из объекта Connection получается набор объектов Errors и связывается с набором объектов Errs1. Затем в цикле в окно отладчика выводится информация об ошибке.

6. Объект Command

Объект Command позволяет передавать команды к БД. Эти команды могут готовить строки запросов и связянные с запросами параметры, но не ограничиваются только запросами. Командный язык и его особенности зависят от конкретного провайдера БД. Здесь содержится информация и примеры для ODBC провайдера от Microsoft, который поддерживает достаточно широкий диапазон реляционных БД. Более подробно про OLE DB и OLE DB провайдеров можно почитать на сайте https://www.microsoft.com/data в разделе OLE DB.
Объект Command может как открывать новое соединение, так и использовать уже существующее соединение для выполнения запросов, в зависимости от установки свойства ActiveConnection. Если свойство ActiveConnection установлено с ссылкой на объект Connection, то объект Command будет использовать существующее соединение из объекта Connection. Если свойство ActiveConnection определяется строкой соединения, то для объекта Command будет установлено новое соединение. Для однго объекта Connection могут использоваться несколько объектов Command.
Ваполнение запросов может генерировать набор записей, множественные наборы записей или не генерировать никаких записей. Например, выполнение запросов языка описания данных (data definition language - DDL) не генерирует наборов записей. Выполнение оператора SELECT может генерировать набор записей и выполнение пакета SELECT операторов или хранимой процедуры генерирует более чем один набор записей.
Строка запроса определяется свойством CommandText. Строка запроса может быть определена на стандартном языке манипулирования данными (data manipulation language - DML) с использованием SELECT, INSERT, DELETE, или UPDATE операторов или на языке описания данных, например CREATE или DROP. В строке запроса может также буть имя хранимой процедуры или таблицы.
Тип строки запроса определяется свойством CommandType. Значения свойства CommandType могут быть следующими: adCmdText, adCmdTable, adCmdStoreProc и adCmdUnknown. Когда строкой запроса является SQL оператор, свойство CommandType должно определяться как adCmdText. Значения adCmdStoreProc или adCmdTable применяются, если строка запроса определяется как хранимая процудура или имя таблицы.
Если определяется значение adCmdStoredProc, то объект Command выполняет строку запроса с синтаксисом {call procedure=name}. Если определяется значение adCmdTable, то объект Command выполняет строку запроса с синтаксисом select * from tablename. Если определяется значение adCmdUnknown, то объект Command должен выполнить дополнительные действаия по определению типа запроса, что уменьшает производительность системы.
Вы также можете определить, будете или нет готовить строку запроса с помощью свойства Prepared. Установка свойства Prepared позволяет планировать запрос перед первым выполнением. Подготовленный таким образом запрос затем используется при последующих выполнениях для улучшения производительности. Строку запроса следует подготавливать только когда запрос будет выполняться более чем один раз, так как это потребует больше времени, чем на непосредственное выполнение запроса. Таким образом производительность может увеличиться только при выполнение такого запроса во второй, третий и т.д. раз.
Свойство Prepared также может быть полезным при неоднократном выполнении запросов с параметрами. Разные значения параметров будут подставляться при каждом выполнении запроса, вместо полной перестройки строки запроса. Объект Parameter может быть создан с помощью метода CreateParameter. Более подробно об этом написано в разделе "Использование подготовленных операторов".
В следующем примере показано выполнение оператора SELECT, которое возвращает объект Recordset.
Dim Cmd As New ADODB.Command
Dim rs As New ADODB.Recordset


Cmd.CommandText = "select * from titles"
Cmd.CommandTimeout = 15
Cmd.CommandType = adCmdText
Set rs = Cmd.Execute()
rs.Close
Свойство ActiveConnection определяется как строка ODBC соединения - DSN=pubs;UID=sa ODBC. Оператор select * from titles определяется в свойстве CommandText и тек как это SQL оператор, то свойство CommandType устанавливается в adCmdText. Затем устанавливается время ожидания в 15 сек. В результате выполнения запроса возвращается объект Recordset, который связывается с объектом rs.
Следующий пример показывает выполнение хранимой процедуры, которая не возвращает набор записей.
Синтаксис хранимой процедуры следующий:.
drop proc myADOProc
go
create proc myADOProc as
create table #tmpADO (id int not NULL, name char(10) NOT NULL)
insert into #tmpADO values(1, "test")

Хранимая процедура myADOProc создает временную таблицу tmpADO и вставляет строку в эту таблицу. Операторы CREATE и INSERT не генерируют результатов и поэтому при выполнение хранимой процедуры myADOProc не возвращается никаких результатов.
Visual Basic код, вызывающий процедуру myADOProc, имеет следующий вид:
Dim Cmd As New ADODB.Command
" Use a connection string or a Connection object.
Cmd.ActiveConnection = "DSN=pubs;UID=sa"
Cmd.CommandText = "myADOProc"
Cmd.CommandTimeout = 15

Cmd.Execute

Хранимая процедура myADOProc определяется свойством CommandText. Свойство CommandType устанавливается в adCmdStoredProc для определения типа выполняемого объекта и в результате генерируется оператор {call myADOProc}. Так как в результате выполнения хранимой процедуры не возвращается никаких данных, то и нет привязки результатов выполнения процедуры к объекту Recordset.
Набор объектов Parameters и объект Parameter
Набор объектов Parameters обеспечивает объект Command информацией о параметрах и данных. Набор объектов Parameters состоит из объектов Parameter. И набор объектов Parameters и объект Parameter требуются только в том случае, если строка запроса в объекте Command требует параметров. Информация об индивидуальных параметрах (например, размер, тип данных, направление и значение) может быть считана или записана для каждого объекта Parameter. Существует четыре типа параметров направления: входной (input), выходной (output), входной и выходной (input and output) и возвращенное значение (return value). Объект Parameter может служить как входной параметр, как выходной параметр, который содержит данные, и как возвращенное значение хранимой процедуры. Применение метода Refresh коллекции параметров может заставить провайдера обновить информацию о параметрах, однако эта процедура потребует дополнительного времени.
При работе с значительными по размеру типами данных для записи части данным можно использовать метод AppendChunk. Дополнительная информация об этом содержится в разделе "Использование больших типов данных"
Следующий пример демонстрирует создание параметров для хранимой процедуры. Хранимая процедура имеет следующий синтаксис:
drop proc myADOParaProc
go
create proc myADOParaProc
@type char(12)
as
select * from titles where type = @type

Процедура myADOParaProc берет один @type входной параметр и возвращает данные, которые соответствуют определенному типу. Типом данных для параметра @type является тип character, который имеет длину 12.
Код Visual Basic, вызывающий эту хранимую процедуру, имеет следующий вид:
Dim cmd As New ADODB.Command
Dim rs As New ADODB.Recordset
Dim prm As ADODB.Parameter
" Define a Command object for a stored procedure.
cmd.ActiveConnection = "DSN=pubs;uid=sa"
cmd.CommandText = "myADOParaProc"
cmd.CommandType = adCmdStoredProc
cmd.CommandTimeout = 15
" Set up new parameter for the stored procedure.
Set prm = Cmd.CreateParameter("Type", adChar, adParamInput, 12, "Business")
Cmd.Parameters.Append prm
" Create a record set by executing the command.
Set rs = Cmd.Execute
While (Not rs.EOF)
Debug.Print rs(0)
rs.MoveNext
Wend

Свойства ActiveConnection, CommandText, CommandType, и CommandTimeout определяются так же, как и в предыдущем примере. Процедура myADOParaPro ожидает входного параметра, который имеет тип character и размер 12. Метод CreateParameter используется для создания объекта Parameter с соответствующими характеристиками: тип данных - adChar для символьных данных, тип параметра - adParamInput для входного параметра и длина - 12. Объект Parameter так же определяется именем Type и так как это входной параметр, то определяеся значение Business. После того, как параметр определен, с помощью метода Append он добавляется к набору объектов Parameters. Затем выполняется хранимая процедура и результат возвращается в объект Recordset.

7. Объект Recordset

Объект Recordset обеспечивает методы для манипулирования наборами данных. Объект Recordset позволяет добавлять, удалять, обновлять записи и перемещаться по записям внаборе данных. С помощью набора объектов Fields и объекта Field можно получить доступ к любой конкретной записи. Обновление объекта Recordset может быть сделано немедленно или в пакетном режиме. При создании объекта Recordset автоматически открывается курсор.
Объект Recordset позволяет определить тип курсора иего расположение для выбора результирующего набора данных. Используя свойство CursorType можно определить тип курсора: forward-only, static, keyset-driven, или dynamic. Тип курсора определяет поведение объекта Recordset при прокрутке записей вперед/назад или при обновлении записей. Тип курсора также влияет на видимость измененных записей.
По умолчанию тип курсора устанавливается в forward-only только для чтения. Если необходимо только читать данные в однонаправленном режиме (forward), то изменять тип курсора не следует. С другой стороны, можно определить тип курсора в зависимости от решаемой задачи.
С помощью свойства CursorLocation можно определить будет ли курсор серверным или клиентским. Расположение курсора играет большую роль при использовании несвязанных наборов записей. Подробнее об этом написано в разделе "Использование серверных курсоров". Объект Recordset может быть создан при выполнении метода Execute объекта Connection или Command.
Следующий пример показывает использование объекта для открытия соединения и получения результирующего набора данных:
Dim rs As New ADODB.Recordset

While (Not rs.EOF)
Debug.Print rs(0)
rs.MoveNext
Wend
rs.Close

В этом примере открывается соединение, создается набор записей, затем в цикле печатается содержимое первого поля каждой строки набора записей.
Набор объектов Fields и объект Field
Набор объектов Fields и объект Field позволяют получить доступ к данным любой колонки в текущей записи. К набору объектов Fields можно получить доступ через объект Recordset. К объекту Field можно получить доступ через набор объектов Fields используя индекс. Объект Field можно использовать для составления новой записи или для изменения уже существующих данных и затем использовать методы AddNew, Update или UpdateBatch объекта Recordset для вставки новых или изменения существующих данных.
В отличи от RDO, в ADO нет метода Edit. Обновление объекта Field приводит к изменению данных и поэтому не требуется явных методов для редактирования данных.
Следующий пример показывает использование объекта Field для возвращения имени, типа и значения каждого поля данных в текущей записи:
Dim rs As New ADODB.Recordset
Dim fld As ADODB.Field
rs.Open "select * from titles", "DSN=pubs;UID=sa"
Set Flds = rs.Fields
Dim TotalCount As Integer
TotalCount = Flds.Count
i = 0
For Each fld In Flds
Debug.Print fld.Name
Debug.Print fld.Type
Debug.Print fld.Value
Next
rs.Close

После создания объекта Recordset в результате выполнения запроса select * from titles, возвращается набор объектов Fields. Затем в цикле для каждого объекта Field из набора объектов Fields выводятся на печать свойства Name, Type и Value.

Набор объектов Properties и объект Property
Набор объектов Properties и объект Property обеспечивают информацию о параметрах объектов Connection, Command, Recordset и Field. К набору объектов Properties можно получить доступ через объекты Connection, Command, Recordset и Field. К объекту Property можно получить доступ через набор объектов Properties используя индекс.
Набор объектов Properties состоит из объектов Property. Кроме значения и типа свойства объект Property позволяет получить доступ и к атрибутам свойства. Атрибуты описывают такие вещи, как будет или нет поддерживаться определенное свойство объекта или оно может быть доступно только для чтения или записи. Например, свойство ConnectionTimeout обеспечивает данные о том, сколько секунд будет длиться ожидание при установке соединения перед тем как будет сгенерирована ошибка завершения времени.
Следующий пример демонстрирует возвращение свойств ConnectionTimeout, CommandTimeout и Updatability:
Dim Cn As New ADODB.Connection
Dim Cmd As New ADODB.Command
Dim rs As New ADODB.Recordset
Cn.Open "pubs", "sa"
" Find out ConnectionTimeout property.
Debug.Print Cn.Properties("Connect Timeout")

Cmd.CommandText = "titles"
Cmd.CommandType = adCmdTable
Set rs = Cmd.Execute()
" find out CommandTimeout property.
Debug.Print Cmd.Properties("Command Time out")
Debug.Print rs.Properties("Updatability")

В этом примере набор объектов Properties возвращается из объектов Connection, Command и Recordset. Свойство ConnectionTimeout объекта Connection выводится на печать. Затем аналогичные шаги выполняются для объектов Command и Recordset.

8. Продвинутые возможности ADO

8.1. Использование языка определения данных (Data Definition Language - DDL)
Язык определения данных - это такие SQL операторы, которые поддерживают определения или объявления объектов БД, например CREATE TABLE, DROP TABLE или ALTER TABLE. Выполнение запросов DDL не генерирует никаких данных и, поэтому, нет необходимости использовать объект Recordset. Для выполнения запросов DDL идеальным является объект Command. Чтобы отличить запросы DDL от имен хранимых процедур или таблиц, свойство CommandType объекта Command должно быть установлено в adCmdText.
SQL Server обеспечивает ряд опций выполнения запросов, которые могут быть установлены оператором SET. Эти SET опции не генерируют никаких результирующих наборов данных и, поэтому они могут рассматриваться в обном ряду с запросами DDL.
Следующий пример показывает использование объекта Command для отключения опции SET NOCOUNT оператора SET SQL Server Transact-SQL - SET:
Dim Cn As New ADODB.Connection
Dim Cmd As New ADODB.Command
" If the ADOTestTable does not exist
On Error GoTo AdoError
Cn.Open "pubs", "sa"
Set Cmd.ActiveConnection = Cn
Cmd.CommandText = "drop table ADOTestTable"
Cmd.CommandType = adCmdText
Cmd.Execute
Done:
Cmd.CommandText = "set nocount on"
Cmd.Execute
Cmd.CommandText = "create table ADOTestTable (id int, name char(100))"
Cmd.Execute
Cmd.CommandText = "insert into ADOTestTable values(1, "Jane Doe")"
Cmd.Execute
Cn.Close
Exit Sub
AdoError:
Dim errLoop As Error
Dim strError As String
" Enumerate Errors collection and display properties of
" each Error object.
Set Errs1 = Cn.Errors
For Each errLoop In Errs1
Debug.Print errLoop.SQLState
Debug.Print errLoop.NativeError
Debug.Print errLoop.Description
Next
GoTo Done
End Sub
Этот пример сбрасывает таблицу, открывает таблицу и вставляет данные в таблицу с использованием метода Execute объекта Command. Для этого типа запросов не создаются объекты Recordset. Таблица ADOTestTable может не существовать в БД и поэтому выполнение drop table ADOTestTable может генерировать ошибку, которая покажет, что таблица не существует. Для обработки такой ситуации предусмотрена обработка ошибок. Затем выполняется установка опции set - set nocount on.
8.2. Использование подготовленных (Prepared) операторов
Запросы могут быть подготовлены перед их выполнением или могут быть непосредственно выполнены. Свойство Prepared объекта Command позволяет определить будет запрос подготавливаться или нет.
Если свойство Prepared установлено в TRUE, то строка запроса будет подвергнута разбору и оптимизации при первом выполнении. При любм последующем выполнении этого запроса будет использоваться "откомпилированная" версия запроса. Это потребует некоторого времени при первом выполнении запроса, но зато потом, при последующих выполнениях, должен наблюдаться заметный рост производительности. Если предполагается, что запрос будет выполняться один раз, то он должен быть выполнен без предварительной подготовки.
Свойство Prepared может также использоваться при выполнении запросов со многими параметрами. Приложение может выполнять запросы с параметрами более чкм один раз с подстановкой разных наборов параметров при каждом выполнении запроса вместо полной перестройки строки запроса всякий раз при изменении параметров. Однако, если запрос с параметрами выполняется один раз, то нет необходимости в его предварительной подготовке.
SQL Server не поддерживает непосредственно модель Prepare/Execute для ODBC. Когда оператор подготавливается, ODBC драйвер SQL server создает временную хранимую процедуру для этого оператора. Эта временная хранимая процедура существует в tempdb и не сбрасывается до тех пор, пока не закроются объекты Recordset или Connection.
Эта опция может быть отключена в диалоге SQL Server ODBC Data Source Setup если для подсоединения к SQL Server используется источник данных ODBC. Если эта опция отключена, то каждый раз при выполнении запроса SQL оператор сначала запоминается и затем посылается к серверу на выполнение.
Следующий пример показывает использование подготовленного оператора для обновления запроса и динамической сборке запроса с различными наборами параметров во время выполнения:
Dim Cn As New ADODB.Connection
Dim Cmd As New ADODB.Command
Dim prm1 As New ADODB.Parameter
Dim prm2 As New ADODB.Parameter
Cn.Open "DSN=pubs", "sa"
Set Cmd.ActiveConnection = Cn
Cmd.CommandText = "update titles set type=? where title_id=?"
Cmd.CommandType = adCmdText
Cmd.Prepared = True
Set prm1 = Cmd.CreateParameter("Type", adChar, adParamInput, 12, "New Bus")
Cmd.Parameters.Append prm1
Set prm2 = Cmd.CreateParameter("Title_id", adChar, adParamInput, 6, "BU7832")
Cmd.Parameters.Append prm2
Cmd.Execute
Cmd("Type") = "New Cook"
Cmd("title_id") = "TC7777"
Cmd.Execute
Cmd("Type") = "Cook"
Cmd("title_id") = "TC7778"
Cmd.Execute
Cn.Close
Этот пример обновляетданные в таблице titles с использованием разных значений параметров. Строка запроса подготавливается так, чтобы при его выполнении могли применяться разные наборы параметров. Для операции обновления требуются два параметра: type и title_id. Они создаются с помощью метода CreateParameters и добавляются к набору объектов Parameters с помощью метода Append.
Первый набор параметров имеет значения New Bus и BU7832. Другие значения параметров могут использоваться перед выполнением метода Execute без перестройки строки запроса, так как свойство Prepared установлено в TRUE.
8.3. Выполнение хранимых процедур
Выполнение хранимых процедур очень похоже на выполнение подготовленных запросов, за исключением того, что хранимая процедура существует как объект в БД даже когда выполнение запроса закончено. Хранимая процедура может также использоваться для сокрытия из приложения сложных SQL операторов.
Когда в объекте Command выполняется хранимая процедура, свойство CommandType должно быть определено как adCmdStoredProc. При таком определениии свойства CommandType генерируется соответствующий оператор SQL для основного провайдера. Для ODBC провайдера генерируются ODBC escape-последовательности для вызовов процедур {[?=]call procedure-name[([,]...)]} и ODBC драйвер SQL Server оптимизирует запрос, чтобы использовать все преемущества этих последовательностей.
Следующий пример показывает выполнение хранимой процедуры sp_who:
Dim Cmd As New ADODB.Command
Dim rs As New ADODB.Recordset
Cmd.ActiveConnection = "DSN=pubs;uid=sa"
Cmd.CommandText = "sp_who"
Cmd.CommandType = adCmdStoredProc
Set rs = Cmd.Execute()
Debug.Print rs(0)
rs.Close
Для оптимальной производительности приложение никогда не должно подготавливать хранимые процедуры SQL Server. Иначе возникнут дополнительные накладные расходы при создании временных хранимых процедур.

Коды возврата и выходные параметры хранимых процедур
Хранимые процедуры могут содержать входные и выходные параметры и возвращать значения. Например, следующая хранимая процедура myProc содержит выходной параметр @ioparm и возвращает значение 99.
CREATE PROCEDURE myProc @ioparm int OUTPUT AS
SELECT name FROM sysusers WHERE uid SELECT @ioparm = 88
RETURN 99
Входной параметр для хранимой процедуры можно определить через объект Parameter. Выходной параметр и возвращенное значение могут также быть определены через объект Parameter, но действительные значения обоих этих параметров не будут возвращены до тех пор, пока объект Recordset не будет полностью выбран (в смысле выборки данных) или пока объект Recordset не будет закрыт.
ADO следующий:
Dim Cmd As New ADODB.Command
Dim rs As New ADODB.Recordset
Dim param As Parameter
Cmd.ActiveConnection = "DSN=pubs;UID=sa"
Cmd.CommandText = "myproc"
Cmd.CommandType = adCmdStoredProc
" Set up parameters.
Set param = Cmd.CreateParameter("Return", adInteger, adParamReturnValue, 0)
Cmd.Parameters.Append param
Set param = Cmd.CreateParameter("Output", adInteger, adParamOutput, 0)
Cmd.Parameters.Append param
Set rs = Cmd.Execute
If Not rs.EOF And Not rs.BOF Then
Debug.Print rs(0)
rs.Close
End If
Debug.Print Cmd(0) " The return code
Debug.Print Cmd(1) " The Output parameter
Для хранимой процедуры myProc требуются два параметра: выходной параметр для сохранения возвращенного значения и выходной параметр @ioparam. В этом примере сначала создаются два параметра: Return и Output. Параметр Return создается как тип adParamReturnValue и имеет тип данных - adInteger, что соответствует integer. Параметр Output - adParamReturnValue для выходного параметра и тоже имеет тип данных integer. Так как оба параметра имеют тип integer, то нет необходимости определять длину данных.
После добавления параметров и выполнения запроса создается набор записей. Затем объект Recordset закрывается, чтобы получить код возврата и выходной параметр.
8.4. Использование пакетного обновления (Batch Updating)
Метод Update объекта Recordset позволяет обновить текущую запись. Метод UpdateBatch позволяет применить все ожидающие новые, обновленные и удаленные записи к объекту Recordset. Используя LockType adLockBatchOptimistic, метод UpdateBatch позволяет выполнить все изменения, находящиеся в состоянии ожидания, на клиентском компьютере и одновременно послать все эти изменения серверу БД. Изменения, находящиеся в состоянии ожидания, могут быть отменены с помощью метода CancelBatch.
Только если все изменения в БД при выполнении метода UpdateBatch будут неудачными, будет возвращена ошибка. Если же только некоторые изменения будут неудачными, то будет возвращено предупреждение.
В SQL Server метод UpdateBatch является допустимым только, когда свойство LockType установлено в adLockBatchOptimistic и тип курсора - либо keyset-driven, либо static. Курсор типа keyset-driven может открываться с таблицами, имеющими уникальные индексы.
Следующий пример демонстрирует использование метода UpdateBatch:
Dim rs As New ADODB.Recordset
rs.CursorType = adOpenKeyset
rs.LockType = adLockBatchOptimistic
rs.Open "select * from titles", "DSN=pubs;uid=sa"
" Change the type for a specified title.
While (Not rs.EOF)
If Trim(rs("Type")) = "trad_cook" Then
rs("Type") = "Cook"
End If
rs.MoveNext
Wend
rs.UpdateBatch
rs.Close
В этом примере сохдается набор записей используя курсор типа keyset-driven со свойством LockType установленным в adLockBatchOptimistic. После создания объекта Recordset, тип trad-cook изменяется на Cook, новый тип для всех записей в таблице title. После выполнения всех изменений, сделанные изменения данных подтверждаются применением метода UpdateBatch.
8.5. Генерация нескольких наборов записей
Вместо выполнения одного запроса несколько раз, SQL Server позволяет выполнять пакеты запросов. В результате выполнении пакета запросов может генерироваться более чем один набор записей. Кроме пакетных запросов, множественные наборы записей могут также генерироваться SQL операторами, включающими предложения COMPUTE BY и COMPUTE, или хранимыми процедурами, которые содержат более одного оператора SELECT.
Когда генерируются множественные наборы записей, важно иметь возможность последовательно выбирать наборы записей до тех пор, пока наборы записей являются доступными. Метод NextRecordset объекта Recordset позволяет выбрать любые последующие наборы записей. Если нет больше доступных наборов записей, то возвращенный объект Recordset будет установлен в Nothing.
Следующий пример показывает использование метода NextRecordset для выборки нескольких наборов записей.
Синтаксис хранимой процедуры следующий:
drop proc myNextproc
go
create proc myNextproc as
select * from titles
select * from publishers
Эта хранимая процедура генерирует два набора записей: один - в результате выполнения запроса select * from titles, другой - в результате выполнения запроса select * from publishers.
ADO код имеет следующий вид:
Dim cmd As New ADODB.Command
Dim rs As ADODB.Recordset
Cmd.ActiveConnection = "DSN=pubs;UID=sa"
Cmd.CommandText = "myNextProc"
Cmd.CommandType = adCmdStoredProc
Set rs = Cmd.Execute()
While Not rs Is Nothing
If (Not rs.EOF) Then
Debug.Print rs(0)
End If
Set rs = rs.NextRecordset()
Wend
После выполнения хранимой процедуры myNextProc создается объект Recordset. Так как в результате выполнения процедуры myNextProc создается два набора записей, каждые объект Recordset может быть получен при помощи метода NextRecordset.
8.6. Использование серверных курсоров
SQL Server обеспечивает целый ряд типов серверных курсоров для использования в приложениях. По умолчанию ADO приложение не использует серверных курсоров когда вместе с SQL Server используется ODBC провайдер. По умолчанию ADO приложение использует несерверный курсор типа forward и только для чтения.
Серверные курсоры полезны при обновлении, вставке или удалении записей. Серверные курсоры также позволяют иметь множественные активные операторы (active statements) при одном соединении. SQL Server намеренно не позволяет множественных активных операторов на соединение, пока используются серверные курсоры. Активный оператор предназначен для того, чтобы в обработке оператора существовало несколько незавершенных результатов (pending results). Если серверные курсоры не используются и приложение пытается иметь более одного активного оператора, то приложение получит сообщение об ошибке "Connection Busy with Another Active Statement".
Чтобы в приложении использовать серверный курсор необходимо сделать следующее:
* Установить тип курсора отличный от устанавливаемого по умолчанию при помощи метода RecordsetOpen. По умолчанию тип курсора устанавливается в adOpenForwardOnly и изменение типа на adOpenKeyset, adOpenDynamic или adOpenStatic приведет к использованию серверного курсора.
* Установить LockType отличный от устанавливаемого по умолчанию при помощи метода RecordsetOpen. По умолчанию LockType установлен в adLockReadOnly и любое изменение его на adLockPessimistic, adLockOptimistic или adLockBatchOptimistic приведет к использованию серверного курсора.
* Установить значение свойства CacheSize на любое другое, отличное от устанавливаемого по умолчанию (по умолчанию устанавливается 1).
Серверные курсоры:
* Создаются только для операторов, начинающахся с SELECT, EXEC procedure_name или {call procedures_name}. Даже если приложение явно потребует создания серверного курсора, серверный курсор не будет создан для операторов таких как INSERT.
* Не могут использоваться с операторами, которые генерируют белее одного набора записей. Это ограниечение применимо ко всем операторам, описанным в разделе "Генерация множественных наборов записей". Если серверный курсор используется с любым оператором, генерирующим множественные наборы записей, то приложение может получить следующие ошибки:
* "Cannot open a cursor on a stored procedure that has anything other than a single select statement in it."
* "sp_cursoropen. The statement parameter can only be a single select or a single stored procedure."
Следующий пример демонстрирует открытие динамического (dynamic) серверного курсора:
Dim rs As New ADODB.Recordset
rs.Open "select * from titles", "DSN=pubs;UID=sa", adOpenDynamic, adLockOptimistic
rs.Close
8.7. Использование объемных типов данных
Объемными типами данных в SQL server являются типы text и image. Тексты и рисунки иногда могут поместиться в памяти, но они также могут оказаться такими большими, что не смогут быть возвращены за одну операцию или целиком разместиться в памяти. Если объемные данные помещаются в памяти, то для получения всех данных за одну операцию может использоваться свойство Value объекта Field. Если данные слишком велики, чтобы поместиться в памяти, то операции с такими данными должны проводиться по частям. Существует два способа манипулирования данными большого размера. Первый - через объект Field и второй - через объект Parameter. Оба объекта Field и Parameter поддерживают метод AppendChunk, а объект Field, кроме того, обеспечивает метод GetChunk для операций с данными большого размера.
Объект Field позволяет записать или прочитать данные через объект Recordset. Метод AppendChunk объекта Field позволяет добавить данные к концу текущих данных когда запрос уже выполнен. Метод GetChunk позволяет читать данные по частям.
Объект Parameter обрабатывает данные большого размера подобным образом. Только у объекта Parameter нет метода GetChunk и нет объекта Recordset при работе с данными большого размера в режиме выполнения. С объектом Parameter данные большого размера свянаны в режиме выполнения и выполняются с объектом Command.
Существует несколько ограничений при работе с данными большого размера при использовании ODBC провайдера. Если не используется серверный курсор, то все колонки с объемными данными должны располагаться справа от всех остальных колонок (с обычными данными). Если есть несколько колонок с данными большого размера, то доступ к ним должен производиться в порядке слева-направо.
Следующий пример демонстрирует использование методов AppendChunk и GetChunk для чтения и записи данных большого размера:
Структура таблицы-приемника следующая:
drop table myBLOB
go
create table myBLOB(id int unique, info text)
go
insert into myBLOB values(1, "test")
go
Таблица myBLOB - это таблица-приемник, в которую будут вставлены данные большого размера:
ADO код имеет следующий вид:
Dim Cn As New ADODB.Connection
Dim rsRead As New ADODB.Recordset
Dim rsWrite As New ADODB.Recordset
Dim strChunk As String
Dim Offset As Long
Dim Totalsize As Long
Dim ChunkSize As Long
Cn.Open "pubs", "sa"
rsRead.CursorType = adOpenStatic
rsRead.Open "select pr_info from pub_info", Cn
rsWrite.CursorType = adOpenKeyset
rsWrite.LockType = adLockBatchOptimistic
rsWrite.Open "select * from myBLOB", Cn
ChunkSize = 1000
Totalsize = rsRead("pr_info").ActualSize
Do While Offset strChunk = rsRead("pr_info").GetChunk(ChunkSize)
Offset = Offset + ChunkSize
rsWrite("info").AppendChunk strChunk
Loop
rsWrite.UpdateBatch
rsWrite.Close
rsRead.Close
End Sub
В этом примере данные pr_info считываются из таблицы pub_info и вставляются в таблицу myBLOB. После создания наборов записей rsRead и rsWrite, размер данных запоминаетсяв переменной Totalsize. Затем в цикле WHILE данные вставляются частями по 1000 байт. Выход из цикла предусмотрен, когда размер вставленных данных превысит размер первоначальный размер данных. После вставки для подтверждения сделанных изменений применяется метод UpdateBatch.

9. Сервисы удаленных данных (Remote Data Services)

Remote Data Services (ранее называемые Advanced Data Connector) разработан, чтобы устранить статичность Web страниц. Традиционная Web технология доступа к БД позволяет получить данные с сервера БД в виде HTML страницы и после этого данные становятся статичными и ими нельзя манипулировать без установления нового соединения с сервером БД.
Remote Data Services устраняет это ограничение, предоставляя дистанционный набор записей на клиентском компьютере, которым можно манипулировать. Remote Data Services позволяет тспользовать набор записей ADO, полученный с удаленного сервера. Такой набор записей может размещаться на клиентском компьютере без продолжения активного соединения с сервером БД.
Remote Data Services интегрирован в модель объектов ADO. Advanced Data Connector, распространаемый с OLE DB SDK version 1.1, обеспечивает механизмы для несвязанных наборов записей (disconnected record sets) и кэширования на стороне клиента, что дает возможность создавать динамические Web приложения с доступом к БД.
Другим подходом, предлагаемым с Remote Data Services, является использование прикладных объектов или объектов промежуточного уровня (business objects). Remote Data Services также позволяет отделить business-правила от данных. Вместо того, чтобы выкладывать всю программную или business логику на Web страницу клиента, можно разместить всю программную или business логику в приккладном объекте.
Прикладные объекты (Business Object) и ADO
Прикладные объекты - это программные модули OLE DLL, которые можно создавать с помощью Visual Basic, Visual C++ или Microsoft Visual J++. Основная цель прикладных объектов - разделить логику приложения и бизнес-правила и защитить бизнес-правила и данные от редистрибьюции. Например, бизнес-правилом может быть правило для расчета цен на авиабилеты. Авиакомпания не хочет, чтобы заказчики знали о том, как расчитываются цены на авиабилеты, а также может захотеть изменять правила расчета цены в любое время. Такие бизнес-правила могут быть реализованы на удаленном компьютере и при необходимости они могут быть изменены без влияния на компьютеры клиентов.
Прикладные объекты отделяют бизнес-правила от клиентского компьютера. Прикладные объекты могут находиться на удаленном сервере в вызываться клиентским компьютером по мере необходимости. Бизнес-правила и данные могут быть надежно защищены только когда на клиентском компьютере находится приложение с необходимой программной логикой (пользовательским интерфейсом), а все бизнес-правила вместе с кодом соединения с БД находятся на удаленном компьютере.
Для создания прикладных объектов можно использовать по умолчанию объект AdvancedDataFactory. Этот объект обеспечивает только чтение и запись данных. Объектная модель ADO является другой альтернативой для выполнения прикладных объектов. Объекты ADO обеспечивают не только всестороннее манипулирование данными, но и механизм удаленного набора записей.
Разъединение набора записей в прикладном объекте
Перед удалением (в смысле не delete, а remote) набора записей, он должен быть в состоянии функционировать как объект Recordset без реального соединения с сервером БД. После размещения набора записей на клиентском компьютере, соединение с данными не может и не размещается с набором записей и, поэтому, набор записей должен быть отсоединен от сервера БД. Набор записей без реального соединения с сервером БД называется разъединенным набором записей (disconnected record set).
Разъединенный набор записей можно создать с использованием свойства CursorLocation объекта Recordset. Свойство CursorLocation позволяет определить будет ли курсор клиентским или серверным и, что более важно, плзволяет проводить изменения данных в пакетном режиме (batch mode). Если свойство CursorLocation определено как adUseClient, то любые обновления в разъединенном наборе записей будут сделаны в пакетном режиме и для кэширования набора записей будет использоваться клиентский курсор.
Следующий пример демонстрирует создание разъединенного набора записей:
Public Function GetData() As Object
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
cn.Open "dsn=testing;uid=sa;pwd=;"
rs.CursorLocation = adUseClient
rs.Open "select * from authors for browse", cn, adOpenUnspecified, adLockUnspecified, adCmdUnspecified
Set GetData = rs
End Function

Помещение удаленных изменений обратно в прикладной объект
После того, как клиентский компьютер получает разъединенный набор записей, он может использовать элементы управления ADC или наборы записей ADO для манипулирования данными. После внесения изменений в набор записей, клиентский компьютер может выбрать, какой способ будет использоваться для возвращения данных обратно: набор записей целиком или только измененные данные. Для этого служит свойство MarshalOptions набора записей ADO.
Следующий код может быть размещен на Web странице для возвращения набора записей от прикладного объекта и помещению набора записей обратно в прикладной объект:
Set rs = BusObj.GetData
Adc1.recordset=rs

Set rso = adc.recordset
rso.MarshalOptions=1
BusObj.SetData rso

Reconnecting and Applying Changes
После того, как обновленный набор данных передан от клиентского компьютера, прикладной объект может переустановить связь с набором данных и использовать метод UpdateBatch для обновления сделанных изменений в данных на сервере БД. Прикладной объект может переустановить связь с набором данных при помощи метода Open, как показано в следующем примере:
Public Function SetData(rso As Object)
Dim rs As New ADODB.Recordset
rs.Open rso, "dsn=pubs;uid=sa"
rs.UpdateBatch
End Function

Компоненты для работы с Microsoft ActiveX Data Objects (ADO) впервые появились в среде Delphi версии 5.

ADO - это технология стандартного обращения к реляционным структурам данных от Microsoft. Она аналогична BDE по назначению и сродни ему по возможностям.

В основе архитектуры ADO лежит объектная модель компонентов COM (Component Object Model). Все объекты и интерфейсы ADO представляют собой интерфейсы и объекты СОМ.

Модель СОМ является базовой для технологий ActiveX и OLE. Проиллюстрировать это отношение можно на примере объекта TObject, как базового объекта для VC L Delphi.

Технология СОМ работаете так называемыми СОМ-обьектами. Во многом СОМ-объекты похожи на обычные объекты визуальной библиотеки компонентов Delphi. Но, в отличие от последних, СОМ-объекты не содержат поля, в них находятся лишь свойства и методы, а также интерфейсы.

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

Обычный СОМ-объект включает в себя один или несколько интерфейсов. Кроме того, СОМ-объект содержит методы, которые позволяют приложению пользоваться им.

Технология СОМ имеет два явных плюса:

· создание СОМ-объектов не зависит от языка программирования. Таким образом, СОМ-объекты могут быть написаны на различных языках;

· СОМ-объекты пригодны для использования в любой среде программирования под Windows. В число этих сред входятDelphi, Visual С++, С++ Builder, Visual Basic и многие другие.

ПРИМЕЧАНИЕ: Хотя технология СОМ имеет очевидные достоинства, ей свойственны также и минусы, среди которых можно выделить зависимость от платформы. Эта технология применима только в операционной системе Windows и на платформе Intel.

Технология СОМ реализуется с помощью СОМ-библиотек (такие файлы операционной системы, как OLE32.DLL и OLEAUT32.DLL). СОМ-библиотеки содержат набор стандартных интерфейсов, которые отвечают за функциональность СОМ-объекта, а также небольшой набор функций API, обеспечивающих создание СОМ-объектов и управление ими.

В Delphi воплощение и поддержка технологии СОМ называется Delphi ActiveX framework, DAX. Реализация DAX описана в модуле AxCtrls.

Рассмотрим особенности архитектуры ADO (Рисунок 1).

Рисунок 1 - Архитектура ADO

Перечислим основные интерфейсы ADO и кратко поясним их назначение:

· интерфейс IConnection выполняет следующие функции:

· осуществляет связь с сервером;

· управляет транзакциями;

· получает информацию о произошедших ошибках (свойство Errors);

· получает информацию о схеме данных (таблицы, поля и т. д.);

· интерфейс IRecordset (на нижнем уровне ADO это IRowset) является аналогом TDataSet в Delphi и поддерживает текущее положение и перемещение курсора, закладки (Bookmarks), чтение, изменение и удаление записей и т. п.;

· интерфейс IReld позволяет получать значение поля, его тип, длину и другие сведения о поле данных;

· интерфейсы ICommand и IParameter обеспечивают работу с командами источника данных. Синтаксис команд для каждого из источников свой собственный;

· интерфейс IProperty позволяет получать и устанавливать параметры, специфические для провайдера данных.

Для работы с механизмом ADO в Delphi 7 предназначены семь стандартных компонентов, расположенных на закладке ADO палитры компонентов (рисунок 2).

Рисунок 2 – палитра компонентов ADO

Первый компонент называется ADOConnection. Функционально он аналогичен компоненту Database закладки BDE. С помощью компонента ADOConnection можно указывать местоположение базы данных и работать с транзакциями. Рассмотрим основные свойства компонента ADOConnection, отображаемые в окне инспектора объектов (Таблица 1).

Таблица 1 – Основные свойства ADOConnection

Следующий компонент закладки ADO называется ADOCommand. Он предназначен для выполнения SQL-команды без возврата результирующего набора данных (Таблица 2).

Таблица 2 - Основные свойства компонента ADOCommand

Третий компонент закладки ADO – ADODataSet. Его назначение - получение набора данных из одной или нескольких таблиц базы данных. Кроме того, он также позволяет работать с возвращенным набором данных визуальным компонентам, предназначенным для отображения данных. Это наиболее общий компонент для работы с набором данных, который может использоваться вместо компонентов ADOTable, ADOQuery или ADOStoredProc. В таблице 3 приведены основные свойства указанного компонента.

Таблица 3 - Основные свойства компонента ADODataSet

Следующий компонент закладки ADO называется ADOTable. Он является аналогом компонента Table, расположенного на закладке BDE, и предназначен для доступа к таблице с помощью механизма ADO. Компонент ADOTable имеет свойства, которые мы уже рассматривали ранее в компонентах Table и ADODataSet, поэтому мы не будем возвращаться к ним еще раз.

Компонент ADOQuery представляет собой запрос к базе данных. Это может быть как запрос, в результате которого возвращаются данные из базы (например, SELECT), так и запрос, не формирующий результирующего набора данных (например, INSERT). Компонент аналогичен компоненту Query из BDE. Все основные его свойства мы уже рассматривали в описании свойств компонентов Query н ADODataSet



Компонент ADOStoredProc предназначен для вызова процедуры, хранимой на сервере базы данных. В отличие от BDE и InterBase хранимые процедуры в ADO могут возвращать набор данных, поэтому компонент такого типа является потомком DataSet и может выступать источником данных в компонентах типа DataSource.

Последний компонент закладки ADO называется RDSConnection. Этот компонент управляет маршалингом данных, когда набор записей переносится из одного компьютера на другой.

ПРИМЕЧАНИЕ: Маршалингом данных называется механизм, который позволяет клиенту получать доступ к объектам, расположенным в другом адресном пространстве или на другом компьютере.

На события в ADO возлагаются те же задачи, что и на события в VCL. Многие из событий ADO имеют аналоги в VCL, и соответствующие компоненты вызывают из событий ADO события VCL.

Все события в ADO условно разделим на несколько групп: события соединения, события транзакции, события выполнения команд. События соединения:

· OnWillConnect - вызывается перед установкой соединения;

· OnConnectComplete - вызывается после установки соединения;

· OnDisconnect - активируется при разрыве соединения. Эти события инкапсулированы в компоненте ADOConnection.События транзакции:

· OnBeginTransComplete - происходят при выполнении BeginTrans;

· OnCommitTransComplete - происходят при выполнении CommitTrans;

· OnRollbackTransComplete - вызываются при выполнении RollbackTrans.

События данной группы инкапсулированы в компоненте ADOConnection. События выполнения команд OnWillExecute и OnExecuteComplete вызываются перед выполнением команды и после него соответственно.

Эти события «зашиты» в компоненте ADOConnection, а не в ADOCommand, как можно было бы предположить. Указанная «странность» связана с тем, что в ADO объекта команды как такового нет, и по этой причине он не может получать сообщения.

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

В ADO есть и события, связанные с набором данных, а не с соединением, как вышеописанные. Они инкапсулированы в компоненты, представляющие наборы данных: ADODataSet, ADOTable, ADOQuery и ADOStoredProc.

Эти события можно условно разбить на три группы.

События выборки данных.

· OnFetchProgress - многократно вызывается в процессе выборки данных;

· OnFetchComplete - завершение выборки.

· Уведомления об изменении положения текущей записи в наборе:

· OnWillMove - вызывается до изменения положения текущей записи. Позволяет отменить действие;

· OnMoveComplete - вызывается после изменения положения текущей записи;

· OnEndOfRecordset - генерируется при достижении конца набора данных. Позволяет добавить новую запись.

Уведомления об изменении набора данных.

· OnWillChangeField, OnFieldChangeComplete - вызываются до и после изменения текущей записи набора;

· OnWillChangeRecord, OnRecordChangeComplete - вызываются до и после изменения, добавления, удаления строки набора и при отмене этих действий;

· OnWillChangeRecordset, OnRecordsetChangeComplete - вызываются до и после открытия, закрытия, повторного запроса и синхронизации набора данных.

Многие события допускают прерывание действия. Эта возможность полезна при асинхронной работе с сервером, например, для прерывания слишком длинного запроса.

В ADO есть возможность, не имеющая аналогов ни в BDE, ни в InterBase. Это асинхронное выполнение операций с сервером. Асинхронно могут выполняться:

· установка соединения с сервером (Connection);

· выполнение команды (Execute);

· выборка данных (Fetch).

Для включения этого режима необходимо присвоить свойству ConnectOptions компонента ADOConnection значение coAsyncConnect.

При установлении соединения происходит следующее:

· вызывается обработчик события OnWillConnect;

· управление передается программе.

· После завершения соединения, как успешного, так и ошибочного, вызывается обработчик события OnConnectComplete.

Надо заметить, что многие компоненты ADO при активизации или выполнении отрабатывают команду на языке SQL. Это такие компоненты, как ADOCommand, ADODataSet, ADOTable, ADOQuery и ADOStoredProc. Для асинхронного выполнения команды установите в свойстве ExecuteOptions значение coAsyncExecute. При выполнении команды будет происходить следующее:

· вызовется обработчик события OnWillExecute;

· управление передастся программе.

После окончания выполнения команды, как успешного, так и ошибочного, вызывается обработчик события OnExecuteComplete.

57

Работа с базами данных в.NET Framework --- ADO.NET --- Модель EDM

Понимая предназначение платформы ADO.NET Entity Framewrok, а также имея общее представление о ее работе, можно приступать к рассмотрению первого примера. Чтобы пока не усложнять картину, построим модель EDM, которая обеспечит доступ только к таблице Inventory базы данных AutoLot, созданной ранее - ADO_NET/base/level3/3_2.php . Разобравшись с основами, мы затем построим новую модель EDM, которая будет рассчитана на всю базу данных AutoLot.

Генерация файла *.edmx

Начнем с создания консольного приложения. Когда планируется использование Entity Framework, первый шаг состоит в генерации необходимой концептуальной, логической и физической модели данных, определенной в файле *.edmx. Один из способов предусматривает применение для этого утилиты командной строки EdmGen.exe из комплекта.NET 4.0 SDK. Откройте окно командной строки Visual Studio 2010 и введите следующую команду:

На консоль выводится список опций, которые можно указывать утилите для генерации необходимых файлов, основываясь на существующей базе данных; кроме того, доступны опции для генерации совершенно новой базы данных на основе имеющихся сущностных файлов. Ниже описаны некоторые общие опции EdmGen.exe:

/mode:FullGeneration

Генерировать файлы *.ssdl, *.msl, *.csdl и клиентские сущности из указанной базы данных

/project:

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

/connectionstring:

Строка соединения, используемая для взаимодействия с базой данных (допускается сокращенная форма - /с:)

/language:

Позволяет указать, какой синтаксис должен использоваться для сгенерированного кода - C# или VB

/pluralize

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

Как и платформа.NET 4.0 в целом, программная модель EF поддерживает программирование в стиле сначала домен, что позволяет создавать свойства (с применением типичных объектно-ориентированных приемов) и использовать их для генерации новой базы данных. В этом вводном обзоре ADO.NET EF ни подход "сначала модель", ни генерация сущностной модели клиентской стороны с помощью утилиты EdmGen.exe применяться не будут. Вместо этого будут использоваться визуальные конструкторы EDM из среды Visual Studio 2010.

Выберите пункт меню Project --> Add New Item (Проект --> Добавить новый элемент) и вставьте новый элемент ADO.NET Entity Data Model по имени InventoryEDM.edmx:

Щелчок на кнопке Add (Добавить) приводит к запуску мастера создания модели сущностных данных (Entity Data Model Wizard) . На первом шаге мастер позволяет выбрать, нужно генерировать EDM из существующей базы данных либо определить пустую модель (для разработки в стиле "сначала модель"). Выберите опцию Generate from database (Генерировать из базы данных) и щелкните на кнопке Next (Далее).

На втором шаге мастера выбирается база данных. Если соединение с базой данных внутри проводника сервера Visual Studio 2010 уже существует, оно будет присутствовать в раскрывающемся списке. Если же нет, щелкните на кнопке New Connection (Создать соединение). В любом случае выберите базу данных AutoLot и отметьте флажок Save entity connection settings in App.config as (Сохранить настройки соединения в файле App.config как):

Прежде чем щелкать на кнопке Next, взгляните на формат строки соединения:

Metadata=res://*/InventoryEDM.csdl|res://*/InventoryEDM.ssdl|res://*/InventoryEDM.msl; provider=System.Data.SqlClient;provider connection string="Data Source=MICROSOF-1EA29E\SQLEXPRESS;Initial Catalog=AutoLot; Integrated Security=True;Pooling=False"

Основной интерес в ней представляет флаг metadata , который используется для указания имен встроенных данных XML-ресурсов концептуального, физического и файла отображений (вспомните, что во время компиляции файл *.edmx будет разделен на отдельные файлы, и данные этих файлов примут вид двоичных ресурсов, встраиваемых в сборку).

На последнем шаге мастера можно выбрать элементы из базы данных, для которой необходимо сгенерировать модель EDM. В рассматриваемом примере ограничимся только таблицей Inventory. Щелкните на кнопке Finish для генерации модели EDM.

Изменение формы сущностных данных

После завершения работы с мастером откроется визуальный конструктор EDM в IDE-среде с одной сущностью по имени Inventory. Просмотреть композицию любой сущности в визуальном конструкторе можно в окне Model Browser (Браузер моделей), которое открывается через пункт меню View --> Other Windows (Вид --> Другие окна).

Теперь взгляните на формат концептуальной модели для таблицы базы данных Inventory, представленный в папке Entity Types (Типы сущности). В узле хранилища, имя которого совпадает с именем базы данных (AutoLotModel.Store), находится физическая модель базы данных:

По умолчанию имена сущностей будут основаны на именах исходных объектов баз данных; однако, вспомните, что имена сущностей в концептуальной модели могут быть любыми. Чтобы изменить имя сущности либо имена свойств сущности, необходимо выбрать нужный элемент в визуальном конструкторе и установить соответствующим образом свойство Name в окне свойств (Properties). Переименуйте сущность Inventory в Car и свойство PetName в CarNickname:

Теперь выберите сущность Car в визуальном конструкторе и снова загляните в окно Properties. Вы должны увидеть поле Entity Set Name (Имя набора сущностей), также переименованное из Inventories в Cars. Значение Entity Set Name важно, потому что оно соответствует имени свойства в классе контекста данных, который используется для модификации базы данных. Вспомните, что это свойство инкапсулирует переменную-член ObjectSet класса-наследника ObjectContext.

Просмотр отображений

Имея данные в измененной форме, можно просматривать отображения между концептуальным уровнем и физическим уровнем в окне Mapping Details (Сведения об отображениях), которое открывается через пункт меню View --> Other Windows --> Mapping Details. Взгляните на рисунок ниже и обратите внимание, что узлы в левой части дерева представляют имена данных из физического уровня, в то время как узлы справа представляют имена концептуальной модели:

Просмотр данных сгенерированного файла *.edmx

Теперь давайте посмотрим, что именно мастер EDM Wizard сгенерировал. Щелкните правой кнопкой мыши на файле InventoryEDM.edmx в проводнике решения и выберите в контекстном меню пункт Open With... (Открыть с помощью). В открывшемся диалоговом окне выберите опцию XML Editor (Редактор XML). Это позволит просмотреть XML-данные, лежащие в основе представления в визуальном конструкторе EDM. Структура этого XML-документа разделена на четыре части: все они находятся в корневом элементе .

Подэлемент определяет XML-данные для концептуальной, физической и модели уровня отображения. Ниже показано определение физической таблицы базы данных Inventory:

Обратите внимание, что узел определяет имя поставщика данных ADO.NET, который использует эту информацию при взаимодействии с базой данных (System.Data.SqlClient). Узлами помечается имя физической таблицы базы данных, а также каждый столбец в таблице.

Следующая важная часть файла *.edmx - элемент . который определяет измененные сущности клиентской стороны. Как видно, сущность Cars определяет свойство CarNickname, которое изменяется в визуальном конструкторе:

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


ГЛАВА 4

Модель ADO.NET: провайдеры данных

Порой кажется, что не успели еще разработчики приложений баз данных привыкнуть к новой технологии, как компания Microsoft предложила совершенно новую модель доступа к базам данных. В этой главе основное внимание уделяется модели ADO.NET, которая является наиболее новым воплощением модели ADO. Сначала обосновывается необходимость введения новой модели доступа к базам данных (с точки зрения авторов), а затем предлагается описание самой модели и ее архитектуры.

В этой главе преследуется цель изложить основные принципы функционирования технологии ADO.NET. Здесь обсуждаются базовые операции и описываются базовые объекты провайдера данных ADO.NET, в частности Connection, Command, Parameter и DataReader. Далее (в главах 5, "ADO.NET: объект DataSet", 6, "ADO.NET: объект DataAdapter", и 7, "ADO.NET: дополнительные компоненты") рассматриваются более сложные объекты, которые тесно связаны с основным объектом ADO.NET - DataSet.

Обзор технологии ADO.NET

Разработчики приложений для работы с базами данных на основе Visual Basic уже привыкли к тому, что Microsoft каждые несколько лет предлагает новую усовершенствованную модель доступа к данным. Кроме новой трехбуквенной аббревиатуры, технология ADO.NET также предлагает новую модель API-интерфейсов и объектов. В течение последних нескольких лет разработчики уже успели познакомиться с предшественниками ADO.NET - технологиями ODBC, DAO, RDO и ADO. При знакомстве с каждой новой технологией им требовалось тщательно изучить ее назначение и принципы работы. При этом они часто задавали себе один и тот же вопрос: имеет ли смысл переходить на новую технологию? В большинстве случаев ответ был положительным, если только новшества не оказывали никакого влияния на текущие и будущие требования проекта. Действительно, чаще всего переход на новые технологии был вполне обоснован, за исключением технологии RDO (Remote Data Objects) для проектов с процессором баз данных Jet (потому что технология ОАО по-прежнему является более удачной технологией работы с Jet).

Мотивация и философия

Итак, зачем же все-таки нужна новая объектная модель доступа к данным? Наиболее простой ответ на этот вопрос можно сформулировать по аналогии с рекламным лозунгом компании Toyota конца 1970-х годов: "Спрашивали? Так получите". Технология ADO.NET предоставляет множество функциональных возможностей, о которых разработчики мечтали с момента появления технологии ADO. Некоторые компоненты, например отсоединенные от источника данных наборы записей и поддержка XML, были добавлены в модель ADO уже после ее выпуска. Но, поскольку они являлись добавлениями к основной модели, которые отсутствовали в исходной архитектуре ADO, способ их применения был крайне неудобным.

Классическая модель ADO основана на модели COM и одном объекте recordset, применение которого может быть весьма разнообразным. В зависимости от параметров конфигурации, например типа курсора, его расположения и типа блокировки, объект recordset будет действовать по-разному и выполнять различные операции.

В технологии ADO.NET разные функции разделены и выполняются разными объектами, которые могут использоваться либо отдельно, либо вместе с другими объектами.

Это дает разработчику возможность специализированного применения объектов без дополнительного обременительного "багажа". В то же время совершенно разные объекты могут взаимодействовать друг с другом для выполнения более сложных функций.

Поддержка распределенных приложений и отсоединенной модели программирования

В технологии ADO.NET предусмотрена эффективная и гибкая поддержка приложений, распределенных между несколькими компьютерами (серверами баз данных, серверами приложений и клиентскими рабочими станциями). В частности, особая поддержка предусмотрена для отсоединенных (трехуровневых или n-уровневых) приложений с минимизацией нагрузки при работе с параллельными операциями доступа к данным и блокировкой ресурсов сервера базы данных. В результате повышаются возможности масштабирования приложений, т.е. поддержки большего количества параллельно работающих пользователей за счет постепенного увеличения количества клиентских компьютеров. Это преимущество имеет особенно большое значение при создании Web-приложений.

Расширенная поддержка XML

Хотя классическая модель ADO способна сохранять и считывать данные в формате XML, фактически используемый для этого формат имеет несколько необычную форму и не так прост в применении. Кроме того, поддержка XML в модели ADO была добавлена в ходе ее эволюции, а потому обладает некоторыми ограничениями, в то время как поддержка XML в технологии ADO.NET является ее ключевым элементом. Философия ADO.NET формулируется очень кратко и просто: "Данные - это данные". Независимо от источника поступления, они могут считываться и обрабатываться как реляционные или иерархические данные, исходя из поставленной задачи и используемых инструментов.

XML используется как формат передачи данных между уровнями и компьютерами. Это не только исключает проблему прохождения COM-объектов через брандмауэры, но и позволяет совместно использовать данные сразу несколькими приложениями, которые работают на платформах, отличных от Windows (так как практически любая платформа способна обрабатывать текстовые данные XML).

Интеграция с.NET Framework

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

Внешний вид объектов ADO.NET

Хотя ADO.NET является составной частью платформы.NET Framework, многие ее компоненты будут знакомы опытным разработчикам, поскольку внешне они во многом напоминают объекты ADO. Даже совершенно новые элементы ADO.NET реализованы так, чтобы максимально упростить процесс их изучения и применения на основе имеющихся знаний.

ADO.NET И ADO 2.X

При работе с моделью ADO.NET нужно учитывать перечисленные ниже отличия от классической модели ADO.

В классической модели ADO предусмотрен присоединенный к источнику данных способ доступа и использование физической модели данных.

В модели ADO.NET проводиться четкое разделение между присоединенным способом доступа к данным и отсоединенной моделью программирования.

В модели ADO.NET нет свойств CursorType, CursorLocation или LockType, потому что в ADO.NET предусмотрены только статические курсоры, клиентские курсоры и оптимистическая блокировка.

Вместо использования простого многоцелевого объекта recordset в ADO.NET разные функции распределены среди объектов меньшего размера - DataReader, DataSet и DataTable.

В ADO.NET разрешается полноценное манипулирование данными в формате XML, а не только использование его в качестве формата ввода-вывода данных.

В ADO.NET предусмотрена поддержка строго типизированных наборов данных DataSet, а не использование для всех полей типа Variant. Это позволяет эффективнее обнаруживать ошибки времени выполнения и повышает производительность работы приложений.

Место ADO.NET в архитектуре.NET Framework

На рис. 4.1 показано место классов ADO.NET в архитектуре.NET Framework. В основе этой платформы лежит общеязыковая исполняющая среда (Common Language Runtime - CLR), которая образует единую среду выполнения для всех.NET-совместимых приложений, независимо от используемого языка программирования. Среда CLR включает общую систему типов, управление памятью и жизненным циклом объектов.

На следующем логическом уровне над средой CLR располагаются базовые системные классы. Именно эти классы отвечают за выполнение базовых функций, которые могут использоваться в.NET-приложениях. На рис. 4.1 показаны только некоторые классы библиотеки классов.NET Framework, которая, по сути, является новым набором API-интерфейсов Windows. В прошлом доступ к функциям операционной системы Windows осуществлялся только через API-интерфейсы, которые состояли из большого набора разрозненных и плохо продуманных функций. На платформе.NET Framework такой доступ организован на основе свойств и методов, которые предлагаются базовыми системными классами. Это объектно-ориентированный, последовательный и комфортабельный способ создания приложений Windows, независимо от типа клиентского приложения: традиционного настольного приложения, броузера или Web-службы.

РИС. 4.1. Классы ADO.NET в структуре платформы.NET Framework


Этот уровень включает несколько пространств имен (групп классов и других определений), предназначенных для организации доступа к данным: System.Data, System.OleDb и System.Data.SqlClient. В оставшейся части данной главы, а также в главах 5, 6 и 7 эти классы и пространства имен рассматриваются более подробно.

Прикладные интерфейсы

На этом наиболее высоком уровне происходит дифференциация, или разделение, выполняемых функций, которые разработчики могут использовать в разных типах приложений. Он содержит классы и элементы управления для создания (классических) приложений Windows на основе форм (Windows Forms), другие классы и элементы управления для создания Web-ориентированных приложений (Web Forms), а также классы для создания приложений на основе Web-служб. Однако все они используют для прикладной логики базовую библиотеку классов - системные базовые классы.

Теперь после первого знакомства с расположением классов ADO.NET в общей структуре платформы.NET Framework рассмотрим подробнее основные объекты ADO.NET.

Провайдеры данных ADO.NET

Несмотря на подчеркнутое значение отсоединенной модели программирования, для извлечения, обновления, вставки и удаления данных все же придется подключиться к физической базе данных. Программное обеспечение ADO.NET для подсоединения и взаимодействия с физической базой данных называется провайдером данных ADO.NET. Провайдер данных (data provider) - это управляемый код.NET, который эквивалентен провайдеру OLEDB или драйверу ODBC. Провайдер данных состоит из нескольких объектов, которые реализуют необходимую функциональность в соответствии с определениями своих классов и интерфейсов.

В настоящее время существует три разных провайдера данных ADO.NET, каждый из которых определен в своем собственном пространстве имен. Для всех объектов в этих пространствах имен используются следующие префиксы: OleDb, Sql и Odbc. Однако при упоминании этих объектов в рамках своего пространства имен имя объекта можно указывать без употребления префикса данного пространства имен.

Провайдер данных SqICIient

Оптимизирован для работы с SQL Server версии 7.0 (или выше) и позволяет добиться более высокой производительности по следующим причинам:

Взаимодействует с базой данных непосредственно через собственный протокол табличной передачи данных (Tabular Data Stream - TDS), а не через OLEDB с отображением интерфейса OLEDB на протокол TDS;

Исключает накладные расходы, связанные с использованием COM-служб взаимодействия;

Отсутствуют ненужные функции, которые не поддерживаются в SQL Server (объекты этого провайдера данных находятся в пространстве имен System.Data.SqlClient).

Провайдер данных Oledb

Основан на существующем COM-поставщике OLEDB и COM-службах взаимодействия платформы.NET Framework, предназначенных для доступа к базе данных. Этот провайдер данных используется для работы с SQL Server более ранних версий, чем 7.0. Он позволяет осуществлять доступ к любой базе данных, для которой имеется поставщик OLEDB. Объекты этого провайдера данных находятся в пространстве имен System.Data.Oledb.

Провайдер данных Odbc

Используется для доступа к базам данных, которые не имеют собственного провайдера данных.NET или COM-поставщика OLEDB. Иногда драйвер ODBC демонстрирует более высокую производительность, чем драйвер OLEDB, поэтому для сравнения их фактической производительности при работе с конкретной базой данных рекомендуется провести ряд тестов. Объекты этого провайдера данных находятся в пространстве имен System. Data.Odbc.

НА ЗАМЕТКУ

Создание провайдера данных для ODBC несколько задержалось и отстало от создания платформы.NET Framework и Visual Studio .NET. Поэтому он не был включен в исходный выпуск Visual Studio .NET и его можно скопировать с Web-узла компании Microsoft. Кроме того, в скором будущем следует учитывать возможное появление дополнительных.NET-совместимых провайдеров данных.

В настоящее время на Web-узле компании Microsoft также можно скопировать провайдер данных Oracle.NEТ. Эти провайдеры данных ОDBС и Oracle будут включены в следующую версию 1.1 платформы.NET Framework и Visual Studio .NET 2003. В результате этого пространство имен провайдера данных ODBC Microsoft.Data.Odbc станет называться System.Data.Odbc.

В примерах этой главы используется провайдер данных ODBC версии 1.0, поэтому при использовании провайдера данных ODBC версии 1.1 нужно изменить имя его пространства имен.

Основные объекты

Каждый провайдер данных имеет четыре основных объекта, которые указаны в табл. 4.1.

Таблица 4.1. Основные объекты провайдера данных

Каждый объект основан на базовом родовом классе и реализует родовой интерфейс, но имеет собственную реализацию. Например, объекты SqlDataAdapter, OleDBDataAdapter и OdbcDataAdapter являются производными от класса DbDataAdapter и реализуют те же интерфейсы. Однако каждый из них реализует их своим собственным способом для соответствующего источника данных.

Пространство имен System. Data. OleDb содержит объекты:

OleDbConnection;

OleDbDataReader;

OleDbDataAdapter.

Пространство имен System.Data.SqlClient содержит объекты:

SqlConnection;

SqlDataReader;

SqlDataAdapter.

Пространство имен Microsoft.Data.Odbc содержит объекты:

OdbcConnection;

OdbcDataReader;

OdbcDataAdapter.

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

Объект Connection

Этот объект модели ADO.NET очень похож на объект Connection в классической модели ADO. Его предназначение очевидно: он служит для установления соединения с заданным источником данных и с указанным в строке подключения учетным именем и паролем. Соединение можно настроить, редактируя нужным образом значения параметров строки подключения. Объект Command (или DataAdapter) может затем использовать это подключение для выполнения нужных операций с источником данных.

НА ЗАМЕТКУ

В отличие от объекта Connection в модели ADO в объекте Connection в модели ADO.NET нет методов Execute и ОpenSchema. Для выполнения команд SQL следует использовать объекты Command или DataAdapter. Функции метода OpenSchema реализуются с помощью методов GetOleSchemaTable объекта OleDbConnection.

Хотя объекты OleDbConnection, SqlConnection и OdbcConnection реализуют одинаковые интерфейсы, они все же имеют разные реализации. Например, они имеют разный формат строки подключения. В объекте OleDbConnection используется стандартный формат строки подключения OLEDB с незначительными исключениями. В объекте OdbcConnection также используется стандартный формат строки подключения ODBC, но с незначительными отклонениями. Наконец, в объекте SqlConnection используется совершенно другой формат строки подключения, который имеет отношение только к SQL Server версии 7.0 или выше.

Более того, некоторые объекты обладают дополнительными свойствами. Например, объект OleDbConnection имеет свойство Provider для указания используемого провайдера данных OLEDB, а объект OdbcConnection имеет свойство Driver для указания используемого драйвера ODBC. Объект SqlConnection вообще не имеет этих свойств, так как используется с предопределенным источником данных, т.е. с SQL Server. Однако он имеет свойства PacketSize и WorkstationID, которые используются только для работы с SQL Server и не нужны для других типов подключения.

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

1. Запустите интегрированную среду разработки приложений Visual Studio .NET.

2. Создайте новый проект Visual Basic Windows Application. Для этого в диалоговом окне New Project (Новый проект) выберите тип проекта Visual Basic Project в области Project Types (Типы проектов), а затем шаблон Windows Application (Приложение Windows) в области Templates (Шаблоны).

3. Назовите проект DataProviderObjects.

6. В окне Properties укажите значение Data Provider Objects для свойства Text формы Form1.

7. В верхнем левом углу формы создайте кнопку, перетаскивая ее из панели элементов управления.

8. В окне Properties укажите значение cmdConnection для свойства (Name) и значение Connection для свойства Text этой кнопки.

9. В верхнем правом углу формы создайте текстовое поле, перетаскивая его из панели элементов управления.

10. В окне Properties укажите значение txtResults для свойства (Name), значение True для свойства Multiline и значение Both для свойства ScrollBars этого текстового поля.

11. Увеличьте размер текстового поля, чтобы оно занимало до 80% всей площади формы.

По окончании этих операций форма будет выглядеть так, как на рис. 4.2.


РИС. 4.2. Форма Form1 проекта DataProviderObjects


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

Обратите внимание, что эти пространства имен содержат классы и определения объектов ADO.NET для каждого провайдера данных.

НА ЗАМЕТКУ

Среда Visual Studio .NET может не обнаружить пространство имен Data.Odbc, потому что оно является расширением базовой версии продукта. В таком случае выполните ряд действий.

1. Скопируйте инсталлятор провайдера данных ODBC с Web-узла компании Microsoft и выполните все инструкции по инсталляции.

2. В окне SolutionExplorer щелкните правой кнопкой мыши на папке References проекта DataProviderObjects.

3. Выберите в контекстном меню команду Add Reference.

4. Во вкладке.NET диалогового окна Add Reference прокрутите список компонентов и найдите файл Microsoft.Data.Odbc.dll.

5. Щелкните дважды на файле Microsoft.Data.Odbc.dll для включения его в список избранных компонентов Selected Components в нижней части диалогового окна Add Reference.

6. Щёлкните на кнопке ОК для закрытия диалогового окна Add Reference.

Если по какой-либо причине не распознано какое-то другое импортированное пространство имен, потребуется привести ссылку на файл System.Data.dll. Для этого выполните действия, перечисленные в пп. 2-6, где вместо файла Microsoft.Data.Odbc.dll при выполнении п. 4 нужно использовать файл System.Data.dll.

Теперь для кнопки btnConnection нужно создать код, приведенный в листинге 4.1, для создания подключения к базе данных pubs сервера SQL Server. Этот код создает подключение и отображает состояние подключения до и после попытки подключения к базе данных.

Листинг 4.1. Код открытия подключения и отображения его состояния
Private Sub btnConnection Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnConnection.Click
" Открытие подключения
txtResults.Text & "Opening DB connection…" _
& ControlChars.CrLf & ControlChars.CrLf
" Отображение состояния подключения
If (cnn.State = System.Data.ConnectionState.Open) Then
txtResults.Text = txtResults.Text & "Connection is Open"
txtResults.Text = txtResults.Text & "Connection is Closed"
txtResults.Text = txtResults.Text & ControlChars.CrLf
СОВЕТ

Новым полезным компонентом Visual Basic .NET является возможность получения текстового представления для значений перечисления (enum) вместо создания специальной подпрограммы на основе операторов select-case для всех возможных значений перечисления. Все типы перечисления, которые являются объектами, наследуют метод ToString, возвращающий строку с текстовым представлением текущего значения. В листинге 4.1 используется приведенный ниже фрагмент кода для отображения состояния подключения на основе операторов if-else.

" Отображение состояния подключения – вариант 1
If (cnn.State = System.Data.ConnectionState.Open) Then
txtResults.Text = txtResults.Text& "Connection is Open"
txtResults.Text = txtResults.Text& "Connection is Closed

Его можно заменить другим вариантом кода, содержащим только одну строку.

" Отображение состояния подключения вариант 2
txtResults.Text & "Connection is " & cnn.State.ToString & ControlChars.CrLf

После запуска полученного приложения DataProviderObjects и щелчка на кнопке Connection в текстовом поле появятся строки о закрытии подключения, о состоянии подключения и повторном открытии подключения, как показано на рис. 4.3.

НА ЗАМЕТКУ

При создании рабочего кода нужно выбрать и реализовать стратегию обработки исключительных ситуаций для большинства подпрограмм и операций. Обычно основана на блоках try-catch обработки исключительных ситуаций. Этот код не включен в приведенные здесь примеры, потому что наша цель - сфокусировать внимание на концепциях программирования баз данных, а не на описании общих принципов программирования для Visual Basic .NET.

Объект Command

Аналогично объекту Connection, объект Command модели ADO.NET очень похож на своего предшественника из прежней модели ADO 2.X. Объект Command позволяет выполнять команды по отношению к источнику данных и получать возвращенные данные или результаты выполнения команд.


РИС. 4.3. Состояние приложения DataProviderObjects до и после открытия подключения с помощью кода из листинга 4.1


Этот объект имеет следующие свойства: CommandText и СommandType для определения текста и типа фактической команды; Connection для указания подключения, используемого для выполнения команды; СommandTimeout для указания времени ожидания, по истечении которого команда отменяется и выдается сообщение об ошибке; Parameters для коллекции параметров команды; Transaction для указания транзакции, в которой используется данная команда.

Все три версии объекта Command (в пространствах имен OleDb, Sql, Odbc) имеют идентичные свойства и методы, за исключением того, что объект SqlCommand имеет дополнительный метод, которого нет у двух других вариантов этого объекта, а именно ExecuteXmlReader. Он использует преимущества SQL Server для автоматического возвращения данных в формате XML (если в запрос SQL добавлено предложение FOR XML).

НА ЗАМЕТКУ

Еще одно отличие между версиями объекта Command для разных провайдеров данных заключается в использовании значений свойства CommandType. Все они поддерживают значения Text и StoredProcedure, а объекты OleDbCommand и SqlCommand поддерживают еще одно, третье возможное значение - TableDirect. Это позволяет эффективно загружать все содержимое таблицы за счет установки значения TableDirect для свойства CommandType и имени таблицы для свойства CommandText.

Продолжим работу с формой, показанной на рис. 4.3.

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

2. В окне свойств Properties установите значение btnCommand для свойства Name и значение Command для свойства Text.

3. Добавьте для кнопки код, показанный в листинге 4.2.

Листинг 4.2. Код открытия подключения к базе данных и подготовки объекта Command
" Создание экземпляра объекта Connection
" Создание экземпляра объекта Command
Dim cmd As SqlCommand = New SqlCommand() txtResults.Clear()
"Select au_lname, state from authors"
" Вывод текста команды
txtResults.Text = txtResults.Text & ControlChars.Tab & cmd.CommandText() & ControlChars.CrLf

После запуска на выполнение приложения DataProviderObjects щелкните на кнопке Command, и в текстовом поле будет показана команда SQL, которая находится в свойстве CommandText объекта SqlCommand, а именно: SELECT au_lname, state FROM authors.

НА ЗАМЕТКУ

Многие классы платформы.NET Framework, а также созданные другими разработчиками классы перегружают конструкторы объектов. Иначе говоря, существует несколько способов создания нового экземпляра класса, где каждый конструктор принимает свойственный только ему набор аргументов. Таким образом можно выбрать версию, оптимальную для выполнения текущей задачи. Конструктор в листинге 4.2 для объекта SqlConnection отличается от го по умолчанию конструктора в листинге 4.1, в котором не используется никаких аргументов. Позже строке подключения присваивается объект SqlConnection с помощью свойства ConnectionString.

" Создание экземпляра объекта Connection
Dim cnn As SqlConnection = New SqlConnection()
" Создание строки подключения
cnn.ConnectionString = "server=localhost;uid=sa;database=pubs"

В листинге 4.2 используется конструктор объекта SqlConnection, который принимает строку подключения в качестве параметра. Это позволяет создать объект и сразу же указать для него строку подключения в одной строке кода.

" Создание экземпляра объекта Connection
Dim cnn SqlConnection = New SqlConnection("server=localhost;uid-sa;database=pubs")

Применение объекта Command с параметрами и хранимыми процедурами

При создании запросов или команд для источника данных часто требуется передавать значения параметров действия (обновление, вставка или удаление данных) или хранимой процедуры. Для решения этой проблемы в объекте Command предусмотрено свойство Parameters, которое является объектом-коллекцией ParameterCollection и содержит коллекцию объектов-параметров Parameter. Это аналогично способу работы, применимому в модели ADO 2.X.

Объекты Parameter и ParameterCollection тесно связаны с соответствующим провайдером данных, поэтому они должны быть реализованы как составная часть провайдера данных ADO.NET. Способы программирования объекта SqlParameterCollection и использование объектов OdbcParameterCollection и OledbParameterCollection имеют существенные отличия. Объекты OdbcParameterCollection и OledbParameterCollection основаны на позиционных параметрах, а объект SqlParameterCollection – на именованных параметрах. Эти различия в значительной степени влияют на способ определения запросов и параметров.

Начнем с создания простого запроса с параметрами для извлечения из базы данных pubs имен и фамилий всех авторов из заданного штата.

С одной стороны, при использовании провайдеров данных OLEDB или ODBC запрос будет иметь следующий вид:

SELECT state, au_fname, au_lname from authors WHERE state = ?

Здесь вопросительный знак заменяет один параметр, а для нескольких параметров можно использовать несколько вопросительных знаков. Порядок расположения параметров в коллекции ParameterCollection должен точно соответствовать порядку, в котором располагаются их заменители в запросе или хранимой процедуре.

С другой стороны, при использовании провайдера данных SqlClient запрос будет выглядеть следующим образом:

SELECT state, au_fname, au_lname from authors WHERE state = @MyParam

Здесь заменителем параметра является его имя, а дополнительные параметры также обозначаются их именами, поэтому порядок расположения параметров в коллекции ParameterCollection не имеет значения.

Объект Parameter можно создать явно, используя конструктор Parameter (т.е. с использованием оператора New) или передавая нужные аргументы методу Add объекта-коллекции ParameterCollection (свойство Parameters объекта Command). Помните, что оба метода (конструктор Parameter и метод Add) имеют перегруженные версии.

Ниже приведен один из способов включения параметра команды за счет явного указания объекта-параметра.

Dim rayParameter As New OdbcParameter("@MyParam", OdbcType.Char, 2)

А способ включения параметра команды с помощью метода Add выглядит иначе.

Второй метод короче и обычно предпочтительнее, если только нет особой причины для повторного использования объекта Parameter.

Для метода Add объекта Parameter обычно требуется указать имя, тип и длину параметра. Затем нужно указать направление передачи данных: Input, Output, InputOutput или ReturnValue. По умолчанию используется направление Input. Наконец, для присвоения значения параметру нужно использовать свойство Value объекта Parameter. Кроме того, для параметра можно указать другие свойства, например масштаб (свойство Scale), точность (свойство Precision) и допустимость использования неопределенных значений (свойство IsNullable).

При использовании провайдера данных SqlClient можно применять практически идентичный код. Единственным отличием являются префиксы Odbc вместо префиксов Sql, а также тип перечисления SqlDbType вместо OdbcType.

Dim myParameter As New SqlParameter("@MyParam", SqlDbType.Char, 2)
myParameter.Direction = ParameterDirection.Input
cmd.Parameters.Add(myParameter)

Аналогично выглядит способ включения параметра команды с помощью метода Add.

cmd.Parameters("@MyParam").Direction = ParameterDirection.Input
cmd.Parameters("@MyParam").Value = "CA"
СОВЕТ

Для передачи параметру неопределенного значения можно использовать свойство Value объекта DBNull.

cmd.Parameters("@MyParam").Value = DBNull.Value

Измените код кнопки cmdButton, как показано в листинге 4.3. После запуска программы и щелчка на кнопке cmdButton в текстовом поле появится текст запроса, а также имя и значение параметра.

Листинг 4.3. Код подготовки и отображения команды и ее параметров
Private Sub btnCommand_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnCommand.Click
" Создание экземпляра объекта Connection
Dim cnn As SqlConnection = New SqlConnection(_
"server=localhost;uid=sa;database=pubs")
" Создание экземпляра объекта Command и объектов Parameter
Dim prm As SqlParameter = New SqlParameter()
" Открытие подключения cnn.Open()
" Указание подключения и текста команды
cmd.CommandType = CommandType.Text
"Select au_lname, state from authors where state = @MyParam"
cmd.Parameters.Add(New SqlParameter("@MyParam", SqlDbType.Char, 2))
cmd.Parameters("@MyParam").Value = "CA"
" Вывод текста команды
txtResults.Text = "Command String:" & ControlChars.CrLf
cmd.CommandText() & ControlChars.CrLf
" Вывод параметров команды
txtResults.Text = txtResults.Text & "Command parameters:" & _
For Each prm In cmd. Parameters
txtResults.Text = txtResults.Text & ControlChars.Tab & _
prm.ParameterName & " = " & prm.Value & ControlChars.CrLf

Аналогично вызываются хранимые процедуры, за исключением того, что вместо свойства CommandType.Text используется свойство CommandType.StoredProcedure, а имя хранимой процедуры присваивается свойству CommandText. Таким образом, код вызова хранимой процедуры GetAuthorsFromState с двухсимвольным параметром для извлечения информации обо всех авторах заданного штата будет выглядеть, как показано ниже.

cmd.CommandText = "GetAuthorsFromState"
cmd.Parameters.Add("@MyParam", SqlDbType.Char, 2)
cmd.Parameters("@MyParam").Direction = ParameterDirection.Input
cmd.Parameters("@MyParam").Value = "CA"
СОВЕТ

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

cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "{GetAuthorsFromState ?}"
cmd.Parameters.Add("@MyParam", OdbcType.Char, 2)
cmd.Parameters("@MyParam").Direction = ParameterDirection.Input
cmd.Parameters("@MyParam").Value = "CA"

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

cmd.CommandText = "{? = GetAuthorsFromState ?}"

Чтобы хранимая процедура возвращала значение, нужно указать направление Output, а затем считать значение свойства параметра после вызова хранимой процедуры. В данном примере также определяется возвращаемое значение хранимой процедуры. Поскольку здесь указан тип Int сервера SQL Server, то для этого параметра не нужно указывать длину, так как по определению она составляет 4 байт.

cmd.Parameters.Add(New SqlParameter("result", SqlDbType.Int)
cmd. Parameters ("result").Direction = ParameterDirection.ReturnValue
cmd.Parameters.Add(New SqlParameter("@MyParam", SqlDbType.Int)
cmd.Parameters("@MyParam").Direction = ParameterDirection.Output
" Вызов хранимой процедуры
Msg (cmd.Parameters("@MyParam").Value)
НА ЗАМЕТКУ

При определении параметра, который будет использоваться для возвращаемого значения ReturnValue хранимой процедуры его нужно указать первым в списке параметров коллекции Parameters. Это обязательное условие для провайдеров данных OLEDB и ODBC, потому что, как отмечалось выше, параметры этих провайдеров определяются по порядку их расположения, а возвращаемое значение может располагаться только с начала. Однако при определении возвращаемого значения для провайдера данных SQL его можно pасполагать в произвольном месте коллекции параметров, потому что параметры провайдера данных SQL определяются по именам.

Выполнение команд

До сих пор мы только указывали свойства и параметры объекта Command, но не выполняли эти команды. Существует три стандартных способа выполнения команд для объекта Command и один способ для объекта SqlCommand.

Метод ExecuteNonQuery. Выполняет команду SQL и не возвращает записей.

Метод ExecuteScalar. Выполняет команду SQL и возвращает первое поле первой записи.

Метод ExecuteReader. Выполняет команду SQL и возвращает набор записей с помощью объекта DataReader.

Метод ExecuteXmlReader (только для объекта-команды SqlCommand). Выполняет команду SQL и возвращает набор записей в формате XML с помощью объекта XmlReader.

Метод ExecuteNonQuery

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

НА ЗАМЕТКУ

Метод ExecuteNonQuery возвращает только целочисленное значение, сообщающее об успешном или неудачном исходе выполнения команды.

При удачном выполнении DDL-команд определения данных для изменения структуры баз данных возвращается значение -1, а при удачном выполнении DML управления данными для их обновления, вставки или удаления возвращается количество строк, задействованных в команде. При неудачном выполнении команд обоих типов возвращается значение 0.

Продолжая работу с проектом DataProviderObjects, попробуем использовать объекты пространства имен OleDb и базу данных pubs. Наша задача – создать новую таблицу tblStateZipCodes для этой базы данных с помощью DDL-команды. Новая таблица tblStateZipCodes предназначена для организации связи между почтовыми индексами и штатами. Определения ее полей совпадают с определениями полей в других таблицах базы данных pubs, но отличаются от определений полей в других таблицах базы данных Novelty. Эта таблица имеет два поля: ZipCode для почтового индекса и State для названия соответствующего штата. Ниже приведена команда SQL для создания этой таблицы.

CREATE TABLE tblStateZipCodes (

Теперь нужно изменить исходную форму Form1, выполнив ряд действий.

1. Откройте форму Form1 в интегрированной среде разработки Visual Studio .NET.

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

3. В окне свойств Properties укажите значение btnNonQuery для свойства (Name) и значение ExecuteNonQuery для свойства Text.

Затем создайте код подпрограммы btnNonQuery_Click, который приведен в листинге 4.4.

Листинг 4.4. Код создания таблицы базы данных с помощью объектов пространства имен OleDb
Private Sub btnNonQuery_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnNonQuery.Click
Dim sql As String Dim result As Integer
Dim cmd As OleDbCommand = New OleDbCommand()
" Указание подключения и текста команды
cmd.CommandType = CommandType.Text
" Указание команды SQL для создания новой таблицы
sql = "CREATE TABLE tblStateZipCodes (" & _
"ZipCode char (5) NOT NULL, " & _
MsgBox(sql) cmd.CommandText = sql
" Открытие подключения перед вызовом метода ExecuteNonQuery.
" Для обработки исключительных ситуаций нужно поместить
" код в блоке Try-Catch, потому что неудачное выполнение
" команды ТАКЖЕ генерирует ошибку времени выполнения.
" Отображение сообщения об ошибке.
" Вывод результатов выполнения команды.
MessageBox.Show("Command completed successfully")
" MessageBox.Show("Команда выполнена успешно")
MessageBox.Show("Command execution failed")
" MessageBox.Show("Команда не выполнена")

После запуска полученного приложения и щелчка на кнопке ExecuteNonQuery сначала появится диалоговое окно с сообщением об успешном выполнении команды. Правильность выполнения команды можно проверить, просматривая список таблиц базы данных pubs в диалоговом окне Server Explorer интегрированной среды разработки Visual Studio .NET (которое описывается в главе 1, "Основы построения баз данных") или в программе SQL Server Enterprise Manager (которая рассматривается в главе 3, "Знакомство с SQL Server 2000").

При повторном щелчке на кнопке ExecuteNonQuery появятся два диалоговых окна с сообщениями: одно с сообщением о возникшей исключительной ситуации (оно создается блоком обработки исключительных ситуаций try-catch), а другое - о неудачном выполнении команды.

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

sql = "CREATE VIEW EmployeeJobs_view AS" & _
"SELECT TOP 100 PERCENT jobs. job_desc," & _
"employee.fname, employee.lname" &_
"employee ON jobs. job_id = employee. job_id &_
НА ЗАМЕТКУ

Для включения предложения ORDER BY в определение представления с сортировкой результатов нужно включить в команду SELECT предложение ТОР.

Для создания хранимой процедуры, которая принимает один параметр и возвращает значение, нужно изменить эту команду SQL, как показано в листинге 4.5.

Листинг 4.5. Код, содержащий команду SQL для создания хранимой процедуры AuthorsInState1
sql = "CREATE PROCEDURE AuthorsInState1 @State char(2)" & _
" AS declare @result int" & _
" select @result = count (*) from authors " & _
НА ЗАМЕТКУ

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

Обратимся теперь ко второму типу команд SQL для управления данными, т.е. их обновления, вставки или удаления. Для этих команд обычно требуется указать параметры, особенно при работе с хранимыми процедурами.

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

1. Создайте новую кнопку под кнопкой cmdExecuteNonQuery.

2. В окне свойств Properties для этой кнопки укажите значение cmdUpdate для свойства (Name) и значение Update для свойства Text.

3. Создайте новое текстовое поле под новой кнопкой Update.

4. В окне свойств Properties для этого текстового поля укажите значение txtParam1 для свойства (Name) и значение 0 для свойства Text. Установка такого значения гарантирует, что при запуске программы и случайном нажатии кнопки Update не будет причинен ущерб данным.

5. Создайте код подпрограммы btnUpdate_Click, приведенный в листинге 4.6.

Листинг 4.6. Код обновления таблицы базы данных с помощью команды UPDATE с параметром
Private Sub btnUpdate_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnUpdate.Click
" Создание экземпляра объекта Connection.
Dim cnn As SqlConnection = New SqlConnection(_
"server=localhost;uid=sa;database=pubs")
" Создание экземпляра объекта Command.
Dim cmd As SqlCommand = New SqlCommand()
cmd.CommandType = CommandType.Text
cmd.CommandText = "UPDATE roysched SET royalty = royalty + @param1"
" Создание параметра и указание его значения.
cmd.Parameters.Add(New SqlParameter("@param1", SqlDbType.Int))
cmd.Parameters("@param1").Direction = ParameterDirection.Input
cmd.Parameters("@param1").Value = Val(txtParam1.Text)
" Открытие подключения перед вызовом метода ExecuteReader().
result = cmd.ExecuteNonQuery()
MessageBox.Show(result & " records updated", "DataProviderObjects")

Теперь таблицу с гонорарами авторов в базе данных pubs можно обновить, запустив приложение DataProviderObjects, задав новое значение гонорара в текстовом поле под кнопкой Update и щелкнув на этой кнопке. После этого на экране появится диалоговое окно с указанием количества охваченных записей. Этот результат можно проверить с помощью программы SQL Server Enterprise Manager, просматривая данные о гонорарах в таблице roysched до и после обновления.

Точно такое же обновление можно выполнить с помощью хранимой процедуры, что позволяет добиться более высокой производительности и централизованно хранить код. Потенциальным недостатком использования хранимых процедур является необходимость назначения администратора базы данных или специалиста с опытом создания хранимых процедур. В крупных организациях порой уходят целые дни на то, чтобы администратор базы данных изменил хранимые процедуры, которые можно самостоятельно изменить за несколько минут. Хранимые процедуры создаются с помощью программ SQL Server Enterprise Manager или SQL Query Analyzer, которые описаны в главе 3, "Знакомство с SQL Server 2000". Это можно также сделать с помощью проекта DataProviderObjects, изменив команду SQL, как это делалось ранее.

Итак, в данном примере хранимая процедура имеет следующий вид:

CREATE PROCEDURE UpdateRoyalties
UPDATE roysched SET royalty = royalty + @param1

В листинге 4.6 для организации вызова хранимой процедуры потребуется заменить свойства CommandText и CommandType объекта Command.

cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "UpdateRoyalties"

Запуск этой программы должен привести к тем же результатам, что и прежде, однако теперь обновление происходит благодаря хранимой процедуре, а не команде SQL из кода приложения.

Метод ExecuteScalar

Иногда нужно выполнить команду, которая возвращает скалярное значение, т.е. только одно значение. Типичными примерами являются команды SQL для вычисления суммы всех значений SUM и общего количества значений COUNT. Другими примерами являются справочные таблицы для подстановки одного значения или команды, возвращающие логическое значение. Метод ExecuteScalar выполняет заданную команду и возвращает значение первой записи из первого поля возвращенного набора записей, а все другие поля и записи игнорируются.

Включим приведенную ниже хранимую процедуру в базу данных pubs.

CREATE PROCEDURE AuthorsInState2
SELECT count(*) FROM authors WHERE state = @param1

Хранимая процедура AuthorsInState2 принимает параметр, который имеет двухсимвольный код штата, и возвращает из таблицы authors количество авторов из этого штата. Эта процедура с функциональной точки зрения эквивалентна процедуре AuthorsInState1 из листинга 4.5, но возвращает вычисленное значение для набора записей, а не только индикатор успешности выполнения команды.

НА ЗАМЕТКУ

Использование метода ExecuteScalar вместо метода ЕxecuteNonQuerу и передача скалярного значения с помощью параметра ReturnValue связаны с дополнительными накладными расходами. Зачем же его используют? Он проще в употреблении, потому что не нужно заботиться об указании параметров в определениях и вызовах команд.

Для вызова данной хранимой процедуры с помощью провайдера данных ODBC выполните следующее.

1. Создайте дополнительную кнопку под текстовым полем txtParam1.

2. В окне свойств Properties укажите значение cmdScalar для свойства (Name) и значение ExecuteScalar для свойства Text.

3. Создайте код подпрограммы btnExecuteScalar_Click, приведенный в листинге 4.7.

Листинг 4.7. Код извлечения скалярного значения из хранимой процедуры с помощью провайдера данных ODBC
Private Sub btnExecuteScalar_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnExecuteScalar.Click
" Создание экземпляра объекта Connection.
Dim cnn As OdbcConnection = New OdbcConnection(_
"DRIVER={SQL Server};server=localhost;uid=sa;database=pubs")
" Создание экземпляра объекта Command.
Dim cmd As OdbcCommand = New OdbcCommand()
" Указание подключения и текста команды.
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "{call AuthorsInState2(?) }"
" Создание параметра и указание его значения
cmd.Parameters.Add("@param1", OdbcType.Char, 2)
cmd.Parameters("@param1").Value = txtParam1.Text
MessageBox.Show("Count is " & result, "DataProviderObjects")

Запустите приложение и введите в текстовом поле над кнопкой ExecuteScalar двухсимвольный код штата. После щелчка на кнопке ExecuteScalar появится диалоговое окно с сообщением о количестве авторов в данном штате. Полученный результат можно проверить с помощью программы SQL Server Enterprise Manager, просматривая данные в таблице authors в базе данных pubs.

НА ЗАМЕТКУ

Учтите, что по умолчанию база данных pubs содержит двух авторов из штата Юта (код UТ) и 15 авторов из штата Калифорния (код СА).

Метод ExecuteReader

Этот метод применяется для возвращения набора записей. В большинстве приложений для работы с базами данных именно он используется чаще всего. Работа этого метода основана на объекте DataReader, с помощью которого записи обрабатываются последовательно одна за другой. Более подробно метод ExecuteReader и объект DataReader рассматриваются в следующем разделе.

Объект DataReader

Данный объект предназначен для чтения в прямом направлении небуферизуемого потока записей, полученных от метода ExecuteReader объекта Command. Объект DataReader в основном эквивалентен объекту Recordset модели ADO 2.X, который также предназначен для чтения в прямом направлении. Объект DataReader предлагает наиболее быстрый способ доступа к источнику данных, но в нем не предусмотрены возможности прокрутки и обновления данных. Поскольку данные не буферизуются и не сохраняются в кэше, этот метод прекрасно подходит для извлечения большого объема данных. Для перехода к следующей записи объекта DataReader нужно вызвать его метод Read.

Кроме коллекции Fields, доступ к полям каждой записи можно осуществить с помощью строго типизированных методов. Доступ к полям с помощью коллекции Fields аналогичен способу доступа к полям в модели ADO 2.X.

НА ЗАМЕТКУ

Объект DataReader не имеет явного конструктора, т.е. его нельзя создать с помощью оператора New(). Для инициализации нового объекта нужно вызвать метод ExecuteReader объекта Command.

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

В самом первом примере используется имя поля Myfield, что значительно упрощает его чтение, использование и совместимость с унаследованными программами. Во втором примере используется номер поля, что значительно повышает производительность, поскольку исключается преобразование имени поля в его номер.

Для демонстрации способов использования этих методов выполните следующее.

1. Создайте под кнопкой ExecuteScalar дополнительную кнопку.

2. В окне свойств Properties укажите значение cmdExecuteReader для свойства (Name) и значение ExecuteReader для свойства Text.

3. Создайте код подпрограммы btnExecuteReader_Click, приведенный в листинге 4.8.

НА ЗАМЕТКУ

Кроме способов использования объекта DataReader, в этом примере демонстрируются и другие функциональные возможности. Например, здесь помимо членов Text и StoredProcedure свойства-перечисления CommandType для указания типа команды используется член TableDirect. Он содержит имя таблицы, все поля которой возвращаются данной командой. Учтите, что этот член перечисления поддерживается только для провайдера данных ODBC.

Представления обычно обрабатываются точно также, как и таблицы. Следовательно, при использовании члена TableDirect свойства-перечисления CommandType можно указывать не только имя таблицы, но и имя представления.

Листинг 4.8. Код создания объекта DataReader и извлечения значений полей с помощью представления и члена TableDirect
Private Sub btnExecuteReader_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnExecuteReader.Click
" Создание экземпляра объекта Connection.
Dim cnn As OleDbConnection = New OleDbConnection(_
"provider=SQLOLEDB;server=localhost;uid=sa;database=pubs")
" Создание экземпляра объекта Command.
Dim As OleDbCommand = New OleDbCommand()
" Указание подключения и текста команды
cmd.CommandType = CommandType.TableDirect
cmd.CommandText = "EmployeeJobs_View"
" Открытие подключения перед вызовом метода
Dim reader As OleDbDataReader
txtResults.Text = txtResults.Text & reader("fname") & _

(Здесь предполагается, что представление EmployeeJobs_view уже создано с помощью подпрограммы btnNonQuery_Click из листинга 4.4, как описано выше. – Прим. ред.)

НА ЗАМЕТКУ

Перед попыткой доступа к данным объекта DataReader не забывайте вызывать метод Read(). В отличие от объекта Recordset в модели ADO 2.X, в которой после загрузки данных текущее расположение автоматически находится на первой записи, в модели ADO.NET в объекте DataReader нужно явно указать текущее расположение возле первой записи с помощью исходного вызова метода

Для организации доступа к данным можно также применить цикл While с более эффективными строго типизированными методами доступа к полям.

txtResults.Text = txtResults.Text & reader.GetString(1) & _
ControlChars.Tab & reader.GetString(2) & _
ControlChars.Tab & ControlChars.Tab & _
reader. GetString(0) & ControlChars.Ctrlf

Еще одно изменение, которое диктуется личным вкусом и стилем программирования автора, заключается в объединении определения объекта DataReader и выполнения метода ExecuteReader в одной строке, т.е. вместо фрагмента кода

Dim reader As OleDbDataReader reader = cmd.ExecuteReader()

можно использовать следующую строку:

Dim reader As OleDbDataReader = cmd.ExecuteReader()

После запуска приложения DataProviderObjects щелкните на кнопке ExecuteReader, и в текстовом поле справа будут отображены данные из представления EmployeeJobs_view, как показано на рис. 4.4.


РИС. 4.4. Результаты выполнения команды ExecuteReader из листинга 4.8


НА ЗАМЕТКУ

По окончании использования объекта DataReader следует вызвать метод Close. Дело в том, что выходные данные или возвращаемые значения объекта Command недоступны до тех пор, пока объект DataReader открыт. Он остается открытым до тех пор, пока открыто само подключение или объект DataReader.

Объект DataReader также предлагает простой и эффективный способ создания Web-страниц для работы сданными на основе элемента управления DataGrid, который подробно рассматривается в главе 11, "Web-формы: приложения на основе ASP.NET для работы с базами данных".

Использование объектов Connection и Command во время создания приложения

Вкладка Data панели элементов управления среды Visual Studio .NET содержит компоненты, которые соответствуют некоторым методам доступа к данным. С помощью окна Properties они позволяют указывать значения свойств во время создания приложения, а не только во время его выполнения. Они также предлагают визуальные инструменты указания значений для более сложных свойств.

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

1. Создайте еще одну форму Form2 в проекте DataProviderObjects.

2. В окне свойств Properties укажите значение Connection and Command Components для свойства Text формы Form2.

3. Увеличьте размер формы.

4. Включите в форму текстовое поле Textbox1.

5. В окне свойств Properties укажите значение True для свойства Multiline и значение Both для свойства Scrollbars этого текстового поля.

6. Увеличьте размер текстового поля так, чтобы оно покрывало большую часть формы.

7. Из вкладки Data панели элементов управления перетащите элемент управления OleDbConnection в форму Form2. Этот компонент невидим во время выполнения приложения, поэтому он появится в разделе невизуальных компонентов в нижней части окна редактирования формы.

8. В окне свойств Properties укажите приведенное ниже значение для свойства ConnectionString элемента управления OledbConnection1.

provider=SQLOLEDB;server=localhost;uid=sa;database=pubs

9. Из вкладки Data панели элементов управления перетащите еще один элемент управления OleDbCommand в форму Form2. Этот компонент также невидим во время выполнения приложения, поэтому он появится в нижней части окна редактирования формы.

10. В окне свойств Properties укажите значение OledbConnection1 для свойства Connection и приведенное ниже значение для свойства CommandText элемента управления OledbCommand1.

SELECT * FROM EmployeeJobs_view

11. Создайте код подпрограммы Form2_Load, приведенный в листинге 4.9.

СОВЕТ

В главе 6, "ADO.NET: объект DataAdapter" представлены графические инструменты, которые позволяют автоматически создать строку подключения и текст команды SQL вместо создания вручную их кода.

Листинг 4.9. Код создания объекта DataReader и извлечения значений полей с помощью компонентов SqlConnection и SqlCommand
Private Sub Form2_Load(ByVal sender As System.Object, _
" Открытие подключения перед вызовом метода ExecuteReader.
Dim reader = OleDbCommand1.ExecuteReader()
TextBox1.Text = TextBox1.Text & reader("fname") & _
ControlChars.Tab & reader("lname") & _
ControlChars.Tab & ControlChars.Tab & _
reader("job_desc") & ControlChars.CrLf
" Отмена выбора всех строк в текстовом поле.

12. Щелкните правой кнопкой мыши на проекте DataProviderObjects в окне Solution Explorer и выберите команду Properties в контекстном меню.

13. В папке Common Properties выберите элемент General, а затем выберите форму Form2 в текстовом поле Startup object (Объект запуска) данного приложения.

После запуска приложения DataProviderObjects в текстовом поле формы Form2 будут отображены данные из представления EmployeeJobs_view, как показано на рис. 4.5.

Другие провайдеры данных

Выше были представлены классы нескольких основных провайдеров данных (например, Parameter и Parameters), а также четыре основных объекта в табл. 4.1. В главе 5, "ADO.NET: объект DataSet", более подробно рассматривается объект DataSet и связанные с ним объекты, а в главе 6, "ADO.NET: объект DataAdapter", – объект DataAdapter.

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


РИС. 4.5. Результаты отображения данных в форме Form2 с помощью элементов управления OleDbConnection и OleDbCommand


Провайдеры данных ADO.NET содержат объект Transaction, который имеет фундаментальные методы обработки транзакций. Метод Commit фиксирует текущую транзакцию, а метод Rollback – откатывает (отменяет) текущую транзакцию. Выполнение транзакции и создание объекта Transaction осуществляется с помощью вызова метода BeginTransaction по отношению к открытому объекту Connection. Способ использования объекта Transaction демонстрируется на примере бизнес-ситуации 4.1.

Бизнес-ситуация 4.1: создание процедуры для архивирования старых заказов по годам

После относительно длительного использования системы управления базами данных некоторые данные рекомендуется архивировать. В каждой рабочей системе операцию архивирования следует включить в состав обязательных и регулярно выполняемых операций резервного копирования. Архивируемые данные - это данные, которые нужны не для постоянного использования (т.е. в оперативном режиме), а только изредка. Удаление этих данных из основных оперативных таблиц базы данных может повысить производительность операций доступа к этим таблицам, так как при этом приходится обрабатывать и фильтровать меньше записей. Однако архивная таблица часто хранится в идентичном формате таблицы и доступ к ней в случае необходимости можно организовать аналогичным образом. В этой бизнес-ситуации создается простая форма для выполнения простого архивирования данных из таблицы tblOrder базы данных Novelty. Она позволит выбирать и архивировать заказы по годам, т.е. после выбора нужного года выполняются перечисленные ниже действия.

1. Сначала в базе данных создается новая таблица tblOrderXXXX, где ХХХХ обозначает тот год, записи о заказах которого будут архивироваться.

2. Затем все записи о заказах за указанный год копируются из таблицы tblOrder втаблицу tblOrderXXXX.

3. Все скопированные записи о заказах за указанный год удаляются из таблицы tblOrder.

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

Запустите интегрированную среду разработки Visual Studio .NET.

2. Создайте новый проект Visual Basic Windows Application.

3. Назовите проект BusinessCase4.

4. Укажите путь к файлам проекта.

5. Увеличьте размер формы Form1.

6. В окне Properties укажите значение frmArchive для свойства (Name) и значение Archive Orders для свойства Text формы Form1.

7. Создайте в форме поле со списком lstYears, надпись Label1, кнопку bntOK и кнопку btnCancel, перетаскивая их из панели элементов управления.

8. В окне Properties укажите значение Archive all orders for the year для свойства Text надписи, значение OK для кнопки btnOK и значение Cancel для кнопки btnCancel.

9. Расположите все элементы управления, как показано на рис. 4.6.

РИС. 4.6. Расположение элементов управления в форме frmArchive


В верхней части файла с исходным кодом вставьте приведенную ниже строку кода для импорта пространства имен SqlClient.

В теле определения класса для формы frmArchive включите код из листинга 4.10.

Листинг 4.10. Код архивирования данных в новой таблице
Private Sub frmArchive_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
" Указание значения по умолчанию.

Private Sub btnCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnCancel.Click

Private Sub btnOK_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnOK.Click
" Создание экземпляров объектов Connection и Command.
Dim cnn As SqlConnection = New SqlConnection(_
"server=localhost;uid=sa;database=novelty")
" Получение значения года.
SelectedYear = lstYears.SelectedItem.ToString
" Размещение кода внутри блока Try-Catch для
" обработки исключительных ситуаций.
" Открытие объекта Connection и запуск транзакции.
" Включение команды в транзакцию.
" Указание команды SQL для вставки соответствующих
" записей в архивную таблицу.
sql = "SELECT * INTO tblOrder" & SelectedYear & _
FROM tblOrder WHERE year (OrderDate) = " & SelectedYear
" Передача текста команды SQL в транзакцию.
result = cmd.ExecuteNonQuery()
" Отображение результатов вставки записей в архивную таблицу.
records = result MessageBox.Show(records & _
" records inserted successfully into tblOrder" & SelectedYear)
"No records inserted into tblOrder" & SelectedYear)
" При отсутствии записей созданная таблица
" не нужна и транзакцию нужно откатить.
" Команда SQL для удаления соответствующих
" записей из текущей таблицы.
sql = "delete FROM tblOrder WHERE year (OrderDate) = " _
" Эта команда находится в той же транзакции.
result = cmd.ExecuteNonQuery()
" Показать результаты удаления записей.
" records deleted successfully")
" Все действия успешно выполнены, можно фиксировать транзакцию.

Подпрограмма btnCancel_Click обработки щелчков мышью на кнопке Cancel просто закрывает форму, что в данном случае приводит к закрытию программы. Все необходимые действия выполняются обработчиком щелчков мышью на кнопке OK. После объявлений переменных следует получить выбранный год из списка lstYears и сохранить его для дальнейшего использования. Для гарантированной отмены транзакции в случае возникновения любой исключительной ситуации следует окружить активный код блоком Try-Catch-Finally.

Поскольку транзакции определяются на уровне подключения, то сначала нужно открыть подключение, а затем создать объект Transaction с помощью вызова метода BeginTransaction для открытого подключения. Объекты Connection и Transaction присваиваются объекту Command, который будет использоваться для выполнения команд по отношению к базе данных.

Первые два этапа создания архивной таблицы и копирования выбранных строк выполняются с помощью одной команды SELECT, которая содержит предложение INTO имя_таблицы. Указанная таким образом таблица создается автоматически, а если такая таблица уже существует, то генерируется исключительная ситуация. Выбранное значение года добавляется к имени таблицы tblOrder для создания имени новой архивной таблицы.

НА ЗАМЕТКУ

Команда SELECT INTO не создает индекс, если он существует в исходной таблице. Для повышения производительности выполнения запросов по отношению к данной таблице, вероятно, придется создать индексы по одному или нескольким полям.

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

Если хотя бы одна запись скопирована в таблицу, то ее прототип в исходной таблице tblOrder удаляется с помощью команды DELETE, содержащей предложение WHERE с заданным годом. В случае успешного выполнения этой команды, т.e. если количество скопированных и удаленных строк совпадает, транзакция считается успешно завершенной и фиксируется. В противном случае, т.е. если какая-то отдельная операция завершится неудачно (нарушится процесс удаления записей, будет отменено разрешение на удаление архивируемых данных или произойдет сбой сервера), вся транзакция будет отвергнута. Откат транзакции гарантирует, что при неудачной попытке удаления корректных записей из таблицы tblOrder архивная таблица tblOrderХХХХ будет удалена.

До сих пор рассматривались только те исключительные ситуации, которые возникают при нарушении последовательного выполнения операций подпрограммы. Однако следует также позаботиться о многих других исключительных ситуациях, которые могут возникнуть во время выполнения. Например, исключительная ситуация может возникнуть при попытке создания уже существующей таблицы. Для ее перехвата и обработки следует использовать блок Try-Catch, который в данном случае выводит текстовое сообщение об исключительной ситуации, а вся транзакция отвергается.

НА ЗАМЕТКУ

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

В блоке Finally используемое подключение закрывается независимо от того, произошла или нет исключительная ситуация.

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

Резюме

В этой главе приводятся общие сведения об ADO.NET и некоторых объектах провайдеров данных.NET. Провайдеры данных образуют интерфейс ADO.NET для взаимодействия с физическими хранилищами данных и предлагают модель программирования в режиме подключения. Здесь кратко рассматриваются свойства и методы объектов Connection, Command, Parameter, DataReader и Transaction, включая стандартные провайдеры данных SqlClient, OleDb и Odbc. В главе 5, "ADO.NET: объект DataSet", описываются способы работы с данными в отключенном режиме на основе объектов DataSet и DataAdapter.

Вопросы и ответы

Судя по содержанию этой главы, модель ADO.NET предназначена для работы в отключенном режиме и нет никакой поддержки для серверных курсоров или пессимистической блокировки. А что же делать, если уже существующее приложение использует их или спецификации нового проекта требуют их применения? Следует ли мне использовать для этого только Visual Basic 6.0?

Во-первых, необходимо тщательно проанализировать приложение и убедиться в том, что действительно всегда нужно использовать серверные курсоры или пессимистические блокировки. Если они (или какие-то другие компоненты, которые не поддерживаются в ADO.NET) действительно необходимы, не стоит отчаиваться. Visual Basic. NET все равно можно использовать для таких приложений, так как на платформе.NET Framework предусмотрена расширенная поддержка взаимодействия с COM, что позволяет использовать в.NET-совместимом приложении COM-объекты, которые, в свою очередь, могут использовать управляемый (.NET-совместимый) код. Иначе говоря, можно использовать не только ADO 2.X, но и любые другие COM-объекты, для которых нет аналогов в.NET. Конечно, за организацию взаимодействия между COM и.NET придется заплатить снижением производительности. В какой мере? Ответ на этот вопрос можно получить только после тщательного тестирования приложения.

Похоже, что программирование объектов, методов и свойств ничем не отличается от способов их программирования в модели ADO 2.X. Зачем же переходить к модели ADO.NET?

Конечно, выполнение основных операций подключения к базе данных не очень отличается от таких же операций в модели ADO 2.X. Однако, кроме уже упомянутых здесь небольших усовершенствований, следует иметь в виду ряд важных отличий.

1. Visual Basic .NET и платформа.NET представляют собой совершенно новый мир, а модель ADO.NET является способом доступа к данным в этом мире.

2. Хотя при создании.NET-совместимых приложений можно продолжать использование уже существующих COM-компонентов, например из модели ADO 2.X, такой способ связан с сокращением производительности при доступе к COM-компонентам и необходимости их корректной инсталляции и регистрации.

3. Рассмотренные в этой главе объекты разных провайдеров данных отвечают только за часть всех операций с базой данных, а именно за чтение и запись данных в подключенном режиме. Другая часть операций выполняется в отключенном режиме и связана с объектом DataSet. Именно в этом режиме прекрасно проявляются преимущества модели ADO.NET, и потому в следующей главе описываются базовые блоки объекта DataSet.


Центр обработки и хранения данных под ключ от компании

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

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

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

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

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

ПРИМЕЧАНИЕ

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

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

ПРИМЕЧАНИЕ

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

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

    Microsoft Data Access Components (MDAC);

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

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

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

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

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

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

MDAC (Microsoft Data Access Components)

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

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

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

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

Драйвер

Провайдер

Описание

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

Microsoft.Jet.OLEDB.3.5

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

Microsoft.Jet.OLEDB.4.0

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

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

Базы данных Oracle

Online Analytical Processing

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Компонент dbGo

Описание

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

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

База данных

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

Компонент ADOConnection

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

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

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

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

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

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

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

ShowMessage(ADOTable1.Properties.Value);

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

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

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

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

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

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

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

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

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

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

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


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

var Filter: OLEVAriant; begin Filter:= VarArrayCreate(, varVariant); Filter := "CUSTOMER"; ADOConnection1.OpenSchema(siPrimaryKeys, Filter, EmptyParam, ADODataSet1); end ;

ПРИМЕЧАНИЕ

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

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

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

ПРИМЕЧАНИЕ

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

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

HKEY_LOCAL_MACHINE\Software\Microsoft\Jet\4.0\ISAM Formats

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

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

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

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

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

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

ADOConnection1.Execute ("SELECT * INTO IN "" + CurrentFolder + "" "HTML Export;" FROM CUSTOMER");

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ПРИМЕЧАНИЕ

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

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

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

ПРИМЕЧАНИЕ

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

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

ADOTable2.Clone(ADOTable1);



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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ADODataSet1.UpdateBatch;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ПРИМЕЧАНИЕ

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

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

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

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

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

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

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

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

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

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

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

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

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