В предыдущей главе было дано общее представление об объектах ADO и подробно рассмотрены свойства, методы и события трех основных объектов - Connection, Command, Recordset. Но я хочу продолжить разговор и дать представление не только о главных героях, но и всех остальных объектах этой модели. Но прежде чем рассматривать новые объекты, давайте разберемся в таком важном вопросе, как обрабатываются события, возникающие с объектами ADO, и о том, как справляться с ошибками, возникающими в процессе работы с этими объектами. Я напомню, что событиями обладают лишь два объекта - Recordset и Connection.
Давайте разберемся с деталями построения обработчиков событий объектов ADO. Прежде всего, рассмотрим, как они создаются, в какой модуль проекта их следует поместить. Хотя объекты Recordset и Connection обладают событиями, но появляются они как объекты без событий. Необходимо предпринять обычные для VBA в таких ситуациях действия, чтобы создать объекты с событиями и написать процедуры, обрабатывающие события, - обработчики событий. В первом томе и других книгах серии "Офисное программирование" я подробно рассказывал о том, как это делается. Напомню, что для решения этой задачи нужно выполнить три шага:
MyEventsADO, в котором описать вложенные объекты - Connection WithEvents и Recordset WithEvents.MyEventsADO. Заметьте, после объявления переменных With Events в этом классе появится возможность написания обработчиков событий, следуя обычной технологии.TestingADO, создать экземпляры класса MyEventsADO и связать их с глобальными или локальными объектами Rst1 и Con1, задающими набор записей и соединение.Чтобы сделать дальнейшее описание конкретным, давайте рассмотрим создание обработчика события WillChangeField объекта Recordset, которое, напомню, появляется при попытках изменить значение поля в записи набора, но перед тем, как это изменение реально произойдет. Вот как выглядит модуль класса MyEventsADO, содержащий обработчик этого события:
'Класс MyEventsADO, определяющий события ADO Public WithEvents EvRst As ADODB.Recordset Public WithEvents EvCon As ADODB.Connection Private Sub EvRst_WillChangeField(ByVal cFields As Long, _ ByVal Fields As Variant, adStatus As ADODB.EventStatusEnum, _ ByVal pRecordset As ADODB.Recordset) 'Печать параметров обработчика события MsgBox "Номер поля = " cFields vbCrLf _ "Значение поля = " Fields(cField) vbCrLf _ "Позиция записи в наборе = " _ pRecordset.AbsolutePosition vbCrLf _ "Статус выполняемой операции = " adStatus 'Изменение статуса приведет к появлению ошибки 'при попытке изменить значение поля, обработка которой 'позволит отменить операцию. If IsNumeric(Fields(cField)) Then If Fields(cField) > 75 Then adStatus = adStatusCancel End If End Sub
Первым делом я включил в обработчике события WillChangeField печать всех его параметров на входе. Обратите внимание, обработчику события передается указатель на сам объект Recordset, поэтому доступна практически любая информация, связанная с этим объектом. Я, например, использовал его, чтобы вывести на печать позицию записи в наборе, поля которой обновляются. Во второй части обработчика я изменяю значение статуса при выполнении некоторого условия на обновляемое поле. Делаю это для того чтобы реализовать возможность отмены изменения значения, когда в результате анализа, проведенного в обработчике, принимается решение о нежелательности выполнения изменений. Заметьте, для этого свойству статус, которое имело значение adStatusOk, уведомляющее о том, что изменения возможны, я присваиваю значение adStausCancel. В этом случае при выходе из обработчика возникнет ошибка в операторе, производящем изменения. Обработав соответствующим образом эту ошибку, я смогу выполнить поставленную задачу.
Давайте теперь рассмотрим процедуру, в которой происходит обновление полей записей набора, чьи действия будут вызывать появление события WillChangeField. Эта процедура изменяет цену у некоторых книг из таблицы "Книги" нашей тестовой базы данных и добавляет новую запись в эту таблицу. При всех этих изменениях начнут работать события:
Public Sub CreateEvents() 'Объект с событиями Dim imEvRst As New MyEventsADO 'добавление и изменение записей базы данных Dim recExist As Boolean ' Связывание объекта с событиями Set imEvRst.EvRst = Rst1 'Создать соединение CreateConnection 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic 'Изменение записей recExist = False .MoveFirst Do While Not .EOF 'Обработка текущей записи On Error Resume Next If !Название = "Офисное программирование" Then recExist = True If ![Год издания] < 2000 And !Цена < 100 Then MsgBox "Старая цена =" Str(!Цена) !Цена = !Цена * 2 .Update End If .MoveNext Loop If Not recExist Then .AddNew !Автор = "Владимир Биллиг" !Название = "Офисное программмирование" ![Год издания] = 2001 ![Число страниц] = 599 !Цена = 150 .Update End If End With End Sub
Обратите внимание, первым делом я объявляю объект imEvRst созданного класса MyEventsADO и связываю его с глобальным объектом Rst1, после чего последний начнет реагировать на события. Сколько раз будет появляться событие WillChangeField? Очевидно, при каждом выполнении оператора !Цена = !Цена * 2, изменяющего значение соответствующего поля записи. Вот как выглядят окошки функции MsgBox, одно из которых открывается перед выполнением этого оператора, а второе в обработчике события в момент выполнения оператора:
(рис 6.1) Окна, открываемые перед изменением поля и в момент измененияСоответствующие сообщения появляются и при создании новой записи, причем столько раз, сколько полей имеет запись.
Важную роль в этой процедуре играет и оператор On Error Resume Next. Всякий раз, когда в обработчике события изменяется значение переменной adStatus, возникнет ошибка при выполнении оператора, изменяющего значение поля и послужившего причиной возникновения события. Оператор OnError позволяет в этом случае обойти выполнение оператора и, тем самым, избежать изменения значения, что и хотелось. Взгляните, как выглядит окно, уведомляющее об ошибке, появляющееся, если в процедуре закомментировать оператор On Error:
(рис 6.2) Окно, уведомляющее о возникновении ошибки при изменении значенияХочу теперь рассмотреть пример построения обработчика события, возникающего после завершения операции. Пример хочу построить такой, чтобы при выполнении операции Провайдер столкнулся с трудностями и сформировал объекты Error, а обработчик события соответственно обрабатывал эти ошибки. Но прежде чем перейти к рассмотрению этого примера, есть смысл более подробно познакомиться с объектом Error - его свойствами и методами.
Напомню, что элементы коллекции Errors появляются при выполнении очередной команды, когда Провайдер сталкивается с определенными трудностями и не может нормальным образом завершить выполнение команды. Коллекция Errors всегда связана только с одной командой, - при первой ошибке в новой команде происходит чистка старого содержимого Errors объекта Connection возвращает эту коллекцию. Добавлять программно элементы в коллекцию невозможно, это делается автоматически, когда Провайдер обнаруживает новую ошибку.
У коллекции два свойства:
Count - возвращает число элементов коллекции,Item - свойство по умолчанию позволяет по индексу получить необходимый элемент коллекции - объект Error.У коллекции два метода:
Clear - позволяет принудительно очистить содержимое коллекции,Refresh - обновляет коллекцию.У объекта Error нет методов и нет событий. У него есть только свойства:
Property Description As String (read-only). Задает описание ошибки, которое можно показать пользователю, если не предвидится другой способ обработки ошибки.Property HelpContext As Long (read-only), Property HelpFile As String (read-only). Эти свойства позволяют обратиться за дополнительными разъяснениями к справочной системе, если в ней содержится более подробная информация об ошибке.Property NativeError As Long (read-only). Код ошибки, специфический для данного Провайдера. Полезен, если есть описание ошибок Провайдера.Property Number As Long (read-only). Номер, однозначно идентифицирующий ошибку. Является константой типа HRESULT. Значения соответствуют, но не совпадают со значениями констант, принадлежащих перечислению ErrorValueEnum. Классификация ошибок достаточно подробная, констант в этом перечислении много. Приведу значения лишь некоторых: adErrCantChangeConnection, adErrCantConvertvalue, adErrDataOverflow, adErrFieldsUpdateFailed, adErrInTransaction. Замечу, что, зная номер Number, не просто разобраться, какой тип ошибки имеет место.Property Source As String (read-only). Задает имя объекта, породившего ошибку. В совокупности свойства Source, Number и Description позволяют проанализировать ошибку и, возможно, в диалоге с пользователем устранить ее причину.Property SQLState As String (read-only). Задает 5-и символьный код, соответствующий стандарту ANSI SQL, определяющий состояние SQL, вызвавшее ошибку.Я хочу теперь рассмотреть ситуации, когда при выполнении операции по тем или иным причинам возникают ошибки. Мы рассмотрим, как выглядят обработчики ошибок в таких ситуациях, как можно проанализировать содержимое объектов Error, созданных Провайдером и переданных обработчику ошибок, как можно исправить ситуацию и продолжить выполнение.
Первым делом я смоделировал ситуацию, приводящую к ошибке в момент создания соединения, для чего сменил свойство " Provider ", не позаботившись о корректном задании характеристик этого Провайдера.
Вот как выглядит модернизованная процедура, организующая соединение:
Public Sub ConnectionWithEvents() 'Создание соединения с тестовой базой данных Access 'Объект с событиями Dim imEvCon As New MyEventsADO ' Связывание объекта с событиями Set imEvCon.EvCon = Con1 Dim strConnStr As String If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение 'Вариант 1 'Конфигурирование соединения Con1 'Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.Provider = "MSDASQL" Con1.ConnectionString = "Data Source=c:\!O2000\DsCd\Ch14\dbPP2000.mdb" Con1.CursorLocation = adUseClient 'Открытие соединения On Error GoTo Check Con1.Open Exit Sub Check: 'Повтор операции соединения Resume End Sub
В этой процедуре следует обратить внимание на следующие моменты:
imEvCon класса MyEventsADO, о создании которого я говорил выше.В класс MyEventsADO я добавил следующий обработчик события ConnectComplete:
Private Sub EvCon_ConnectComplete(ByVal pError As ADODB.Error, _ adStatus As ADODB.EventStatusEnum, _ ByVal pConnection As ADODB.Connection) 'Печать параметров обработчика события MsgBox "Обработчик события ConnectComplete! " vbCrLf _ "Строка соединения = " _ pConnection.ConnectionString vbCrLf _ "Статус выполняемой операции = " adStatus 'Печать параметров объекта Error If Not (pError Is Nothing) Then With pError MsgBox "Описание ошибки:" .Description vbCrLf _ "Код Провайдера:" .NativeError vbCrLf _ "Константа, идентифицирующая ошибку:" .Number vbCrLf _ "Имя объекта:" .Source vbCrLf _ "ANSI SQL код:" .SqlState End With End If If adStatus = adStatusErrorsOccurred Then 'Исправление ошибки pConnection.Provider = "Microsoft.jet.oledb.4.0" End If 'Анализ коллекции Errors Dim myErr As Error Dim MyCon As Connection Set MyCon = pConnection Debug.Print MyCon.Errors.Count For Each myErr In MyCon.Errors Debug.Print myErr.Description Next myErr MyCon.Errors.Clear Debug.Print MyCon.Errors.Count MyCon.Errors.Refresh Debug.Print MyCon.Errors.Count For Each myErr In MyCon.Errors Debug.Print myErr.Description Next myErr End Sub
Прокомментирую работу этой процедуры:
ConnectComplete работает тогда, когда обработчику передается объект Error. В функции MsgBox, окно которой также показано на рис. 6.3, формируется информация о свойствах объекта Error, в частности, описание ошибки показывает, что ODBC-Провайдер не сумел найти источник данных.ConnectionWithEvents в оператор Con1.Open, вызвавший событие, то из-за возникновения ошибки в Check. ConnectComplete, но в ситуации, когда ошибка не возникает. В этот раз будет работать только первая функция MsgBox, которая уведомит о нормальном завершении и выдаст подробную информацию в строке соединения.ConnectComplete выводится на печать отладочная информация о коллекции Errors объекта Connection. Я специально поместил ее в эту процедуру, чтобы обратить Ваше внимание на тот факт, что при обработке события Complete коллекция Errors объекта Connection еще не сформирована, она создается лишь после завершения работы этой процедуры. Поэтому, как ни странно, но при первом вызове, когда ошибка есть, коллекция Errors пуста, а во втором вызове, когда ошибки нет, коллекция существует, сохраняя историю первого вызова. Заметьте, по этой причине было бы разумнее с содержательной точки зрения поместить эту группу операторов в блок с меткой Check. Иллюстрацией к этому тексту являются результаты отладочной печати:0 0 0 1 [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified 0 0
Еще одна ошибка, которую я решил смоделировать, возникает при работе с объектом Recordset, - некорректно формируется значение поля при изменении записи. К уже имеющемуся обработчику события WillChangeField я добавил обработчик события ChangeFieldComplete. Поскольку общая схема экспериментов остается такой же, как и для объекта Connection, то я приведу тексты процедур без дополнительных комментариев. В уже описанной процедуре CreateEvents я заменил оператор
!Цена = !Цена * 2
на оператор
!Цена = "По Договоренности"
Эта замена приводит к ошибке несоответствия типов. Вот текст обработчика события ChangeFieldComplete:
Private Sub EvRst_FieldChangeComplete(ByVal cFields As Long, _ ByVal Fields As Variant, ByVal pError As ADODB.Error, _ adStatus As ADODB.EventStatusEnum, _ ByVal pRecordset As ADODB.Recordset) 'Печать параметров обработчика события MsgBox "Номер поля = " cFields vbCrLf _ "Значение поля = " Fields(cField) vbCrLf _ "Позиция записи в наборе = " _ pRecordset.AbsolutePosition vbCrLf _ "Статус выполняемой операции = " adStatus 'Печать параметров объекта Error If Not (pError Is Nothing) Then With pError MsgBox "Описание ошибки:" .Description vbCrLf _ "Код Провайдера:" .NativeError vbCrLf _ "Константа, идентифицирующая ошибку:" .Number vbCrLf _ "Имя объекта:" .Source vbCrLf _ "ANSI SQL код:" .SqlState End With End If 'Анализ коллекции Errors Dim myErr As Error Dim MyCon As Connection Set MyCon = pRecordset.ActiveConnection Debug.Print MyCon.Errors.Count For Each myErr In MyCon.Errors Debug.Print myErr.Description Next myErr End Sub
Вот как выглядит серия появляющихся MsgBox, вызываемой в процедурах: CreateEvents, WillChangeField, ChangeFieldComplete, при попытке изменить поле "Цена":
(рис 6.4) Серия окон, появляющихся при попытке изменить значение поля.Возможно, полезно взглянуть и на информацию, появляющуюся в окне отладки:
0 1 Operation was canceled. 1 Operation was canceled. 1 Multiple-step operation generated errors. Check each status value.
Свойства, методы и события основных объектов ADO - Connection, Command, Recordset, Error уже рассмотрены. В примерах появлялись и другие объекты - Field, Parameter, Property и их коллекции. Рассмотрим свойства и методы этих и других объектов ADO, но уже с меньшей степенью детализации.
Этот объект представляет отдельную запись набора Recordset. Поскольку, однако, объект Recordset не имеет свойства Records, возвращающее коллекцию записей, ни свойства Item, возвращающего отдельную запись, то явным способом получить этот объект из набора записей не удается. Некоторые Провайдеры возвращают объект Record в тех случаях, когда в результате запроса результатом является объект Recordset, состоящий из одной записи. Обычно создается этот объект методом Open, имеющим привычный для объектов ADO синтаксис:
Sub Open([Source], [ActiveConnection], [Mode As ConnectModeEnum], [CreateOptions As RecordCreateOptionsEnum = adFailIfNotExists], [Options As RecordOpenOptionsEnum = adOpenRecordUnspecified], [UserName As String], [Password As String])
При открытии задается источник данных, соединение, устанавливаются параметры открытия и при необходимости задаются имя пользователя и пароль доступа к данным. Заметьте, источники могут быть различных типов. Объект Record хотя и подобен набору записей, состоящему из одной записи, но имеет все-таки отличающийся набор свойств и методов. Этот объект часто используют при доставке данных из Интернета, поскольку его значениями могут быть файл или каталог. Некоторые Провайдеры поддерживают объекты Record и Stream как альтернативу объекту Recordset или как дополнительные объекты, позволяющие манипулировать данными, поступающими от Провайдеров. В роли Провайдера часто выступает Microsoft OLE DB Provider for Internet Publishing. Этот Провайдер позволяет использовать объект Record для управления данными, такими как каталоги и папки файловой системы, а также сообщениями в системе электронной почты. Источником данных в этом случае выступает абсолютный или относительный адрес URL, сочетаемый с объектом Connection.
Этот объект с успехом может использоваться при работе с данными, имеющими иерархическую структуру. С одной стороны его родителем может быть объект Recordset. С другой стороны, объект Recordset может быть его потомком, поскольку метод GetChildren объекта Record возвращает в качестве результата объект Recordset. Чуть позже я приведу пример такого взаимодействия этих двух объектов.
Большая часть свойств объекта Record совпадает со свойствами объекта Recordset. К таковым относятся свойства: ActiveConnection, Source, Fields, Properties, State. То, что эти два объекта имеют свойство Fields, возвращающее коллекцию полей записей, более всего роднит эти объекты. Еще одно свойство Mode, имеющее значениями константы из перечисления ConnectModeEnum, является общим с объектом Connection. Особых свойств у этого объекта два:
RecordType задает тип объекта, его значениями являются константы из перечисления RecordTypeEnum: adCollectionRecord, adSimpleRecord, adStructDoc, имена которых достаточно точно отражают смысл типа объекта.ParentURL определяет URL-адрес родителя объекта Record.Методов у объекта Record немного. О центральном методе Open, позволяющем создать объект я уже сказал. Кроме этого есть типичные для всех объектов методы Close и Cancel. Три метода CopyRecord, MoveRecord и DeleteRecord выполняют типичные операции над этим объектом. О специфическом методе GetChildren скажу чуть подробнее. Его синтаксис:
Function GetChildren() As Recordset
Записи возвращаемого объекта Recordset являются потомками текущего объекта Record. Например, потомками объекта Record, представляющего собой каталог, могут быть файлы и подкаталоги, содержащиеся внутри родительского каталога.
Рассмотрим теперь пример, в котором в роли Провайдера выступает Internet Publishing Provider, а участниками будут объекты Record, Recordset и Stream. Я поочередно создам два объекта Record, в первый раз для чтения текстового файла и передачи этих данных объекту Stream. Во втором случае объект Record будет представлять каталог, а созданный на его основе объект Recordset будет содержать имена файлов этого каталога. Вот процедура, решающая эту задачу:
Public Sub CreateRecord() 'Создание и работа с объектом Record Dim FirstRec As New Record Dim fld As Field Dim strText As String 'Create Connection with Internet Publishing Provider If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение Con1.Open "Provider = MSDAIPP.DSO; " _ "Data Source = http://serverva/proba2_2/testtext.txt" 'Объекту Record передается текстовый файл FirstRec.Open ActiveConnection:=Con1 'Поля объекта Record Debug.Print "Число полей:", FirstRec.Fields.Count For Each fld In FirstRec.Fields Debug.Print "Имя поля: ", fld.Name, _ "Значение поля:", fld.Value, "Тип:", fld.Type Next fld 'Создание объекта Stream на основе объекта Record If Strm1.State = adStateOpen Then Strm1.Close Strm1.Open Source:=FirstRec, Mode:=adModeRead, _ Options:=adOpenStreamFromRecord strText = Strm1.ReadText(100) Debug.Print "Текстовый файл:", strText Con1.Close 'Новое соединение и открытие объекта Record 'Объекту Record передается каталог Con1.Open "Provider = MSDAIPP.DSO; " _ "Data Source = http://serverva/" FirstRec.Open ActiveConnection:=Con1, _ Options:=adOpenSource, Mode:=adModeRead 'Поля объекта Record Debug.Print "Число полей:", FirstRec.Fields.Count For Each fld In FirstRec.Fields Debug.Print "Имя поля: ", fld.Name, _ "Значение поля:", fld.Value, "Тип:", fld.Type Next fld 'Создание объекта Recordset Set Rst1 = FirstRec.GetChildren Debug.Print Rst1.ActiveConnection Debug.Print Rst1.RecordCount Rst1.MoveFirst Do While Not Rst1.EOF 'Обработка текущей записи Debug.Print Rst1(2).Name, Rst1(2).Value Rst1.MoveNext Loop Con1.Close End Sub
Приведу комментарии к этой программе:
Proba2_2. Этот каталог установлен под Internet Information Server на моем компьютере. Соответствующие строки программного текста подсвечены.FirstRec класса Record методом Open, которому в качестве параметра передается имя открытого соединения. В этот момент данные из текстового файла передаются объекту FirstRec.Fields. Полей у этого объекта в данном контексте достаточно много - 26. Приведу имена и значения пяти полей:RESOURCE_PARSENAME - testtext.txt RESOURCE_PARENTNAME - http://serverva/proba2_2 RESOURCE_ABSOLUTEPARSENAME - http://serverva/proba2_2/testtext.txt RESOURCE_ISHIDDEN - False RESOURCE_CONTENTCLASS - text/plainВ данном контексте большая часть полей, но не все, определяет характеристики ресурса.
FirstRec задает теперь текстовый файл, то он может в свою очередь послужить источником данных для объекта Stream, который создается, как обычно, методом Open.ReadText глобального объекта Strm1 класса Stream позволяет прочесть нужную порцию символов в обычную строку и распечатать ее, что позволяет убедиться в правильности передачи данных.FirstRec закрывается и заново открывается, но теперь у него другой источник данных и его значением становится каталог.RESOURCE_PARSENAME - RESOURCE_PARENTNAME - http://serverva RESOURCE_ABSOLUTEPARSENAME - http://serverva RESOURCE_ISHIDDEN - RESOURCE_CONTENTCLASS -В сравнении с предыдущим случаем не для всех полей определены значения, в частности не задан класс контента.
Recordset, записи которого будут содержать сведения о подкаталогах и файлах, хранимых в каталоге. Для создания объекта Recordset используется метод GetChildren, вызываемый объектом FirstRec.RESOURCE_ABSOLUTEPARSENAME http://serverva/_private RESOURCE_ABSOLUTEPARSENAME http://serverva/images RESOURCE_ABSOLUTEPARSENAME http://serverva/win2000.gif RESOURCE_ABSOLUTEPARSENAME http://serverva/web.gif RESOURCE_ABSOLUTEPARSENAME http://serverva/warning.gif
На этом я закончу описание примера, в котором успешно взаимодействовали объекты Record, Stream и Recordset. Знакомство с объектом Stream уже состоялось, но давайте, хотя бы вкратце познакомимся с его свойствами и методами.
Этот объект представляет двоичный поток данных или текст. Методы и свойства этого объекта позволяют выполнять операции над этим потоком, позволяя читать, писать, копировать и сохранять данные потока. Объекты Stream обычно используются при работе с данными, сохраненными в XML-формате, при работе с текстовыми файлами и сообщениями электронной почты, во всех случаях, когда данные рассматриваются как поток байтов. Объект Stream может быть получен тремя способами:
Record, представляющим структурированный документ, или папкой.Record.Объект Stream имеет следующие свойства:
Property Charset As String. Строка, задающая множество символов, в которое транслируется данные потока. Свойство имеет смысл только для текстовых файлов, которые по умолчанию должны быть представлены в кодировке Unicode, -значением свойства по умолчанию.Property EOS As Boolean. Аналог свойства EOF для файлов, позволяет проследить за окончанием потока при последовательном чтении его байтов. Принимает значение True, когда свойство Position определяет позицию, лежащую за последним символом потока.Property LineSeparator As LineSeparatorEnum. Константы перечисления задают символы, задающие конец строки в текстовом файле. Возможные значения: adCR, adCRLF, adLF.Property Mode As ConnectModeEnum. Свойство, задающее статус объекта. Им обладают большинство объектов ADO.Property Position As Long. Определяет текущую позицию в потоке. Поскольку над потоком определены операции чтения и записи, то понятно, что должна быть определена текущая позиция (курсор), отмечающая начальную точку при выполнении этих операций.Property Size As Long. Задает размер потока. И позиция и размер потока измеряются в байтах.Property State As ObjectStateEnum. Свойство, задающее состояние объекта. Им обладают большинство объектов ADO.Property Type As StreamTypeEnum. Тип потока задается константой из перечисления. Имеет два возможных значения: adTypeBinary, adTypeText.Рассмотрим теперь методы объекта Stream:
Sub Cancel(). Как и для других объектов ADO позволяет прервать выполнение асинхронных операций над объектом Stream.Sub Close(). Закрывает объект Stream.Sub CopyTo(DestStream As Stream, [CharNumber As Long = -1]). Позволяет создать копию SetEOS для потока назначения.Sub Flush(). Принуждает оставаться содержимому потока в ADO буфере того базового объекта, с которым ассоциирован объект Stream.Sub LoadFromFile(FileName As String), Sub SaveToFile(FileName As String, [Options As SaveOptionsEnum = adSaveCreateNotExist]). Эти методы позволяют загрузить содержимое файла в поток и сохранить поток в файле.Sub Open([Source], [Mode As ConnectModeEnum = adModeUnknown], [Options As StreamOpenOptionsEnum = adOpenStreamUnspecified], [UserName As String], [Password As String]). Один из основных методов, создающий поток. Первый параметр задает источник данных. Это может быть абсолютный URL-адрес, в этом случае параметр имеет следующий синтаксис "URL = scheme://server/folder". Адрес может задавать узел в структуре дерева каталогов файловой системы или системе электронной почты. Источник может быть ссылкой на открытый объект Record. Объект Stream ассоциируется с потоком по умолчанию этого объекта. В наших примерах использовался именно такой способ создания объекта Stream. Источник может и не указываться, тогда создается объект Stream, не ассоциированный с потоком другого объекта. Данные в него могут поступать, например, при загрузке из файла при вызове метода LoadFropmFile или при создании копии потока.Function Read([NumBytes As Long = -1]), Function ReadText([NumChars As Long = -1]) As String, Sub Write(Buffer), Sub WriteText(Data As String, [Options As StreamWriteEnum = adWriteChar]). Эта группа методов позволяет читать и писать в поток заданное число байтов (символов). Методы Read и Write, работающие с байтами, используют переменную типа Variant, а методы ReadText и WriteText используют переменную типа String. Заметьте, речь идет о потоке, поэтому все операции по чтению и записи идут только в одном направлении - от начала к концу потока. Если требуется несколько проходов, то следует заново переоткрывать поток.Sub SetEOS(). Метод устанавливает значение свойства EOS в True, а, главное, делает текущую позицию концом потока, отсекая все оставшиеся символы. Поскольку методы Write, WriteText, CopyTo не производят отсечения, то, чаще всего, они используются в комбинации с методом SetEOS.Sub SkipLine(). Позволяет пропустить одну строку при чтении текстового файла. Используется в цикле, когда нужно пропустить несколько строк.Свойство Fields объектов Recordset и Record возвращает одноименную коллекцию, элементами которой являются объекты Field, представляющие содержательно поля записи или столбцы данных, пользуясь табличной терминологией. Об этих объектах уже было сказано немало, так что я постараюсь долго на них не останавливаться.
У коллекции Fields всего два традиционных для всех Count и Item, а вот методов гораздо больше. Наряду с методами, о которых я уже говорил при рассмотрении других объектов ADO, такими как Delete, Refresh, Update, CancelUpdate, имеется два специфических метода:
Sub Append(Name As String, Type As DataTypeEnum, [DefinedSize As Long], [Attrib As FieldAttributeEnum = adFldUnspecified], [FieldValue ]). Метод позволяет создать новое поле и присоединить его к коллекции. При определении поля задается необходимая информация - имя поля, его тип, определяемый размер данных, атрибуты и, возможно, значение, если речь идет об объекте Record. При работе с полями объекта Recordset они должны быть созданы при закрытом объекте Recordset, после этого объект можно открыть и присвоить значения созданным полям. Присоединять поля к объекту Recordset можно только в том случае, если он закрыт и для него не установлено свойство ActiveConnection. Поскольку в реальных ситуациях эти объекты появляются при выполнении метода Open или Execute, когда соединение с источником данных установлено, то присоединять новые поля к объекту Recordset таким способом не удается. Для изменения структуры базы данных используются обычно объекты ADOX, которые будут рассмотрены ниже. Так что, чаще всего, метод Append применяется при работе с полями объекта Record. Добавлять поле к этому объекту можно и тогда, когда он открыт, при добавлении можно указать и значение поля. После добавления поля следует вызвать метод Update, чтобы сохранить сделанные изменения.Sub Resync([ResyncValues As ResyncEnum = adResyncAllValues]). Позволяет обновить значения всех полей у записей, хранящихся в буфере.Объект Field задает поле записи. С помощью его свойств можно определить характеристики поля - тип, значение, формат и установить эти значения при формировании новых полей, присоединяемых к записи. Рассмотрим все по порядку:
Property Name As String. Задает имя поля.Property ActualSize As Long, Property DefinedSize As Long. Первое свойство позволяет определить фактический размер хранимого в поле значения. Оно имеет статус только для чтения. В случае, если ADO не может определить по каким либо причинам истинный размер значения, в качестве результата возвращается константа adUnknown. Второе свойство задает метаданные - размер поля, заданный в момент его определения. Его статус - "чтение/запись". Размер всегда задается в байтах.Property Precision As Byte. Для числовых полей указывает максимальное число цифр, используемых для представления значения.Property NumericScale As Byte. Для числовых полей указывает число цифр после запятой, используемых для представления значения.Property Type As DataTypeEnum. Задает тип поля. Приведу значения некоторых констант из перечисления: adInteger = 3, adVarWChar = 202 (для строковых полей), adCurrency = 6, adLongVarWChar = 203 (для поля Memo).Property DataFormat As Unknown. Это свойство, которое, судя по названию, должно задавать формат данных, отсутствует в версии ADO 2.6. Хотя оно присутствует в предыдущей версии 2.5, но возвращает в качестве результата ссылку на пустой объект, так что не стоит пытаться его использовать.Property Status As Long. Возвращает статус поля, позволяющий, например, определить, было ли поле нормально добавлено или удалено из записи. Значения свойства задаются константами из перечисления FieldStatusEnum. Вот некоторые значения констант: adFieldAlreadyExists, adFieldOK, adFieldPendingInsert, adFieldPendingDelete, adFieldCantCreate. Анализ статуса поля, позволяет определить, что произошло при выполнении операций над тем или иным полем.Property OriginalValue As Variant, Property UnderlyingValue As Variant, Property Value As Variant. Все эти свойства задают значение поля в разных его ипостасях - значение поля в записи до его изменений, значение поля в базе данных, текущее значение поля в записи.Property Attributes As Long. Свойство задает маску, определяющую характеристики поля. Значение представляет сумму констант из перечисления FieldAttributeEnum. Вот лишь некоторые значения этих констант: adFldKeyColumn, adFldLong, adFldIsChapter, adFldUpdatable.Property Properties As Properties. Возвращает У этого объекта имеется всего два метода:
Sub AppendChunk(Data). Этот метод позволяет формировать "длинные" значения полей, присоединяя очередную порцию данных к текстовому или двоичному значению поля. При первом вызове метода значение, заданное аргументом Data переписывается в поле и становится его значением, при последующих вызовах значение аргумента Data добавляется к значению, хранимому в поле. Метод не применим к полям объекта Record. Аргумент Data задается переменной типа Variant и содержит добавляемые данные. Заметьте, для того чтобы можно было использовать этот метод бит adFldLong в свойстве Attributes должен быть установлен - иметь значение True.Function GetChunk(Length As Long). Метод позволяет получить все значение или нужную порцию большого текста или двоичного кода, хранящегося в поле. Длина порции устанавливается параметром Length. Возвращаемый результат является переменной типа Variant.Приведу теперь пример, в котором анализируются поля объекта Recordset. По изложенным выше причинам я не стал пытаться вводить новые поля и ограничился получением информации о свойствах уже существующих полей записи. Вот код соответствующей процедуры:
Public Sub CreateField()
'Процедура печатает информацию о существующих полях записи
Dim fld As Field
Dim df As Object
'Создать соединение
CreateConnection
'Создать команду
'задание свойств объекта Command
Cmd1.ActiveConnection = Con1
Cmd1.CommandText = "Select * From [Книги]"
Cmd1.CommandType = adCmdText
'Открытие обновляемого объекта Recordset
With Rst1
.Open Source:=Cmd1, CursorType:=adOpenDynamic, _
LockType:=adLockOptimistic
.MoveFirst
Do While Not .EOF
'Печать данных о полях записи набора
If !Название = "Офисное программирование" Then
For Each fld In .Fields
With fld
Debug.Print "Имя поля ", .Name
Debug.Print "Фактический размер поля =", .ActualSize
Debug.Print "Определяемый размер поля =", .DefinedSize
Debug.Print "Точность", .Precision
Debug.Print "Число цифр после запятой в числовых полях -", _
.NumericScale
Debug.Print "Тип поля", .Type
Set df = .DataFormat
If df Is Nothing Then
Debug.Print "объект - Формат данных -не определен "
End If
Debug.Print "Статус", .Status
Debug.Print "Значение поля до его изменений ", .OriginalValue
Debug.Print "Текущее значение в базе данных", .UnderlyingValue
Debug.Print "Текущее значение поля в наборе", .Value
Debug.Print "Атрибуты =", .Attributes
Debug.Print "Число свойств =", .Properties.Count
Debug.Print "Имя и значение первого свойства - ", _
.Properties(1).Name, .Properties(1).Value
End With
Next fld
End If
.MoveNext
Loop
'попытка изменить характеристики полей
'закрываем объект Recordset
.Close
Set fld = .Fields("Год издания")
With fld
.DefinedSize = 8
.Type = adDecimal
End With
'.Fields.Update
End With
End Sub
Приведу краткий комментарий:
Data Format возвращает ссылку на пустой объект.Recordset изменение значений этих характеристик возможно, но метод Fields.Update в данном контексте не работает, по этой причине он закомментирован.Подведем теперь некоторые итоги. Вот что можно делать с объектом Field:
Name позволяет вернуть имя поля, но не позволяет изменить это имя.Value позволяет не только получить значение поля, но и изменить его.Type, Precision, NumericScale, DefinedSize позволяют получить, но не изменить метаданные, задающие соответствующие характеристики поля.ActualSize позволяет определить фактический размер хранимого в поле значения.Attributes и Properties.AppendChunk и GetChunk.Recordset, что может быть полезно при динамическом конструировании форм.Объект Command может определять запрос или хранимую процедуру с параметрами. А посему этот объект имеет свойство Parameters, возвращающее одноименную коллекцию, элементами которой являются объекты Parameter. Содержательно, каждый из этих объектов определяет параметр - входной или выходной, который передается или возвращается при вызове хранимой процедуры. Я уже приводил пример вызова запроса с параметром, где появлялись эти объекты.
Коллекция Parameters устроена достаточно просто. У нее всего два типичных свойства - Item и Count. У коллекции три метода, которые также появлялись в нашем рассмотрении. Вот эти методы:
Sub Append(Object As Object). Метод позволяет присоединить объект Parameter, заданный аргументом, к коллекции. Обычно присоединяемый объект создается методом CreateParameter объекта Command. В процедуре CreateCommands, приведенной в предыдущей главе, продемонстрировано применение методов CreateParameter и Append.Sub Delete(Index). Метод позволяет удалить из коллекции параметр, по имени или порядковому номеру, указываемому в аргументе Index.Sub Refresh(). Позволяет обновить содержимое коллекции, получая информацию о параметрах из хранимой процедуры.Поскольку все свойства и методы объекта Parameter так или иначе уже появлялись в нашем описании, то я их только перечислю, не приводя подробного описания. Вот список свойств этого объекта: Attributes, Direction, Name, NumericScale, Precision, Properties, Size, Type, Value. Метод у этого объекта всего один - AppendChunk, - и он тоже был описан при рассмотрении объекта Field.
Используя свойства и методы коллекции и самого объекта Parameter можно делать следующее:
Name позволяет устанавливать и возвращать имя параметра, а свойство Value - его значение.Attributes, Direction, Precision, NumericScale, Size и Type позволяют устанавливать и возвращать различные характеристики параметра.Direction позволяет указать, является ли параметр входным, выходным, входным-выходным или представляет возвращаемое процедурой значение. Поскольку не все Провайдеры могут установить направление передаваемых параметров, то в таких ситуациях необходимо программно устанавливать значение этого свойства перед тем, как вызывать хранимую процедуру или параметризованный запрос.AppendChunk позволяет передавать параметру порции двоичных или символьных данных в случае длинных значений параметра.Properties.CreateParameter и присоединить его к коллекции, используя ее метод Append. Это позволяет избежать лишних вызовов метода Refresh для получения нужной информации от Провайдера и тем самым снизить нагрузку на передачу данных.Многие из объектов ADO имеют свойство Properties, возвращающее одноименную коллекцию элементов Property. Поскольку каждый объект обладает набором свойств, то возникает естественный вопрос, зачем необходимо еще и свойство Properties, которое тоже задает свойства. Дело в том, что свойства разделяются на встроенные и динамические. Properties. Каждый объект этой коллекции описывает то или иное динамическое свойство объекта ADO, зависящее от специфики Провайдера и доступное, естественно, только тогда, когда объект открыт. Добавлять или удалять элементы этой коллекции невозможно, - это прерогатива Провайдера.
С объектной точки зрения коллекция Properties устроена очень просто - у нее всего два типичных свойства: Item, Count и один метод Refresh. Также просто устроен и объект Property - у него всего четыре свойства: Name, Type, Value, Attributes. Они задают имя, тип, значение и некоторые дополнительные характеристики. Примеры работы с этими объектами уже приводились.
На этом я завершаю утомительное описание объектов ADO. Но нам предстоит еще хотя бы вкратце познакомиться с объектами ADOX.
Клиентское приложение, использующее объекты ADO, получает необходимые ему данные, обращаясь к Провайдеру. Провайдер или Поставщик данных - это приложение, обеспечивающее интерфейс между конкретным источником данных и клиентским приложением. Провайдер способен корректно транслировать вызовы методов ADO в запросы к источнику данных, выполнять эти запросы и передавать результаты клиентскому приложению. Несмотря на многообразие источников данных, многие из них имеют единый интерфейс запросов, что позволяет иметь одного Провайдера на все источники данных, экспонирующие единый интерфейс запросов. Конечно же, для уникальных источников данных может понадобиться разработка собственного Провайдера. Но для наиболее известных источников данных Провайдеры уже разработаны как Microsoft, так и другими фирмами, поддерживающими работу с источниками данных. Рассмотрим особенности ряда стандартных Провайдеров.
Полное имя этого Провайдера - Microsoft OLE DB Provider for ODBC. Он является Провайдером по умолчанию для объектов ADO, так что если не задать аргумент Provider в строке соединения, то связь по умолчанию будет осуществляться с этим Провайдером. Этот Поставщик данных позволяет связаться со всеми СУБД с интерфейсом ODBC. Все СУБД, поставляемые Microsoft - Microsoft SQL Server, Microsoft Access (Microsoft Jet database engine), Microsoft FoxPro - обладают этим интерфейсом. Но и СУБД других фирм, например Oracle, обладают, как правило, этим интерфейсом, так что Провайдер ODBC реально позволяет связаться с любой профессиональной базой данных.
Провайдер ODBC, являясь Провайдером по умолчанию, поддерживает все зависящие от Провайдера свойства и методы объектов ADO. Он поддерживает транзакции, в том числе и гнездованные транзакции. Однако различные СУБД могут обеспечивать различный уровень поддержки транзакций, например, Microsoft Access поддерживает гнездованные транзакции на глубину не более пяти уровней.
Для этого Провайдера аргумент Provider свойства ConnectionString следует установить как MSDASQL. Типичная строка соединения имеет вид:
"Provider = MSDASQL; DSN = dsnName; UID = userName; PWD = userPassword;"
Аргумент DSN (Data Source Name), задает имя источника данных. Это имя должно быть зарегистрировано в Администраторе источников данных ODBC, добраться до которого можно из панели управления. Вот как выглядит окно Администратора, в котором я установил DSN для тестовой базы данных.
(рис 6.5) Установка DSN в окне Администратора ODBCПриведу пример работы с этой базой данных:
Public Sub CreateODBCConnect()
'Создание соединения с тестовой базой данных Access
Dim strConnStr As String
Dim Start As Single, Finish As Single
If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение
'Вариант1: Провайдер ODBC
Con1.Provider = "MSDASQL"
strConnStr = "DSN=dbTestingADO; DATABASE =c:\dbPP2000.mdb;"
'Вариант2: Провайдер Microsoft Jet
'Con1.Provider = "Microsoft.jet.oledb.4.0"
'strConnStr = "Data Source=c:\dbPP2000.mdb;"
Start = Timer
Con1.Open strConnStr
'Создать команду
'задание свойств объекта Command
Cmd1.ActiveConnection = Con1
Cmd1.CommandText = "Select * From [Книги]"
Cmd1.CommandType = adCmdText
'Открытие обновляемого объекта Recordset
With Rst1
.Open Source:=Cmd1, CursorType:=adOpenDynamic, _
LockType:=adLockOptimistic
recExist = False
.MoveFirst
Do While Not .EOF
'Проверка существования записи
If !Название = "Война и мир" Then recExist = True
.MoveNext
Loop
If Not recExist Then
.AddNew Array("Автор", "Название", "Год издания", _
"Число страниц", "Цена"), _
Array("Лев Толстой", "Война и мир", 2001, 799, 220)
End If
End With
Con1.Close
Finish = Timer
Debug.Print "Время работы с таблицей:", Finish - Start
End Sub
Хочу обратить внимание на два момента:
Время работы с таблицей: 5,878906 Время работы с таблицей: 0,0390625
Заметьте, время работы с ODBC Провайдером для базы данных Access в данном случае существенно больше, - на два порядка, чем время работы с "родным" Провайдером.
DATABASE, указывающий путь к базе данных. Эта информация является дублирующей, поскольку реальный путь к базе данных задается в момент определения DSN. Тем не менее, это полезно делать, чтобы явно указать, с какой базой данных пользователь намеревается работать.Провайдер ODBC добавляет в коллекцию Properties объектов Connection, Command, Recordset ряд свойств, часть из которых является специфической для данного Провайдера. Некоторые из этих свойств добавляются к не открытым объектам, другие - при открытии объектов. Для всех этих свойств, доступных в ADO, есть аналог в модели OLE DB. Перечисление всех этих свойств заняло бы слишком много места. Чтобы дать некоторое представление, укажу несколько свойств объекта Connection:
Accesible Tables - булево свойство, указывающее, имеет ли пользователь разрешение на выполнение запросов (SQL-операторов) над таблицами базы данных.File Usage - указывает, как драйвер воспринимает источник данных, - как файл или как каталог.Special Characters - указывает, какие символы имеют специальный смысл для ODBC драйвера.Stored Procedures - определяет доступность использования хранимых процедур.Этот Провайдер позволяет получить доступ к Microsoft Jet базам данных, а, следовательно, к базам данных, созданным в приложении Access. В большинстве приведенных примерах использовался именно этот Провайдер. Типичная строка соединения имеет вид:
"Provider=Microsoft.Jet.OLEDB.4.0; Data Source=databaseName; User ID =userName; Password=userPassword;"
В строке соединения Провайдеру могут быть переданы и специфические свойства, например, параметр Jet OLEDB: Database Password позволяет задать пароль к базе данных. В одном из последующих примеров я использую этот способ, чтобы указать имя системной базы данных. Другой способ передачи специфических для Провайдера параметров - через коллекцию Propertieis объекта Connection. Заметьте, большую часть этой коллекции для объектов Connection, Command и Recordset Провайдер формирует динамически.
Полное имя этого Провайдера - Microsoft OLE DB Provider for Internet Publishing. Он позволяет получить доступ к ресурсам Web-серверов, работающих под управлением Internet Information Server. Типичная строка соединения имеет вид:
"Provider= MSDAIPP.DSO; Data Source=ResourceURL; User ID =userName; Password=userPassword;"
или
"URL =ResourceURL; User ID =userName; Password=userPassword;"
Параметр Data Source | URL задает источник данных - URL файла или каталога, расположенного в Web-парке на сервере. Заметьте, во втором случае, когда используется имя URL для второго параметра, первый параметр, определяющий Провайдера, задавать не следует. Ошибка возникнет и в том случае, если предварительно будет задано свойство Provider.
Примеры использования этого Провайдера уже были приведены.
Полное имя этого Провайдера - Microsoft OLE DB Provider for SQL Server. Он позволяет получить доступ к данным Microsoft SQL Server. Типичная строка соединения имеет вид:
"Provider= SQLOLEDB; Data Source=ServerName; Initial Catalog =databaseName; User ID =userName; Password=userPassword;"
Параметр Data Source или Server задает имя сервера. Параметр Initial Catalog или Database - имя базы данных на сервере.
В строке соединения Провайдеру могут быть переданы и специфические свойства, например, параметр Current Language позволяет задать язык, используемый для системных сообщений. Язык должен быть инсталлирован на SQL Server, иначе возникнет ошибка в момент открытия соединения. Другой способ передачи специфических для Провайдера параметров - через коллекцию Propertieis объекта Connection. Большую часть этой коллекции для объектов Connection, Command и Recordset Провайдер формирует динамически.
Полное имя этого Провайдера - Microsoft OLE DB Provider for Oracle. Он позволяет получить доступ к базе данных Oracle. Типичная строка соединения имеет вид:
"Provider= MSDAORA; Data Source=ServerName; User ID =userName; Password=userPassword;"
Параметр Data Source задает имя сервера. В строке соединения Провайдеру могут быть переданы и специфические свойства, например, параметр OLE DB Services позволяет задать маску, каждый бит которой позволяет включить или выключить соответствующую OLE DB службу. Другой способ передачи специфических для Провайдера параметров - через коллекцию Propertieis объекта Connection. Большую часть этой коллекции для объектов Connection, Command и Recordset Провайдер формирует динамически.
Этот Провайдер поддерживает работу только со
Краткие данные о других Провайдерах приведу в таблице.
| Название Провайдера | Строка соединения | Назначение |
|---|---|---|
| >Microsoft OLE DB Remoting Provider | "Provider=MS Remote" |
Позволяет удаленному пользователю подключиться к источнику данных так, как если бы он был локальным пользователем. |
| Microsoft OLE DB Simple Provider (OSP) | "Provider=MSDAOSP;Data Source=serverName" |
Позволяет ADO получить доступ к любым данным, для которых Провайдер написан с использованием специального инструментария - OLE DB Simple Provider Toolkit. Такие источники данных требуют лишь базисных средств поддержки OLE DB. Типичным примером данных являются - XML документы. |
| Microsoft OLE DB Persistence Provider | "Provider=MSPersist" |
Этот Провайдер позволяет сохранить в файле данные и всю необходимую информацию об объекте Recordset, в том числе и иерархическом. Затем, при необходимости, объект Recordset может быть восстановлен. |
| Microsoft Data Shaping Service for OLE DB | "Provider=MSDataShape" |
Поддерживает конструирование иерархических объектов Recordset. |
| Microsoft OLE DB Provider for Microsoft Indexing Service | "Provider=MSIDXS;Data Source=myCatalog;Locale Identifier=nnnn;"Типично указывается только первый параметр. Второй параметр задает имя каталога специальной поисковой службы - Indexing Service. Если параметр опущен, то по умолчанию используется системный каталог. Параметр |
Поддерживает программный доступ со статусом "только для чтения" к файловой системе и Web-страницам, индексируемых службой Microsoft Indexing Service. |
| Microsoft Active Directory Service Interfaces (ADSI) Provider | "Provider=ADSDSOObject;User ID=userName;Password=userPassword;" |
to connect to Позволяет связаться с гетерогенными службами каталогов - Windows NT 4.0, Windows 2000, Novell Directory Services, LDAP Directory Services. |
| Microsoft Cursor Service for OLE DB | Строки соединения нет, поскольку это специальная служба, вызываемая автоматически в зависимости от расположения курсора. | Эта специальная служба обеспечивает функции поддержки курсора для различных Провайдеров данных, что обеспечивает единую функциональность в работе ADO. Вызывается служба автоматически, если соответствующим образом задать положение курсора объектов Connection или Recordset:connection.CursorLocation=adUseClient recordset.CursorLocation=adUseClient |
До появления объектов ADO при работе с базой данных Access использовались объекты
В объектной модели Workspaces, каждый элемент которой - объект Workspace - задает сеанс работы, являясь неким эквивалентом объекта Session в модели OLE DB. В модели ADO сеанс работы связывается с объектом Connection, задающим соединение с базой данных. Заметьте, модель ADO во многом упрощена в сравнении с моделью Database, являющегося одним из основных объектов в модели
Конечно, главное достижение в модели ADO связано с возможностью работы с разнообразными источниками данных и введением такого понятия как Провайдер. В модели WorkSpace, задающий рабочее пространство, вкладываются две достаточно разные совокупности объектов. Я ограничусь рассмотрением лишь одной половинки этой модели и рассмотрю модель
(рис 6.6) Объектная модель DAO для рабочей области Microsoft JetОбратите внимание, чтобы не усложнять схему, на нижних уровнях иерархии ряд объектов опущен. Вот как следует дополнить эту схему:
Container содержит коллекцию Documents с элементами Document,QueryDef содержит коллекцию Parameters с элементами Parameter,OueryDef, Recordset, Relation, TableDef содержат коллекцию Fields c элементами Field.Я уже говорил, что объектная модель
Connection позволяет связаться с базой данных и заменяет, в определенной степени, объекты Workspace и Database. Объект Command, позволяющий выполнять SQL-операторы, заменяет объект QueryDef, который выполняет эти действия в модели Recordset присутствует в обеих моделях, выполняя, в общем, аналогичные действия.QueryDef, TableDef используются для этих целей, создавая или модифицируя стандартные запросы и таблицы базы данных. Объекты User, Group и их коллекции, присутствующие в моделях Replica , задающий репликацию базы данных. Свойства и методы этого объекта используются для создания новой репликации, получения и модификации свойств уже существующих реплик и синхронизации реплик. В модели Database. Еще один объект JetEngine из модели JRO позволяет выполнять операции по сжатию базы данных и обновлению данных, хранимых в кэш-памяти.Я не буду останавливаться на деталях сходства и различия в моделях ADO и
Workspace. Но поскольку в этот объект вложена коллекция объектов Databases, то в транзакции участвуют все открытые базы данных, запоминаются все изменения и при откате восстанавливается состояние всех баз данных. В модели ADO каждая транзакция связана только со своей базой данных, открытой заданным соединением.Объекты этой группы, как уже было сказано, играют важную роль, позволяя работать с метаданными баз данных. С их помощью можно программно создать новую базу данных, создать или модифицировать элементы существующей базы данных, создать новые или модифицировать существующие таблицы, запросы, хранимые процедуры. Кроме того, эти объекты предназначены для работы с пользователями, группами пользователей, задания их прав доступа к данным, обеспечивая, тем самым, нужный уровень безопасности.
Рассмотрим объектную модель ADOX и начнем со схемы, отражающей взаимосвязи между объектами модели:
(рис 6.7) Объектная модель ADOXНа схеме не отражен тот факт, что объекты Table, Index, Column имеют стандартную коллекцию Properties с элементами Property.
Заметьте, в этой модели есть центральный объект Catalog, в который и встроены все основные коллекции: Tables, Procedures, Views, Groups, Users. Эти коллекции определяют различные метаэлементы базы данных. Давайте подробно рассмотрим свойства и методы объектов модели.
Центральный объект модели ADOX, задает каталог базы данных. Его метод Create позволяет создать новый источник данных, а свойства позволяют добраться до элементов, определяющих схему базы данных. Не все Провайдеры поддерживают полный набор возможностей этого объекта, определяемых его свойствами и методами. Так что источник данных и его Провайдер определяют, допустимо ли для него использование того или иного свойства или метода объектов ADOX.
У объекта Catalog 6 свойств: ActiveConnection, Tables, Procedures, Views, Groups, Users. Пять последних свойств возвращают одноименные коллекции. А о первом свойстве поговорим чуть подробнее.
Свойство имеет статус "чтение/запись". Синтаксически определяется следующим образом:
Property ActiveConnection As Variant
Позволяет установить ссылку на объект Connection или задать строку, определяющую соединение. Возвращает ссылку на активный объект Connection.
С объектной точки зрения все эти коллекции устроены одинаково. Они хранят соответствующие элементы классов: Table, Procedure, View, Group, User. У них есть три метода и два свойства. Для добавления нового элемента в коллекцию применяется метод Append(item), удаления - метод Delete(item), для обновления состояния коллекции, учитывающего текущее состояние базы данных - метод Refresh(). Свойство Item позволяет добраться до нужного элемента коллекции, а свойство Count возвращает число элементов в коллекции.
Некоторая особенность связана с коллекциями Groups и Users. Коллекция Groups объекта Catalog задает все группы базы данных. С другой стороны, как показано на схеме, отражающей связи между объектами (Рис. 7), коллекция Groups связана и с отдельным объектом User. Коллекция групп, связанная с отдельным пользователем, возвращает все группы, в которые входит этот пользователь. Заметьте, прежде чем добавить новую группу с именем name в коллекцию Groups объекта User, элемент с таким именем уже должен находиться в коллекции Groups объекта Catalog. Совершенно симметрично дело обстоит и с коллекцией Users, которая в объекте Catalog описывает всех пользователей, а в объекте Group - пользователей данной группы. Аналогично, прежде чем добавить пользователя в отдельную группу, он должен быть добавлен в коллекцию Users объекта Catalog
.
У этого объекта три метода:
Function Create(ConnectString As String). Позволяет создать новую базу данных. Параметр ConnectString задает строку соединения. Если создание базы данных прошло успешно, то в качестве результата возвращается объект Connection из библиотеки ADODB, задающий соединение с базой. Этот же объект становится значением свойства ActiveConnection объекта Catalog. Естественно, при выполнении метода возникнет ошибка, если Провайдер не поддерживает создание нового источника данных.Function GetObjectOwner(ObjectName As String, ObjectType As ObjectTypeEnum, [ObjectTypeId]) As String.Sub SetObjectOwner(ObjectName As String, ObjectType As ObjectTypeEnum, UserName As String, [ObjectTypeId]).Эти два метода позволяют определить и, соответственно, установить собственника объекта. Параметр UserName метода Set задает имя собственника, которое возвращает метод (функция) Get. Параметры ObjectName и ObjectType задают имя и тип объекта, для которого устанавливается собственник. Наличие этих параметров связано с тем, что методы применимы не только к объекту Catalog. Тип объекта задается константами из перечисления ObjectTypeEnum. Вот их значения: adPermObjColumn, adPermObjDatabase, adPermObjProcedure, adPermObjTable, adPermObjView. Еще одно возможное значение - adPermObjProviderSpecific указывается тогда, когда объект не принадлежит ни к одному из указанных типов и зависит от Провайдера, в этой ситуации тип задается последним параметром ObjectTypeId.
Чтобы оживить изложение, давайте перейдем к рассмотрению примеров. Для экспериментов с объектами ADOX я создал новый модуль. Глобальные переменные этого модуля будут определять уже знакомые объекты - Connection, Command, Recordset. Но к ним теперь добавится и новый объект, задающий Catalog.
Option Explicit 'Модуль TestingADOX 'Глобальные переменные Public Con1 As New ADODB.Connection Public Cmd1 As New ADODB.Command Public Rst1 As New ADODB.Recordset Public Strm1 As New ADODB.Stream Public Cat1 As New ADOX.Catalog
Вот как выглядит первая процедура этого модуля, создающая новую базу данных:
Public Sub CreateNewDB() 'Создание новой базы данных и открытие соединения с ней Dim strConnStr As String strConnStr = "Provider=Microsoft.jet.oledb.4.0;" _ "Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb" Set Con1 = Cat1.Create(strConnStr) Debug.Print Cat1.ActiveConnection Dim Con2 As Connection Set Con2 = Cat1.ActiveConnection Debug.Print Con2.ConnectionString End Sub
В этой процедуре я создал новую базу данных Access и установил с ней соединение. Заметьте, я ввел два объекта класса Connection, - один из них определяется при вызове метода Create объекта Cat1 класса Catalog, а второй определяется, используя значение свойства ActiveConnection.
Provider=Microsoft.Jet.OLEDB.4.0;Password="";User ID=Admin;Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database="";Jet OLEDB:Registry Path="";Jet OLEDB:Database Password="";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password="";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False
В ней отражены все свойства объекта Connection.
Этот объект определяет таблицу базы данных. Является элементом коллекции Tables, вложенной в объект Catalog. Создается конструктором New и добавляется в коллекцию Tables методом Append. Вот типичная схема программного создания новой таблицы:
Dim NewTable As New ADOX.Table 'Формирование объекта Table … Call Cat1.Tables.Append(NewTable)
Чуть позже я приведу полный пример программного создания объекта Table, а пока займемся рассмотрением его свойств, методов и событий. Задача облегчается тем, что у объекта Table нет методов и событий, - есть только свойства, рассмотрением которых сейчас и займемся.
Свойства объекта позволяют определить новую таблицу или модифицировать уже существующую. Свойств у объекта сравнительно немного. Вот они:
Columns, Keys и Indexes возвращают одноименные коллекции, которые и задают основные элементы таблицы - ее поля, ключи и индексы. Все три коллекции имеют одинаковый и уже описанный набор из трех методов: Append, Delete, Refresh и двух свойств - Item и Count. Создаются элементы коллекций конструктором New, а добавляются в коллекцию методом Append, в полном соответствии с выше приведенной схемой для объекта Table.Property DateCreated As Variant, Property DateModified As Variant. Свойства имеют статус только для чтения и возвращают дату создания и дату последней модификации таблицы.Property Name As String. Задает имя таблицы.Property ParentCatalog As Catalog. Задает родительский каталог, - объект, метод Create которого создал базу данных, которой и принадлежит таблица.Property Properties As Properties. Возвращает Property Type As String. Свойство задает тип, но не, как обычно, константой, а строкой, значения которой могут быть, например, "TABLE", "SYSTEM TABLE " или "GLOBAL TEMPORARY".Таким образом, для создания таблицы или ее модификации приходится, главным образом, работать с объектами Column, Key, Index и их коллекциями. Рассмотрим устройство этих объектов.
Коллекция Columns с элементами Column возвращается при вызове одноименного свойства объекта Table, в этом случае она задает поля таблицы. Но эта коллекция появляется и в других случаях. Напомню, ключи и индексы таблицы могут быть составными и состоять из нескольких полей, по этой причине объекты Key и Index также обладают свойством Columns, которое возвращает коллекции полей, задающих составной ключ или составной индекс. Так что объект Column может задавать поле таблицы, ключа или индекса. Этот объект является центральным объектом в мире, связанном с таблицами. Устроен он достаточно просто, у него нет ни методов, ни событий, - есть только свойства.
Свойства этого объекта определяют различные характеристики поля, включенного в таблицу:
Property Name As String. Задает имя поля.Property Type As DataTypeEnum. Задает тип поля. Возможные значения определяются константами из уже упоминавшегося перечисления DataTypeEnum.Property DefinedSize As Long. Задает определяемый размер поля. Заметьте, фактический размер может быть меньше и зависит от значения, хранимого в поле.Property Precision As Long, Property NumericScale As Byte. Эти два свойства определены только для полей, хранящих числовые значения. Они определяют общее число хранимых цифр и число цифр после запятой.Property SortOrder As SortOrderEnum. Записи в таблице могут быть упорядочены по тому или иному полю. Данное свойство определяет порядок сортировки, - по возрастанию или убыванию, что задается его возможными значениями: adSortAscending, adSortDescending. Свойство применимо только для индексируемых полей.Property ParentCatalog As Catalog. Свойство задает родительский каталог, позволяя подняться при необходимости к корневому объекту ADOX. Включение корневого объекта в объекты нижних уровней типично для объектных моделей, используемых Microsoft. Такое включение облегчает программистам работу, упрощая навигацию между объектами.Property RelatedColumn As String. Это свойство применимо только для ключевых полей, оно указывает имя поля, являющегося внешним ключом в связываемой таблице. Для уже присоединенных полей к коллекции Columns свойство имеет статус "только для чтения".Property Attributes As ColumnAttributesEnum. Как и для других объектов ADO, обладающих подобным свойством, данное свойство задает набор дополнительных атрибутов объекта. Значение свойства представляет маску, каждый бит которой определяет, включен или выключен тот или иной атрибут. Возможных атрибутов, которые можно установить, всего два - adColFixed и adColNullable. Они указывают, имеет ли поле фиксированную длину и может ли использоваться значение Null. Значение этого свойства, принимаемое по умолчанию и равное 0, означает, что упомянутые атрибуты не включены.Property Properties As Properties. Задает В заключение отмечу, что конкретный Провайдер может не поддерживать всех свойств объекта Column.
Напомню, ключом таблицы называется совокупность полей, однозначно идентифицирующая каждую запись таблицы. Если для идентификации записи требуется несколько полей, то такой ключ называется составным. Среди возможных ключей выделяется один, называемый первичным ключом (primary key). Access при построении таблицы, если не задается ее ключ, предлагает включить в число полей специальное поле типа "Счетчик". Это поле объявляется ключевым, а его значения строятся автоматически, чаще всего путем увеличения предыдущего значения счетчика на 1 при каждом добавлении новой записи, что и обеспечивает уникальность значений ключа. Однако, заметьте, среди типов полей, задаваемых перечислением DataTypeEnum, значения, соответствующего полю "Счетчик", нет.
Таблицы в реляционных базах данных связываются между собой, за счет того, что они имеют общие ключевые поля. Поле в связанной таблице называется внешним ключом, если это поле является частью ключа другой таблицы. Таблица с первичным ключом называется основной или базисной, а таблица, содержащая внешний ключ, связанной или связующей. В предыдущих версиях Access все таблицы обязаны были иметь первичный ключ. В Access 2000 связующие таблицы могут и не иметь первичного ключа. Разумно, однако, задавать ключи для всех таблиц базы данных. Ключ, который не является первичным, имеет статус "уникальный" (Unique).
Объект Key представляет первичный, внешний или уникальный ключ. Создается объект конструктором New, а добавляется в коллекцию методом Append, в соответствии с приведенной выше схемой. С объектной точки зрения объект Key устроен достаточно просто. Также как и многие другие объекты ADOX, он не имеет ни событий, ни методов, - только свойства. Их немного, - всего 6. Вот их описание:
Property Name As String. Задает имя ключа.Property Type As KeyTypeEnum. Задает тип ключа. Возможные значения задаются константами перечисления: adKeyPrimary, adKeyForeign, adKeyUnique, определяющими, соответственно, первичный, внешний и уникальный ключ.Property Columns As Columns. Возвращает коллекцию полей для составного ключа.Property DeleteRule As RuleEnum, Property UpdateRule As RuleEnum. Эти два свойства определяют правила (процедуры), выполняемые при удалении или изменении первичного ключа.Property RelatedTable As String. Задает таблицу, которой принадлежит ключ, что позволяет подняться по иерархии объектов.Если таблица имеет первичный ключ, то она имеет и индекс, который автоматически создается по данному ключу. Однако индексы могут создаваться и независимо от создания ключевых полей. На индексы не накладывается жесткое условие уникальности значений индекса в таблице. Индексы служат для того, чтобы ускорить операции по поиску и сортировке данных таблицы. Платой за это является увеличение памяти, требуемой для индексов и увеличение времени, требуемое на перестроение индексов, когда записи в таблицу добавляются или удаляются. Для больших, редко обновляемых таблиц, к которым часто приходится обращаться для поиска данных, построение индексов может существенно ускорить работу с базой данных. Для больших, часто обновляемых таблиц задание индексов может приводить к потере эффективности. Поэтому разумное введение индексов требует, как правило, проведения специальных исследований и опыта работы у администратора базы данных.
Объект Index задает индекс таблицы. Он создается с использованием конструктора New и добавляется в коллекцию Indexes методом Append, в соответствии с ранее приведенной схемой. Как и объекты Table и Key он имеет только свойства. Рассмотрим их:
Property Name As String. Свойство по умолчанию, задает имя индекса.Property PrimaryKey As Boolean, Property Unique As Boolean, Property Clustered As Boolean. Булевы свойства, позволяющие определить, является ли Property Columns As Columns. Для составного индекса возвращает коллекцию полей, входящих в индекс.Property IndexNulls As AllowNullsEnum. Позволяет указать, как обрабатываются значения Null, если они встречаются в поле индекса.Property Properties As Properties. Возвращает Пора привести пример программного создания таблицы и работы с объектами, связанными с таблицами базы данных. В предыдущих примерах уже была создана программно база данных с именем NewDB. Рассмотрим еще одну процедуру из модуля "TestingADOX", в которой в эту базу данных будет добавлена таблица "Книги", уже появлявшаяся в наших примерах. Но теперь создадим эту таблицу программно, используя возможности объектов ADOX. Вот текст процедуры, решающей эту задачу:
Public Sub CreateTable() 'Создание таблицы "Книги" и присоединение ее к базе данных NewDB 'Создание объектов - таблицы, ее полей и ключа Dim myT As New ADOX.Table 'таблица Dim myC(1 To 6) As New ADOX.Column 'поля таблицы Dim myK As New ADOX.Key 'ключ таблицы 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 'Создание объекта Table 'Создание полей таблицы myC(1).ParentCatalog = Cat1 myC(1).Name = "Код_книги" myC(1).Type = adInteger Call myT.Columns.Append(myC(1)) myC(2).ParentCatalog = Cat1 myC(2).Name = "Автор" Call myT.Columns.Append(myC(2)) myC(3).ParentCatalog = Cat1 myC(3).Name = "Название_книги" Call myT.Columns.Append(myC(3)) myC(4).ParentCatalog = Cat1 myC(4).Name = "Год_издания" Call myT.Columns.Append(myC(4)) myC(5).ParentCatalog = Cat1 myC(5).Name = "Число_страниц" myC(5).Type = adSmallInt Call myT.Columns.Append(myC(5)) myC(6).ParentCatalog = Cat1 myC(6).Name = "Цена" myC(6).Type = adCurrency Call myT.Columns.Append(myC(6)) 'Создание ключа myK.Columns.Append (myC(1)) myK.RelatedTable = "myT" myK.Type = adKeyPrimary myK.Name = "Код_книги" Call myT.Keys.Append(myK) 'Присоединение таблицы myT.Name = "Книги" Call Cat1.Tables.Append(myT) End Sub
А теперь комментарии к этой процедуре:
Table, Column и Key. Работа с ними ведется в полном соответствии со схемой, которую я приводил выше. Заметьте, я ввел массив объектов Column, число элементов которого определяется по числу полей, добавляемых в таблицу.Append в момент присоединения поля к коллекции не увенчались успехом в данном контексте. В этом случае все поля создаваемой таблицы в Access имели один и тот же текстовый тип.myK я добавил к его коллекции Columns ключевое поле - уже созданный объект myC(1). Заметьте, добавление в коллекцию Columns объекта Key возможно только после того, как объект добавлен в коллекцию Columns объекта Table.myT, после чего мне осталось только присоединить объект к коллекции Keys.Tables и Keys уже сформированы, к коллекции Tables объекта Catalog, для которого установлено соединение с ранее созданной базой данных Access с именем NewDB.В следующем примере в таблицу "Книги" я добавляю два индекса, один из них связан с полем "Автор", другой - "Название_книги". Приведу процедуру, решающую задачу:
Public Sub AddIndex()
'Процедура добавляет индексы в таблицу "Книги"
Dim myT As ADOX.Table 'таблица
Dim myC1 As Column, myC2 As Column 'поля таблицы
Dim myInd1 As New Index, myInd2 As New Index 'индексы таблицы
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'Связывание объекта Table с таблицей "Книги"
Set myT = Cat1.Tables("Книги")
Set myC1 = myT.Columns("Автор")
Set myC2 = myT.Columns("Название_книги")
'Формирование индексов
myInd1.Columns.Append (myC1)
myInd1.Name = "Author"
myInd1.PrimaryKey = False
myInd1.Unique = False
myInd1.Clustered = False
myInd2.Columns.Append (myC2)
myInd2.Name = "Book"
myInd2.PrimaryKey = False
myInd2.Unique = False
myInd2.Clustered = False
'Добавление индексов
Call myT.Indexes.Append(myInd1)
Call myT.Indexes.Append(myInd2)
'Отладочная печать
Debug.Print "Число индексов =", myT.Indexes.Count
For Each myInd1 In myT.Indexes
Debug.Print "Имя индекса -", myInd1.Name
Next myInd1
End Sub
Результаты отладочной печати для нее следующие:
Число индексов = 3 Имя индекса - Код_книги Имя индекса - Author Имя индекса - Book
Заметьте, индексов три, поскольку первый из них был создан автоматически в момент добавления ключа в таблицу.
Думаю, что в особых комментариях данная процедура не нуждается, поскольку работа с индексами ведется по той же схеме, что и при работе с ключами таблицы. Чтобы убедиться, что все работает должным образом, взгляните, как выглядит таблица "Книги", открытая в режиме конструктора:
(рис 6.9) Программно созданная таблица "Книги"
В следующей процедуре в уже созданную таблицу добавляется новое поле и новый составной ключ. Приведу текст процедуры:
Public Sub InsertItems()
'Эта процедура добавляет элементы в уже созданную таблицу
Dim myT As Table
Dim myC As New Column
Dim myK As New Key
Dim myInd As Index
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'Связывание объекта Table с таблицей "Книги"
Set myT = Cat1.Tables("Книги")
'Добавление поля
myC.ParentCatalog = Cat1
myC.Name = "Художник"
myC.Attributes = adColNullable
Call myT.Columns.Append(myC)
'Добавление составного ключа
myK.Columns.Append (myT.Columns("Автор"))
myK.Columns.Append (myT.Columns("Название_книги"))
myK.RelatedTable = "myT"
myK.Type = adKeyUnique
myK.Name = "AandB"
Call myT.Keys.Append(myK)
End Sub
Все эти добавления в таблицу возможны и проходят без препятствий. Заметьте, добавление составного ключа приведет к добавлению элемента в коллекцию индексов. Взгляните, как выглядит измененная таблица "Книги". Здесь же на рисунке можно увидеть и открытое окно с индексами таблицы, в котором отображается введенный составной ключ.
(рис 6.10) Изменения полей и индексов таблицы "Книги"
К сожалению, в Access попытка программно изменить характеристики полей таблицы или ее ключей и индексов невозможна. Я специально написал процедуру, в которой делается такая попытка:
Public Sub Modify()
'Изменение характеристик таблицы в Access невозможно!
Dim myT As Table
Dim myC As Column
Dim myK As Key
Dim myInd As Index
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'Связывание объекта Table с таблицей "Книги"
Set myT = Cat1.Tables("Книги")
Set myC = myT.Columns("Название_книги")
'myC.DefinedSize = 150
Set myC = myT.Columns("Число_страниц")
'myC.Type = adVarWChar
Set myK = myT.Keys("Код_книги")
'myK.Type = adKeyUnique
Set myK = myT.Keys("AandB")
'myK.Type = adKeyPrimary
End Sub
Как видите, все операторы, производящие изменения закомментированы, поскольку выполнение любого из них приводит к появлению ошибки. Хотя модификация характеристик полей, ключей и индексов таблицы базы данных Access невозможна, удаление самих элементов происходит без затруднений. Это позволяет при необходимости внесения изменений в характеристики элемента, вначале удалить его, а затем ввести заново с новыми характеристиками. Приведу процедуру, выполняющую удаление поля, ключа и индекса:
Public Sub Removing()
'Удаление элементов таблицы
Dim myT As Table
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'Связывание объекта Table с таблицей "Книги"
Set myT = Cat1.Tables("Книги")
myT.Columns.Delete ("Художник")
myT.Indexes.Delete ("AandB")
'myT.Keys.Delete ("AandB")
End Sub
Заметьте, когда я вводил ключ "AandB", то был создан индекс с этим же именем. Удалив этот индекс, я автоматически удалили и ключ. По этой причине последний оператор процедуры закомментирован.
На этом я закончу рассмотрение объектов, связанных с таблицами базы данных и займусь другими объектами модели ADOX, которые позволяют работать с запросами, представлениями и пользователями.
Объект Procedure представляет собой хранимую процедуру. Хранимые процедуры, текст которых, чаще всего, пишется на SQL, хранятся на сервере в оттранслированном виде, что ускоряет их выполнение. Еще одно немаловажное достоинство состоит в том, что облегчается задача программиста, вызывающего хранимую процедуру, когда он пишет свой код на VBA или VBScript, поскольку ему не нужно вникать в тонкости операторов SQL.
Коллекция Procedures объекта Catalog включает в себя все хранимые процедуры базы данных. Как и у всех коллекций ADOX у нее три метода: Append, Delete, Refresh и два свойства - Item и Count. Тем не менее, есть особенность в создании объекта Procedure и добавлении его в коллекцию. Прежняя схема, используемая при создании объектов Table, Key, Index для него не применима. Этот объект не может быть создан с помощью конструктора New. Для того чтобы добавить в коллекцию новый объект Procedure, следует создать объект Command, задать текст процедуры, используя свойство CommandText, а затем применить метод Append коллекции Procedures. Заметьте, у этой коллекции метод Append имеет два параметра, - первый задает имя хранимой процедуры, второй объект Command, определяющий, по существу, саму процедуру. Используя объекты Procedure, Procedures и Command можно программно создать хранимую процедуру, модифицировать существующую процедуру или удалить процедуру из коллекции.
Объект Procedure устроен очень просто, - у него всего 4 свойства:
Property Name As String. Задает имя объекта. Имеет статус "только для чтения", что объясняется особенностями создания этого объекта.Property DateCreated As Variant, Property DateModified As Variant. Эти два свойства, также имеющие статус "только для чтения", задают дату создания и последней модификации процедуры.Property Command As Variant. Задает объект Command из библиотеки ADODB. В предыдущей главе этот объект был достаточно подробно описан и приведено большое число примеров работы с ним. Свойство является центральным, поскольку именно объект Command определяет суть хранимой процедуры и позволяет создать процедуру.В базе данных Access нет понятия хранимых процедур, их роль играют хранимые запросы. Было бы хорошо, если бы Провайдер Microsoft Jet при создании объектов коллекции Procedures создавал вместо этого хранимые запросы. Однако этого не происходит, - запросы не создаются. Поэтому, казалось бы, при работе с базой данных Access создавать программно процедуры невозможно и, главное, бесполезно. Тем не менее, при попытке создать коллекцию Procedures никаких ошибок не возникает и, по крайней мере, одна процедура создается и с ней можно впоследствии работать, вызывая ее на исполнение.
Я приведу сейчас пример, в котором демонстрируется сам способ создания коллекции Procedures. Этот способ не зависит от Провайдера и базы данных, он может с успехом использоваться при работе с Access
Public Sub CreateQuery()
'Создание хранимых процедур
Dim myCmd1 As New Command, myCmd2 As New Command
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'SQL-запросы, представляющие тексты хранимых процедур
myCmd1.CommandText = "Select [Книги.Автор],[Книги.Название_книги]" _
"From [Книги]" _
"WHERE ((([Книги.Год_издания])= 1999))" _
"ORDER BY [Книги.Автор];"
'Запрос с параметром
myCmd2.CommandText = "Parameters [Avtor] Text(50);" _
"Select [Книги.Автор],[Книги.Название_книги]" _
"From [Книги]" _
"WHERE ((([Книги.Автор])= [Avtor]));"
'Удаление процедур
Dim i As Integer
For i = 0 To Cat1.Procedures.Count - 1
Cat1.Procedures.Delete (i)
Next i
'Добавление процедур
Debug.Print Cat1.Procedures.Count
Call Cat1.Procedures.Append("Books1999", myCmd1)
Debug.Print Cat1.Procedures.Count
Call Cat1.Procedures.Append("BooksOfAuthor", myCmd2)
Debug.Print Cat1.Procedures.Count
End Sub
Сама процедура и то, как она выполняется, нуждается в подробных комментариях:
Command.CommandText этих объектов позволяет задать текст команд, представляющих в данном случае SQL-запросы. Второй из этих запросов представляет запрос с параметром.Append коллекции Procedures, которому в качестве одного из параметров передаются созданные объекты Command. По завершении работы процедуры создается впечатление, что коллекция Procedures с двумя элементами успешно создана. Однако это не совсем так.Count в конце работы этой процедуры будет возвращать число 1, а сама коллекция будет хранить второй Books1999 ", хотя коллекция и пуста.Не следует особенно расстраиваться, что хранимые процедуры не работают корректно в Access, - они и не должны работать. Можно, однако, использовать тот факт, что с одной процедурой все-таки работать разрешается. Вот пример работы, демонстрирующий работу с сохраненной в предыдущем примере процедурой с именем BooksOfAuthor, имеющей имя автора в качестве параметра запроса:
Public Sub WorkWithProcs()
'работа с хранимыми процедурами
Dim myCmd As Command
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
Set myCmd = Cat1.Procedures("BooksOfAuthor").Command
Set Rst1 = myCmd.Execute(, "Пушкин")
With Rst1
.MoveFirst
Do While Not .EOF
Debug.Print .Fields(0), .Fields(1)
.MoveNext
Loop
End With
End Sub
В результате работы этой процедуры на печать были выданы все книги, написанные Пушкиным, записи о которых хранились в базе данных.
Объекты View задают так называемые представления. Они подобны объектам Procedure, хранятся на сервере в виде оттранслированных запросов и задают отфильтрованное множество записей. Представления являются элементами таких баз данных как Microsoft SQL Server , а, следовательно, и View имеет те же свойства, что и объект Procedure и соответствующие коллекции устроены одинаково, то я не буду задерживаться на более подробном описании этих объектов.
Для того чтобы обеспечить защиту баз данных от неавторизованного доступа, для того чтобы разным пользователям предоставить разные права доступа к тем или иным объектам базы данных - таблицам, запросам, полям таблицы, одним из основных используемых механизмов является введение учетных записей для пользователей и групп пользователей базы данных. На объектном уровне защита баз данных поддерживается объектами и их Users и Groups объекта Catalog возвращают одноименные коллекции всех пользователей и всех групп, связанных с базой данных - объектом Catalog. Но поскольку каждая группа содержит одного или нескольких пользователей, то свойством Users обладает и объект Group, для него свойство возвращает всех пользователей, входящих в группу. Groups обладает объект User, для него свойство возвращает все группы, в которые входит данный пользователь.
Коллекции Users и Groups устроены, как и все коллекции ADOX, у них три метода - Append, Delete, Refresh и два свойства: Item и Count. Создаются объекты и присоединяются к коллекции также согласно общей схеме, - при создании используется конструктор New, для присоединения - метод Append. Замечу, что, наконец-то, хотя бы в ADOX достигнута желаемая общность в организации коллекций.
Объект определяет учетную запись пользователя, позволяет задать имя пользователя, пароль, права доступа к объектам базы данных и группы, в которые входит этот пользователь. У этого объекта два свойства:
Property Name As String. Задает имя пользователя. Является свойством по умолчанию.Property Groups As Groups. Возвращает одноименную коллекцию, содержащую все группы, в которые входит данный пользователь.Пароль пользователя и разрешения на право доступа к тем или иным объектам базы данных задаются методами объекта User. Методов всего три:
Sub ChangePassword(OldPassword As String, NewPassword As String). Чтобы задать новый пароль, нужно знать и старый пароль. В случае, когда пароль задается в первый раз, старым паролем является пустая строка.Function GetPermissions(Name, ObjectType As ObjectTypeEnum, [ObjectTypeId]) As RightsEnum. Метод (функция) возвращает разрешения на то, что может делать пользователь с объектом базы данных, чье имя задается параметром Name, а тип - параметром ObjectType. Третий параметр указывается лишь в тех исключительных ситуациях, когда речь идет об объектах, специфических для Провайдера. Права пользователя на объект задаются константами из перечисления RightsEnum. Возвращаемое значение может быть суммой отдельных констант и представляет маску, задающую отдельные разрешения. Различных констант в перечислении достаточно много, приведу лишь некоторые из них: adRightCreate, adRightDelete, adRightMaximumAllowed, adRightNone.Sub SetPermissions(Name, ObjectType As ObjectTypeEnum, Action As ActionEnum, Rights As RightsEnum, [Inherit As InheritTypeEnum = adInheritNone], [ObjectTypeId]). Метод позволяет установить разрешения на возможные действия с элементом базы, чье имя задано параметром Name, а тип - параметром ObjectType. Параметр Rights задает маску, определяющую право на те или иные действия, значением параметра является сумма констант из перечисления RightsEnum. Параметр Action задает действия, выполняемые при установке разрешений. Параметр Inherit указывает, как объекты наследуют разрешения.Группы облегчают управление правами доступа. Проще, быстрее и правильнее с содержательной точки зрения задать права группы, чем задавать их индивидуально для каждого пользователя. Хотя, конечно же, есть пользователи, для которых права задаются индивидуально.
С объектной точки зрения объект Group, во многом, устроен аналогично объекту User, - у него нет метода ChangePassword, поскольку у групп нет паролей, а свойство Groups заменяет симметричное свойство Users. Таким образом, у объекта Group два свойства: Name, Users и два метода - GetPermissions, SetPermission.
База данных Microsoft Jet позволяет устанавливать защиту с использованием механизма учетных записей пользователей и групп пользователей. По умолчанию, когда создается новая база данных, автоматически создаются две типичные группы пользователей - Admins и Users. Первая из них включает администраторов базы, обладающих максимальными правами, другая - "обычных" пользователей. В специальной системной базе данных Access хранится информация о группах, о пользователях, их именах и паролях. Эта база данных хранится в файле с именем System и расширением - mdw. Обычный путь к этому файлу - "c:\Program Files\Microsoft Office\Offce\system.mdw". Фактический путь можно посмотреть и при необходимости изменить в реестре, где он находится в разделе:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\9.0\Access\Jet\4.0\Engines Параметр, задающий путь, имеет имя SystemDB.
В нижеследующем примере я ввел в дополнение к существующим группам еще две группы - читателей и писателей - с именами, соответственно, Readers и Writers и четырех новых пользователей, входящих по-разному в эти группы. Группам заданы различные права на доступ к таблицам базы данных. Но, прежде чем давать другие пояснения приведу соответствующий программный код:
Public Sub CreateUsersАndGroups()
'Cоздание пользователей и групп
Dim myUs(1 To 4) As New User 'учетные записи пользователей
Dim myGr(1 To 2) As New Group 'учетные записи групп
'Установить соединение с базой NewDB
'и системной базой данных System.mdw
CreateConnectionSystemDB
Cat1.ActiveConnection = Con1
'Пользователи
myUs(1).Name = "Vladimir"
myUs(1).ChangePassword "", "Old111"
Cat1.Users.Append myUs(1)
myUs(2).Name = "Nina"
Call myUs(2).ChangePassword("", "Best111")
Call Cat1.Users.Append(myUs(2))
myUs(3).Name = "Ilya"
Call myUs(3).ChangePassword("", "Prim111")
Call Cat1.Users.Append(myUs(3))
myUs(4).Name = "Yuly"
Call myUs(4).ChangePassword("", "Clev111")
Call Cat1.Users.Append(myUs(4))
'Группы
myGr(1).Name = "Readers"
Call Cat1.Groups.Append(myGr(1))
myGr(1).Users.Append (myUs(1))
myGr(1).Users.Append (myUs(2))
myGr(1).Users.Append (myUs(3))
myGr(1).Users.Append (myUs(4))
myGr(2).Name = "Writers"
Call Cat1.Groups.Append(myGr(2))
myGr(2).Users.Append (myUs(1))
'Установление прав
Call myGr(1).SetPermissions("Книги", _
adPermObjTable, adAccessSet, adRightRead, adInheritBoth)
Call myGr(1).SetPermissions("Заказчики", _
adPermObjTable, adAccessSet, adRightRead, adInheritBoth)
Call myGr(2).SetPermissions("Книги", _
adPermObjTable, adAccessSet, adRightFull, adInheritBoth)
Call myGr(2).SetPermissions("Заказчики", _
adPermObjTable, adAccessSet, adRightFull, adInheritBoth)
Call myUs(3).SetPermissions("Заказчики", _
adPermObjTable, adAccessSet, adRightMaximumAllowed, adInheritBoth)
End Sub
Прежде всего, хочу обратить внимание на главную особенность работы с объектами и их коллекциями, когда речь идет о Провайдере Microsoft Jet. При установлении соединения необходимо связаться не только с самой базой данных, но и с системной базой данных. По этой причине вызывается специальная процедура, устанавливающая такое соединение:
Public Sub CreateConnectionSystemDB() 'Создание соединения с базой данных Access - NewDB ' и системной базой данных - System.mdw Dim strConnStr As String If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.ConnectionString = _ "Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb;" _ "Jet OLEDB:System database =" _ "c:\Program Files\Microsoft Office\Office\system.mdw" 'Открытие соединения Con1.Open End Sub
Заметьте, я задаю специфическое для Провайдера свойство "Jet OLEDB:System database" непосредственно в строке соединения, хотя мог бы это сделать, используя коллекцию Properties.
Возвращаясь к процедуре CreateUsersAndGroups, приведу еще несколько комментариев к ее работе:
User и Group, присоединение их к коллекциям идет по традиционной схеме.Users для каждой группы. Заметьте, при этом автоматически будут созданы коллекции Groups для каждого объекта User."Ilya".В заключение пара экранных снимков. Взгляните, как выглядит результаты одного из запросов к системной базе данных, после выполнения процедуры CreateUsersAndGroups:

(рис 6.12) Результаты запроса к системной базе данных Access(рис 6.11) Показ прав доступа к элементам базы данных AccessКонечно, все, что я сделал программно, можно сделать и руками, выбрав пункт меню "Сервис | Защита" и открыв соответствующее окно, например, окно разрешений, показанное на рис. 6.12.
Последний пример в этой главе я хочу посвятить хотя бы беглому знакомству использования объектов ADO в сценариях, написанных на VBScript. Я рассмотрю пример, представляющий фрагмент создания Web-узла. Создаваемый узел является активным, он может реагировать на действия пользователя, используя для этих целей технологию ASP(Active Server Page) страниц. Напомню, эти страницы содержат сценарии, которые собственно и определяют реакцию на действия пользователя. Особенностью ASP-страниц является то, что код сценариев выполняется на сервере и пользователю пересылается статическая страница, содержащая не код, а результат его выполнения.
В рассматриваемом фрагменте рассматривается типичная для активных Web-узлов ситуация, когда пользователь заполняет некоторую форму и отсылает ее на сервер. ASP-страница обрабатывает данные формы, сохраняет их в базе данных, добавляя новые записи в таблицы, и формирует новую страницу, пересылаемую пользователю. Вот как выглядит страница узла, на которой пользователь заполняет форму:
(рис 6.13) Web-страница, содержащая форму, отсылаемую на серверПриведу теперь текст ASP-страницы, которая выполняется на сервере:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title>vbazu</title>
<meta name="GENERATOR" content="Microsoft FrontPage 4.0">
<meta name="ProgId" content="FrontPage.Editor.Document">
<meta name="Microsoft Theme" content="none, default">
<meta name="Microsoft Border" content="none, default">
</head>
<body>
<table border="0" width="100%" bgcolor="#0000FF" cellspacing="0" cellpadding="0" height="81">
<tr>
<td width="100%" height="62">
<p align="center">
<b><font face="Bradley Hand ITC" size="7" color="#FFFFFF">BiVANTnbsp;nbsp;</font></b></td>
</tr>
<tr>
<td width="100%" bgcolor="#FFFF00" height="19">
<font color="#0000FF"><b><a href="index.htm">BiVANT</a>nbsp;
<a href="книги.htm">Книги</a>nbsp; <a href="статьи.htm">Статьи</a>nbsp;
<a href="учебники.htm">Учебники</a>nbsp; <a href="обучение.htm">Обучение</a>nbsp;
<a href="чайничек.htm">Чайничек</a>nbsp;
<a href="Дело%20отца/дело_отца.htm">Дело_Отца</a>nbsp;
<a href="онас.htm">О нас</a>nbsp; <a href="авторы.htm">Авторы</a></b></font></td>
</tr>
</table>
<p>
<%
'Переменные, задающие поля формы
Dim DAvtor, DFam, DName, DVid, DGorod, Dmail, DOtziv, DVopros, DPriob
'Передача значений полей формы
DFam= Request.Form("Fam")
DName = Request.Form("Name")
DVid= Request.Form("Vid")
DGorod =Request.Form("Gorod")
Dmail= Request.Form("mail")
DOtziv= Request.Form("Otziv")
DPriob= Request.Form("Priob")
DVopros =Request.Form("Vopros")
DAvtor = Request.Form("Avtor")
'Объявление объектов ADO - Connection, Command, Recordset
Dim Cconnect, Cmd, Records
Dim StrSQL 'строка запроса
' Установить связь с базой данных
Set Cconnect =Server.CreateObject("ADODB.Connection")
Cconnect.ConnectionString= "provider=microsoft.jet.oledb.4.0;"
"data source ='d:\Bivant\svyaz.mdb'"
Cconnect.Open
'Формирование строки запроса
StrSQL = "Select * From VoprosOtvet"
'Создание команды и задание свойств объекта Command
Set Cmd =Server.CreateObject("ADODB.Command")
Cmd.ActiveConnection = Cconnect
Cmd.CommandText = StrSQL
Cmd.CommandType =1
'Работа с RecordSet
Set Records =Server.CreateObject("ADODB.Recordset")
With Records
'Открытие обновляемого объекта RecordSet
.Open Cmd,,2,3
' Добавление записи
.MoveFirst
.AddNew
.Fields("Автор")=DAvtor
.Fields("Вопрос") = DVopros
.Fields("Отзыв") = DOtziv
.Fields("Фамилия") =DFam
.Fields("Имя") =DName
.Fields("Город") = DGorod
.Fields("Вид_деят") =DVid
.Fields("Почта") =Dmail
.Fields("Приобрести") =DPriob
.Update
End With
%>
<b>Спасибо за Ваш интерес к нашей тематике!</b>
</p>
<p><b>Мы постараемся учесть Ваши пожелания и
своевременно ответить на вопрос. </b>
</p>
</body>
</html>
Приведу теперь комментарии к этому тексту:
Variant, поэтому для переменных, соответствующих полям формы не задан тип.ASP Request позволяет передать значения из полей формы в переменные VBScript.CreateObject объекта Server.Recordset и обновление базы данных, выполняется на VBScript совершенно аналогично тому, как это делалось на VBA. В результате выполнения этого сценария база данных действительно изменяется и данные, посланные в форме, будут сохранены в соответствующей таблице базы данных.На этом я закончу разговор о компонентах ADO, представляющих универсальный способ взаимодействия с самыми разными источниками данных.
В предыдущей главе было дано общее представление об объектах ADO и подробно рассмотрены свойства, методы и события трех основных объектов - Connection, Command, Recordset. Но я хочу продолжить разговор и дать представление не только о главных героях, но и всех остальных объектах этой модели. Но прежде чем рассматривать новые объекты, давайте разберемся в таком важном вопросе, как обрабатываются события, возникающие с объектами ADO, и о том, как справляться с ошибками, возникающими в процессе работы с этими объектами. Я напомню, что событиями обладают лишь два объекта - Recordset и Connection.
Давайте разберемся с деталями построения обработчиков событий объектов ADO. Прежде всего, рассмотрим, как они создаются, в какой модуль проекта их следует поместить. Хотя объекты Recordset и Connection обладают событиями, но появляются они как объекты без событий. Необходимо предпринять обычные для VBA в таких ситуациях действия, чтобы создать объекты с событиями и написать процедуры, обрабатывающие события, - обработчики событий. В первом томе и других книгах серии "Офисное программирование" я подробно рассказывал о том, как это делается. Напомню, что для решения этой задачи нужно выполнить три шага:
MyEventsADO, в котором описать вложенные объекты - Connection WithEvents и Recordset WithEvents.MyEventsADO. Заметьте, после объявления переменных With Events в этом классе появится возможность написания обработчиков событий, следуя обычной технологии.TestingADO, создать экземпляры класса MyEventsADO и связать их с глобальными или локальными объектами Rst1 и Con1, задающими набор записей и соединение.Чтобы сделать дальнейшее описание конкретным, давайте рассмотрим создание обработчика события WillChangeField объекта Recordset, которое, напомню, появляется при попытках изменить значение поля в записи набора, но перед тем, как это изменение реально произойдет. Вот как выглядит модуль класса MyEventsADO, содержащий обработчик этого события:
'Класс MyEventsADO, определяющий события ADO Public WithEvents EvRst As ADODB.Recordset Public WithEvents EvCon As ADODB.Connection Private Sub EvRst_WillChangeField(ByVal cFields As Long, _ ByVal Fields As Variant, adStatus As ADODB.EventStatusEnum, _ ByVal pRecordset As ADODB.Recordset) 'Печать параметров обработчика события MsgBox "Номер поля = " cFields vbCrLf _ "Значение поля = " Fields(cField) vbCrLf _ "Позиция записи в наборе = " _ pRecordset.AbsolutePosition vbCrLf _ "Статус выполняемой операции = " adStatus 'Изменение статуса приведет к появлению ошибки 'при попытке изменить значение поля, обработка которой 'позволит отменить операцию. If IsNumeric(Fields(cField)) Then If Fields(cField) > 75 Then adStatus = adStatusCancel End If End Sub
Первым делом я включил в обработчике события WillChangeField печать всех его параметров на входе. Обратите внимание, обработчику события передается указатель на сам объект Recordset, поэтому доступна практически любая информация, связанная с этим объектом. Я, например, использовал его, чтобы вывести на печать позицию записи в наборе, поля которой обновляются. Во второй части обработчика я изменяю значение статуса при выполнении некоторого условия на обновляемое поле. Делаю это для того чтобы реализовать возможность отмены изменения значения, когда в результате анализа, проведенного в обработчике, принимается решение о нежелательности выполнения изменений. Заметьте, для этого свойству статус, которое имело значение adStatusOk, уведомляющее о том, что изменения возможны, я присваиваю значение adStausCancel. В этом случае при выходе из обработчика возникнет ошибка в операторе, производящем изменения. Обработав соответствующим образом эту ошибку, я смогу выполнить поставленную задачу.
Давайте теперь рассмотрим процедуру, в которой происходит обновление полей записей набора, чьи действия будут вызывать появление события WillChangeField. Эта процедура изменяет цену у некоторых книг из таблицы "Книги" нашей тестовой базы данных и добавляет новую запись в эту таблицу. При всех этих изменениях начнут работать события:
Public Sub CreateEvents() 'Объект с событиями Dim imEvRst As New MyEventsADO 'добавление и изменение записей базы данных Dim recExist As Boolean ' Связывание объекта с событиями Set imEvRst.EvRst = Rst1 'Создать соединение CreateConnection 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic 'Изменение записей recExist = False .MoveFirst Do While Not .EOF 'Обработка текущей записи On Error Resume Next If !Название = "Офисное программирование" Then recExist = True If ![Год издания] < 2000 And !Цена < 100 Then MsgBox "Старая цена =" Str(!Цена) !Цена = !Цена * 2 .Update End If .MoveNext Loop If Not recExist Then .AddNew !Автор = "Владимир Биллиг" !Название = "Офисное программмирование" ![Год издания] = 2001 ![Число страниц] = 599 !Цена = 150 .Update End If End With End Sub
Обратите внимание, первым делом я объявляю объект imEvRst созданного класса MyEventsADO и связываю его с глобальным объектом Rst1, после чего последний начнет реагировать на события. Сколько раз будет появляться событие WillChangeField? Очевидно, при каждом выполнении оператора !Цена = !Цена * 2, изменяющего значение соответствующего поля записи. Вот как выглядят окошки функции MsgBox, одно из которых открывается перед выполнением этого оператора, а второе в обработчике события в момент выполнения оператора:
(рис 6.1) Окна, открываемые перед изменением поля и в момент измененияСоответствующие сообщения появляются и при создании новой записи, причем столько раз, сколько полей имеет запись.
Важную роль в этой процедуре играет и оператор On Error Resume Next. Всякий раз, когда в обработчике события изменяется значение переменной adStatus, возникнет ошибка при выполнении оператора, изменяющего значение поля и послужившего причиной возникновения события. Оператор OnError позволяет в этом случае обойти выполнение оператора и, тем самым, избежать изменения значения, что и хотелось. Взгляните, как выглядит окно, уведомляющее об ошибке, появляющееся, если в процедуре закомментировать оператор On Error:
(рис 6.2) Окно, уведомляющее о возникновении ошибки при изменении значенияХочу теперь рассмотреть пример построения обработчика события, возникающего после завершения операции. Пример хочу построить такой, чтобы при выполнении операции Провайдер столкнулся с трудностями и сформировал объекты Error, а обработчик события соответственно обрабатывал эти ошибки. Но прежде чем перейти к рассмотрению этого примера, есть смысл более подробно познакомиться с объектом Error - его свойствами и методами.
Напомню, что элементы коллекции Errors появляются при выполнении очередной команды, когда Провайдер сталкивается с определенными трудностями и не может нормальным образом завершить выполнение команды. Коллекция Errors всегда связана только с одной командой, - при первой ошибке в новой команде происходит чистка старого содержимого Errors объекта Connection возвращает эту коллекцию. Добавлять программно элементы в коллекцию невозможно, это делается автоматически, когда Провайдер обнаруживает новую ошибку.
У коллекции два свойства:
Count - возвращает число элементов коллекции,Item - свойство по умолчанию позволяет по индексу получить необходимый элемент коллекции - объект Error.У коллекции два метода:
Clear - позволяет принудительно очистить содержимое коллекции,Refresh - обновляет коллекцию.У объекта Error нет методов и нет событий. У него есть только свойства:
Property Description As String (read-only). Задает описание ошибки, которое можно показать пользователю, если не предвидится другой способ обработки ошибки.Property HelpContext As Long (read-only), Property HelpFile As String (read-only). Эти свойства позволяют обратиться за дополнительными разъяснениями к справочной системе, если в ней содержится более подробная информация об ошибке.Property NativeError As Long (read-only). Код ошибки, специфический для данного Провайдера. Полезен, если есть описание ошибок Провайдера.Property Number As Long (read-only). Номер, однозначно идентифицирующий ошибку. Является константой типа HRESULT. Значения соответствуют, но не совпадают со значениями констант, принадлежащих перечислению ErrorValueEnum. Классификация ошибок достаточно подробная, констант в этом перечислении много. Приведу значения лишь некоторых: adErrCantChangeConnection, adErrCantConvertvalue, adErrDataOverflow, adErrFieldsUpdateFailed, adErrInTransaction. Замечу, что, зная номер Number, не просто разобраться, какой тип ошибки имеет место.Property Source As String (read-only). Задает имя объекта, породившего ошибку. В совокупности свойства Source, Number и Description позволяют проанализировать ошибку и, возможно, в диалоге с пользователем устранить ее причину.Property SQLState As String (read-only). Задает 5-и символьный код, соответствующий стандарту ANSI SQL, определяющий состояние SQL, вызвавшее ошибку.Я хочу теперь рассмотреть ситуации, когда при выполнении операции по тем или иным причинам возникают ошибки. Мы рассмотрим, как выглядят обработчики ошибок в таких ситуациях, как можно проанализировать содержимое объектов Error, созданных Провайдером и переданных обработчику ошибок, как можно исправить ситуацию и продолжить выполнение.
Первым делом я смоделировал ситуацию, приводящую к ошибке в момент создания соединения, для чего сменил свойство " Provider ", не позаботившись о корректном задании характеристик этого Провайдера.
Вот как выглядит модернизованная процедура, организующая соединение:
Public Sub ConnectionWithEvents() 'Создание соединения с тестовой базой данных Access 'Объект с событиями Dim imEvCon As New MyEventsADO ' Связывание объекта с событиями Set imEvCon.EvCon = Con1 Dim strConnStr As String If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение 'Вариант 1 'Конфигурирование соединения Con1 'Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.Provider = "MSDASQL" Con1.ConnectionString = "Data Source=c:\!O2000\DsCd\Ch14\dbPP2000.mdb" Con1.CursorLocation = adUseClient 'Открытие соединения On Error GoTo Check Con1.Open Exit Sub Check: 'Повтор операции соединения Resume End Sub
В этой процедуре следует обратить внимание на следующие моменты:
imEvCon класса MyEventsADO, о создании которого я говорил выше.В класс MyEventsADO я добавил следующий обработчик события ConnectComplete:
Private Sub EvCon_ConnectComplete(ByVal pError As ADODB.Error, _ adStatus As ADODB.EventStatusEnum, _ ByVal pConnection As ADODB.Connection) 'Печать параметров обработчика события MsgBox "Обработчик события ConnectComplete! " vbCrLf _ "Строка соединения = " _ pConnection.ConnectionString vbCrLf _ "Статус выполняемой операции = " adStatus 'Печать параметров объекта Error If Not (pError Is Nothing) Then With pError MsgBox "Описание ошибки:" .Description vbCrLf _ "Код Провайдера:" .NativeError vbCrLf _ "Константа, идентифицирующая ошибку:" .Number vbCrLf _ "Имя объекта:" .Source vbCrLf _ "ANSI SQL код:" .SqlState End With End If If adStatus = adStatusErrorsOccurred Then 'Исправление ошибки pConnection.Provider = "Microsoft.jet.oledb.4.0" End If 'Анализ коллекции Errors Dim myErr As Error Dim MyCon As Connection Set MyCon = pConnection Debug.Print MyCon.Errors.Count For Each myErr In MyCon.Errors Debug.Print myErr.Description Next myErr MyCon.Errors.Clear Debug.Print MyCon.Errors.Count MyCon.Errors.Refresh Debug.Print MyCon.Errors.Count For Each myErr In MyCon.Errors Debug.Print myErr.Description Next myErr End Sub
Прокомментирую работу этой процедуры:
ConnectComplete работает тогда, когда обработчику передается объект Error. В функции MsgBox, окно которой также показано на рис. 6.3, формируется информация о свойствах объекта Error, в частности, описание ошибки показывает, что ODBC-Провайдер не сумел найти источник данных.ConnectionWithEvents в оператор Con1.Open, вызвавший событие, то из-за возникновения ошибки в Check. ConnectComplete, но в ситуации, когда ошибка не возникает. В этот раз будет работать только первая функция MsgBox, которая уведомит о нормальном завершении и выдаст подробную информацию в строке соединения.ConnectComplete выводится на печать отладочная информация о коллекции Errors объекта Connection. Я специально поместил ее в эту процедуру, чтобы обратить Ваше внимание на тот факт, что при обработке события Complete коллекция Errors объекта Connection еще не сформирована, она создается лишь после завершения работы этой процедуры. Поэтому, как ни странно, но при первом вызове, когда ошибка есть, коллекция Errors пуста, а во втором вызове, когда ошибки нет, коллекция существует, сохраняя историю первого вызова. Заметьте, по этой причине было бы разумнее с содержательной точки зрения поместить эту группу операторов в блок с меткой Check. Иллюстрацией к этому тексту являются результаты отладочной печати:0 0 0 1 [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified 0 0
Еще одна ошибка, которую я решил смоделировать, возникает при работе с объектом Recordset, - некорректно формируется значение поля при изменении записи. К уже имеющемуся обработчику события WillChangeField я добавил обработчик события ChangeFieldComplete. Поскольку общая схема экспериментов остается такой же, как и для объекта Connection, то я приведу тексты процедур без дополнительных комментариев. В уже описанной процедуре CreateEvents я заменил оператор
!Цена = !Цена * 2
на оператор
!Цена = "По Договоренности"
Эта замена приводит к ошибке несоответствия типов. Вот текст обработчика события ChangeFieldComplete:
Private Sub EvRst_FieldChangeComplete(ByVal cFields As Long, _ ByVal Fields As Variant, ByVal pError As ADODB.Error, _ adStatus As ADODB.EventStatusEnum, _ ByVal pRecordset As ADODB.Recordset) 'Печать параметров обработчика события MsgBox "Номер поля = " cFields vbCrLf _ "Значение поля = " Fields(cField) vbCrLf _ "Позиция записи в наборе = " _ pRecordset.AbsolutePosition vbCrLf _ "Статус выполняемой операции = " adStatus 'Печать параметров объекта Error If Not (pError Is Nothing) Then With pError MsgBox "Описание ошибки:" .Description vbCrLf _ "Код Провайдера:" .NativeError vbCrLf _ "Константа, идентифицирующая ошибку:" .Number vbCrLf _ "Имя объекта:" .Source vbCrLf _ "ANSI SQL код:" .SqlState End With End If 'Анализ коллекции Errors Dim myErr As Error Dim MyCon As Connection Set MyCon = pRecordset.ActiveConnection Debug.Print MyCon.Errors.Count For Each myErr In MyCon.Errors Debug.Print myErr.Description Next myErr End Sub
Вот как выглядит серия появляющихся MsgBox, вызываемой в процедурах: CreateEvents, WillChangeField, ChangeFieldComplete, при попытке изменить поле "Цена":
(рис 6.4) Серия окон, появляющихся при попытке изменить значение поля.Возможно, полезно взглянуть и на информацию, появляющуюся в окне отладки:
0 1 Operation was canceled. 1 Operation was canceled. 1 Multiple-step operation generated errors. Check each status value.
Свойства, методы и события основных объектов ADO - Connection, Command, Recordset, Error уже рассмотрены. В примерах появлялись и другие объекты - Field, Parameter, Property и их коллекции. Рассмотрим свойства и методы этих и других объектов ADO, но уже с меньшей степенью детализации.
Этот объект представляет отдельную запись набора Recordset. Поскольку, однако, объект Recordset не имеет свойства Records, возвращающее коллекцию записей, ни свойства Item, возвращающего отдельную запись, то явным способом получить этот объект из набора записей не удается. Некоторые Провайдеры возвращают объект Record в тех случаях, когда в результате запроса результатом является объект Recordset, состоящий из одной записи. Обычно создается этот объект методом Open, имеющим привычный для объектов ADO синтаксис:
Sub Open([Source], [ActiveConnection], [Mode As ConnectModeEnum], [CreateOptions As RecordCreateOptionsEnum = adFailIfNotExists], [Options As RecordOpenOptionsEnum = adOpenRecordUnspecified], [UserName As String], [Password As String])
При открытии задается источник данных, соединение, устанавливаются параметры открытия и при необходимости задаются имя пользователя и пароль доступа к данным. Заметьте, источники могут быть различных типов. Объект Record хотя и подобен набору записей, состоящему из одной записи, но имеет все-таки отличающийся набор свойств и методов. Этот объект часто используют при доставке данных из Интернета, поскольку его значениями могут быть файл или каталог. Некоторые Провайдеры поддерживают объекты Record и Stream как альтернативу объекту Recordset или как дополнительные объекты, позволяющие манипулировать данными, поступающими от Провайдеров. В роли Провайдера часто выступает Microsoft OLE DB Provider for Internet Publishing. Этот Провайдер позволяет использовать объект Record для управления данными, такими как каталоги и папки файловой системы, а также сообщениями в системе электронной почты. Источником данных в этом случае выступает абсолютный или относительный адрес URL, сочетаемый с объектом Connection.
Этот объект с успехом может использоваться при работе с данными, имеющими иерархическую структуру. С одной стороны его родителем может быть объект Recordset. С другой стороны, объект Recordset может быть его потомком, поскольку метод GetChildren объекта Record возвращает в качестве результата объект Recordset. Чуть позже я приведу пример такого взаимодействия этих двух объектов.
Большая часть свойств объекта Record совпадает со свойствами объекта Recordset. К таковым относятся свойства: ActiveConnection, Source, Fields, Properties, State. То, что эти два объекта имеют свойство Fields, возвращающее коллекцию полей записей, более всего роднит эти объекты. Еще одно свойство Mode, имеющее значениями константы из перечисления ConnectModeEnum, является общим с объектом Connection. Особых свойств у этого объекта два:
RecordType задает тип объекта, его значениями являются константы из перечисления RecordTypeEnum: adCollectionRecord, adSimpleRecord, adStructDoc, имена которых достаточно точно отражают смысл типа объекта.ParentURL определяет URL-адрес родителя объекта Record.Методов у объекта Record немного. О центральном методе Open, позволяющем создать объект я уже сказал. Кроме этого есть типичные для всех объектов методы Close и Cancel. Три метода CopyRecord, MoveRecord и DeleteRecord выполняют типичные операции над этим объектом. О специфическом методе GetChildren скажу чуть подробнее. Его синтаксис:
Function GetChildren() As Recordset
Записи возвращаемого объекта Recordset являются потомками текущего объекта Record. Например, потомками объекта Record, представляющего собой каталог, могут быть файлы и подкаталоги, содержащиеся внутри родительского каталога.
Рассмотрим теперь пример, в котором в роли Провайдера выступает Internet Publishing Provider, а участниками будут объекты Record, Recordset и Stream. Я поочередно создам два объекта Record, в первый раз для чтения текстового файла и передачи этих данных объекту Stream. Во втором случае объект Record будет представлять каталог, а созданный на его основе объект Recordset будет содержать имена файлов этого каталога. Вот процедура, решающая эту задачу:
Public Sub CreateRecord() 'Создание и работа с объектом Record Dim FirstRec As New Record Dim fld As Field Dim strText As String 'Create Connection with Internet Publishing Provider If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение Con1.Open "Provider = MSDAIPP.DSO; " _ "Data Source = http://serverva/proba2_2/testtext.txt" 'Объекту Record передается текстовый файл FirstRec.Open ActiveConnection:=Con1 'Поля объекта Record Debug.Print "Число полей:", FirstRec.Fields.Count For Each fld In FirstRec.Fields Debug.Print "Имя поля: ", fld.Name, _ "Значение поля:", fld.Value, "Тип:", fld.Type Next fld 'Создание объекта Stream на основе объекта Record If Strm1.State = adStateOpen Then Strm1.Close Strm1.Open Source:=FirstRec, Mode:=adModeRead, _ Options:=adOpenStreamFromRecord strText = Strm1.ReadText(100) Debug.Print "Текстовый файл:", strText Con1.Close 'Новое соединение и открытие объекта Record 'Объекту Record передается каталог Con1.Open "Provider = MSDAIPP.DSO; " _ "Data Source = http://serverva/" FirstRec.Open ActiveConnection:=Con1, _ Options:=adOpenSource, Mode:=adModeRead 'Поля объекта Record Debug.Print "Число полей:", FirstRec.Fields.Count For Each fld In FirstRec.Fields Debug.Print "Имя поля: ", fld.Name, _ "Значение поля:", fld.Value, "Тип:", fld.Type Next fld 'Создание объекта Recordset Set Rst1 = FirstRec.GetChildren Debug.Print Rst1.ActiveConnection Debug.Print Rst1.RecordCount Rst1.MoveFirst Do While Not Rst1.EOF 'Обработка текущей записи Debug.Print Rst1(2).Name, Rst1(2).Value Rst1.MoveNext Loop Con1.Close End Sub
Приведу комментарии к этой программе:
Proba2_2. Этот каталог установлен под Internet Information Server на моем компьютере. Соответствующие строки программного текста подсвечены.FirstRec класса Record методом Open, которому в качестве параметра передается имя открытого соединения. В этот момент данные из текстового файла передаются объекту FirstRec.Fields. Полей у этого объекта в данном контексте достаточно много - 26. Приведу имена и значения пяти полей:RESOURCE_PARSENAME - testtext.txt RESOURCE_PARENTNAME - http://serverva/proba2_2 RESOURCE_ABSOLUTEPARSENAME - http://serverva/proba2_2/testtext.txt RESOURCE_ISHIDDEN - False RESOURCE_CONTENTCLASS - text/plainВ данном контексте большая часть полей, но не все, определяет характеристики ресурса.
FirstRec задает теперь текстовый файл, то он может в свою очередь послужить источником данных для объекта Stream, который создается, как обычно, методом Open.ReadText глобального объекта Strm1 класса Stream позволяет прочесть нужную порцию символов в обычную строку и распечатать ее, что позволяет убедиться в правильности передачи данных.FirstRec закрывается и заново открывается, но теперь у него другой источник данных и его значением становится каталог.RESOURCE_PARSENAME - RESOURCE_PARENTNAME - http://serverva RESOURCE_ABSOLUTEPARSENAME - http://serverva RESOURCE_ISHIDDEN - RESOURCE_CONTENTCLASS -В сравнении с предыдущим случаем не для всех полей определены значения, в частности не задан класс контента.
Recordset, записи которого будут содержать сведения о подкаталогах и файлах, хранимых в каталоге. Для создания объекта Recordset используется метод GetChildren, вызываемый объектом FirstRec.RESOURCE_ABSOLUTEPARSENAME http://serverva/_private RESOURCE_ABSOLUTEPARSENAME http://serverva/images RESOURCE_ABSOLUTEPARSENAME http://serverva/win2000.gif RESOURCE_ABSOLUTEPARSENAME http://serverva/web.gif RESOURCE_ABSOLUTEPARSENAME http://serverva/warning.gif
На этом я закончу описание примера, в котором успешно взаимодействовали объекты Record, Stream и Recordset. Знакомство с объектом Stream уже состоялось, но давайте, хотя бы вкратце познакомимся с его свойствами и методами.
Этот объект представляет двоичный поток данных или текст. Методы и свойства этого объекта позволяют выполнять операции над этим потоком, позволяя читать, писать, копировать и сохранять данные потока. Объекты Stream обычно используются при работе с данными, сохраненными в XML-формате, при работе с текстовыми файлами и сообщениями электронной почты, во всех случаях, когда данные рассматриваются как поток байтов. Объект Stream может быть получен тремя способами:
Record, представляющим структурированный документ, или папкой.Record.Объект Stream имеет следующие свойства:
Property Charset As String. Строка, задающая множество символов, в которое транслируется данные потока. Свойство имеет смысл только для текстовых файлов, которые по умолчанию должны быть представлены в кодировке Unicode, -значением свойства по умолчанию.Property EOS As Boolean. Аналог свойства EOF для файлов, позволяет проследить за окончанием потока при последовательном чтении его байтов. Принимает значение True, когда свойство Position определяет позицию, лежащую за последним символом потока.Property LineSeparator As LineSeparatorEnum. Константы перечисления задают символы, задающие конец строки в текстовом файле. Возможные значения: adCR, adCRLF, adLF.Property Mode As ConnectModeEnum. Свойство, задающее статус объекта. Им обладают большинство объектов ADO.Property Position As Long. Определяет текущую позицию в потоке. Поскольку над потоком определены операции чтения и записи, то понятно, что должна быть определена текущая позиция (курсор), отмечающая начальную точку при выполнении этих операций.Property Size As Long. Задает размер потока. И позиция и размер потока измеряются в байтах.Property State As ObjectStateEnum. Свойство, задающее состояние объекта. Им обладают большинство объектов ADO.Property Type As StreamTypeEnum. Тип потока задается константой из перечисления. Имеет два возможных значения: adTypeBinary, adTypeText.Рассмотрим теперь методы объекта Stream:
Sub Cancel(). Как и для других объектов ADO позволяет прервать выполнение асинхронных операций над объектом Stream.Sub Close(). Закрывает объект Stream.Sub CopyTo(DestStream As Stream, [CharNumber As Long = -1]). Позволяет создать копию SetEOS для потока назначения.Sub Flush(). Принуждает оставаться содержимому потока в ADO буфере того базового объекта, с которым ассоциирован объект Stream.Sub LoadFromFile(FileName As String), Sub SaveToFile(FileName As String, [Options As SaveOptionsEnum = adSaveCreateNotExist]). Эти методы позволяют загрузить содержимое файла в поток и сохранить поток в файле.Sub Open([Source], [Mode As ConnectModeEnum = adModeUnknown], [Options As StreamOpenOptionsEnum = adOpenStreamUnspecified], [UserName As String], [Password As String]). Один из основных методов, создающий поток. Первый параметр задает источник данных. Это может быть абсолютный URL-адрес, в этом случае параметр имеет следующий синтаксис "URL = scheme://server/folder". Адрес может задавать узел в структуре дерева каталогов файловой системы или системе электронной почты. Источник может быть ссылкой на открытый объект Record. Объект Stream ассоциируется с потоком по умолчанию этого объекта. В наших примерах использовался именно такой способ создания объекта Stream. Источник может и не указываться, тогда создается объект Stream, не ассоциированный с потоком другого объекта. Данные в него могут поступать, например, при загрузке из файла при вызове метода LoadFropmFile или при создании копии потока.Function Read([NumBytes As Long = -1]), Function ReadText([NumChars As Long = -1]) As String, Sub Write(Buffer), Sub WriteText(Data As String, [Options As StreamWriteEnum = adWriteChar]). Эта группа методов позволяет читать и писать в поток заданное число байтов (символов). Методы Read и Write, работающие с байтами, используют переменную типа Variant, а методы ReadText и WriteText используют переменную типа String. Заметьте, речь идет о потоке, поэтому все операции по чтению и записи идут только в одном направлении - от начала к концу потока. Если требуется несколько проходов, то следует заново переоткрывать поток.Sub SetEOS(). Метод устанавливает значение свойства EOS в True, а, главное, делает текущую позицию концом потока, отсекая все оставшиеся символы. Поскольку методы Write, WriteText, CopyTo не производят отсечения, то, чаще всего, они используются в комбинации с методом SetEOS.Sub SkipLine(). Позволяет пропустить одну строку при чтении текстового файла. Используется в цикле, когда нужно пропустить несколько строк.Свойство Fields объектов Recordset и Record возвращает одноименную коллекцию, элементами которой являются объекты Field, представляющие содержательно поля записи или столбцы данных, пользуясь табличной терминологией. Об этих объектах уже было сказано немало, так что я постараюсь долго на них не останавливаться.
У коллекции Fields всего два традиционных для всех Count и Item, а вот методов гораздо больше. Наряду с методами, о которых я уже говорил при рассмотрении других объектов ADO, такими как Delete, Refresh, Update, CancelUpdate, имеется два специфических метода:
Sub Append(Name As String, Type As DataTypeEnum, [DefinedSize As Long], [Attrib As FieldAttributeEnum = adFldUnspecified], [FieldValue ]). Метод позволяет создать новое поле и присоединить его к коллекции. При определении поля задается необходимая информация - имя поля, его тип, определяемый размер данных, атрибуты и, возможно, значение, если речь идет об объекте Record. При работе с полями объекта Recordset они должны быть созданы при закрытом объекте Recordset, после этого объект можно открыть и присвоить значения созданным полям. Присоединять поля к объекту Recordset можно только в том случае, если он закрыт и для него не установлено свойство ActiveConnection. Поскольку в реальных ситуациях эти объекты появляются при выполнении метода Open или Execute, когда соединение с источником данных установлено, то присоединять новые поля к объекту Recordset таким способом не удается. Для изменения структуры базы данных используются обычно объекты ADOX, которые будут рассмотрены ниже. Так что, чаще всего, метод Append применяется при работе с полями объекта Record. Добавлять поле к этому объекту можно и тогда, когда он открыт, при добавлении можно указать и значение поля. После добавления поля следует вызвать метод Update, чтобы сохранить сделанные изменения.Sub Resync([ResyncValues As ResyncEnum = adResyncAllValues]). Позволяет обновить значения всех полей у записей, хранящихся в буфере.Объект Field задает поле записи. С помощью его свойств можно определить характеристики поля - тип, значение, формат и установить эти значения при формировании новых полей, присоединяемых к записи. Рассмотрим все по порядку:
Property Name As String. Задает имя поля.Property ActualSize As Long, Property DefinedSize As Long. Первое свойство позволяет определить фактический размер хранимого в поле значения. Оно имеет статус только для чтения. В случае, если ADO не может определить по каким либо причинам истинный размер значения, в качестве результата возвращается константа adUnknown. Второе свойство задает метаданные - размер поля, заданный в момент его определения. Его статус - "чтение/запись". Размер всегда задается в байтах.Property Precision As Byte. Для числовых полей указывает максимальное число цифр, используемых для представления значения.Property NumericScale As Byte. Для числовых полей указывает число цифр после запятой, используемых для представления значения.Property Type As DataTypeEnum. Задает тип поля. Приведу значения некоторых констант из перечисления: adInteger = 3, adVarWChar = 202 (для строковых полей), adCurrency = 6, adLongVarWChar = 203 (для поля Memo).Property DataFormat As Unknown. Это свойство, которое, судя по названию, должно задавать формат данных, отсутствует в версии ADO 2.6. Хотя оно присутствует в предыдущей версии 2.5, но возвращает в качестве результата ссылку на пустой объект, так что не стоит пытаться его использовать.Property Status As Long. Возвращает статус поля, позволяющий, например, определить, было ли поле нормально добавлено или удалено из записи. Значения свойства задаются константами из перечисления FieldStatusEnum. Вот некоторые значения констант: adFieldAlreadyExists, adFieldOK, adFieldPendingInsert, adFieldPendingDelete, adFieldCantCreate. Анализ статуса поля, позволяет определить, что произошло при выполнении операций над тем или иным полем.Property OriginalValue As Variant, Property UnderlyingValue As Variant, Property Value As Variant. Все эти свойства задают значение поля в разных его ипостасях - значение поля в записи до его изменений, значение поля в базе данных, текущее значение поля в записи.Property Attributes As Long. Свойство задает маску, определяющую характеристики поля. Значение представляет сумму констант из перечисления FieldAttributeEnum. Вот лишь некоторые значения этих констант: adFldKeyColumn, adFldLong, adFldIsChapter, adFldUpdatable.Property Properties As Properties. Возвращает У этого объекта имеется всего два метода:
Sub AppendChunk(Data). Этот метод позволяет формировать "длинные" значения полей, присоединяя очередную порцию данных к текстовому или двоичному значению поля. При первом вызове метода значение, заданное аргументом Data переписывается в поле и становится его значением, при последующих вызовах значение аргумента Data добавляется к значению, хранимому в поле. Метод не применим к полям объекта Record. Аргумент Data задается переменной типа Variant и содержит добавляемые данные. Заметьте, для того чтобы можно было использовать этот метод бит adFldLong в свойстве Attributes должен быть установлен - иметь значение True.Function GetChunk(Length As Long). Метод позволяет получить все значение или нужную порцию большого текста или двоичного кода, хранящегося в поле. Длина порции устанавливается параметром Length. Возвращаемый результат является переменной типа Variant.Приведу теперь пример, в котором анализируются поля объекта Recordset. По изложенным выше причинам я не стал пытаться вводить новые поля и ограничился получением информации о свойствах уже существующих полей записи. Вот код соответствующей процедуры:
Public Sub CreateField()
'Процедура печатает информацию о существующих полях записи
Dim fld As Field
Dim df As Object
'Создать соединение
CreateConnection
'Создать команду
'задание свойств объекта Command
Cmd1.ActiveConnection = Con1
Cmd1.CommandText = "Select * From [Книги]"
Cmd1.CommandType = adCmdText
'Открытие обновляемого объекта Recordset
With Rst1
.Open Source:=Cmd1, CursorType:=adOpenDynamic, _
LockType:=adLockOptimistic
.MoveFirst
Do While Not .EOF
'Печать данных о полях записи набора
If !Название = "Офисное программирование" Then
For Each fld In .Fields
With fld
Debug.Print "Имя поля ", .Name
Debug.Print "Фактический размер поля =", .ActualSize
Debug.Print "Определяемый размер поля =", .DefinedSize
Debug.Print "Точность", .Precision
Debug.Print "Число цифр после запятой в числовых полях -", _
.NumericScale
Debug.Print "Тип поля", .Type
Set df = .DataFormat
If df Is Nothing Then
Debug.Print "объект - Формат данных -не определен "
End If
Debug.Print "Статус", .Status
Debug.Print "Значение поля до его изменений ", .OriginalValue
Debug.Print "Текущее значение в базе данных", .UnderlyingValue
Debug.Print "Текущее значение поля в наборе", .Value
Debug.Print "Атрибуты =", .Attributes
Debug.Print "Число свойств =", .Properties.Count
Debug.Print "Имя и значение первого свойства - ", _
.Properties(1).Name, .Properties(1).Value
End With
Next fld
End If
.MoveNext
Loop
'попытка изменить характеристики полей
'закрываем объект Recordset
.Close
Set fld = .Fields("Год издания")
With fld
.DefinedSize = 8
.Type = adDecimal
End With
'.Fields.Update
End With
End Sub
Приведу краткий комментарий:
Data Format возвращает ссылку на пустой объект.Recordset изменение значений этих характеристик возможно, но метод Fields.Update в данном контексте не работает, по этой причине он закомментирован.Подведем теперь некоторые итоги. Вот что можно делать с объектом Field:
Name позволяет вернуть имя поля, но не позволяет изменить это имя.Value позволяет не только получить значение поля, но и изменить его.Type, Precision, NumericScale, DefinedSize позволяют получить, но не изменить метаданные, задающие соответствующие характеристики поля.ActualSize позволяет определить фактический размер хранимого в поле значения.Attributes и Properties.AppendChunk и GetChunk.Recordset, что может быть полезно при динамическом конструировании форм.Объект Command может определять запрос или хранимую процедуру с параметрами. А посему этот объект имеет свойство Parameters, возвращающее одноименную коллекцию, элементами которой являются объекты Parameter. Содержательно, каждый из этих объектов определяет параметр - входной или выходной, который передается или возвращается при вызове хранимой процедуры. Я уже приводил пример вызова запроса с параметром, где появлялись эти объекты.
Коллекция Parameters устроена достаточно просто. У нее всего два типичных свойства - Item и Count. У коллекции три метода, которые также появлялись в нашем рассмотрении. Вот эти методы:
Sub Append(Object As Object). Метод позволяет присоединить объект Parameter, заданный аргументом, к коллекции. Обычно присоединяемый объект создается методом CreateParameter объекта Command. В процедуре CreateCommands, приведенной в предыдущей главе, продемонстрировано применение методов CreateParameter и Append.Sub Delete(Index). Метод позволяет удалить из коллекции параметр, по имени или порядковому номеру, указываемому в аргументе Index.Sub Refresh(). Позволяет обновить содержимое коллекции, получая информацию о параметрах из хранимой процедуры.Поскольку все свойства и методы объекта Parameter так или иначе уже появлялись в нашем описании, то я их только перечислю, не приводя подробного описания. Вот список свойств этого объекта: Attributes, Direction, Name, NumericScale, Precision, Properties, Size, Type, Value. Метод у этого объекта всего один - AppendChunk, - и он тоже был описан при рассмотрении объекта Field.
Используя свойства и методы коллекции и самого объекта Parameter можно делать следующее:
Name позволяет устанавливать и возвращать имя параметра, а свойство Value - его значение.Attributes, Direction, Precision, NumericScale, Size и Type позволяют устанавливать и возвращать различные характеристики параметра.Direction позволяет указать, является ли параметр входным, выходным, входным-выходным или представляет возвращаемое процедурой значение. Поскольку не все Провайдеры могут установить направление передаваемых параметров, то в таких ситуациях необходимо программно устанавливать значение этого свойства перед тем, как вызывать хранимую процедуру или параметризованный запрос.AppendChunk позволяет передавать параметру порции двоичных или символьных данных в случае длинных значений параметра.Properties.CreateParameter и присоединить его к коллекции, используя ее метод Append. Это позволяет избежать лишних вызовов метода Refresh для получения нужной информации от Провайдера и тем самым снизить нагрузку на передачу данных.Многие из объектов ADO имеют свойство Properties, возвращающее одноименную коллекцию элементов Property. Поскольку каждый объект обладает набором свойств, то возникает естественный вопрос, зачем необходимо еще и свойство Properties, которое тоже задает свойства. Дело в том, что свойства разделяются на встроенные и динамические. Properties. Каждый объект этой коллекции описывает то или иное динамическое свойство объекта ADO, зависящее от специфики Провайдера и доступное, естественно, только тогда, когда объект открыт. Добавлять или удалять элементы этой коллекции невозможно, - это прерогатива Провайдера.
С объектной точки зрения коллекция Properties устроена очень просто - у нее всего два типичных свойства: Item, Count и один метод Refresh. Также просто устроен и объект Property - у него всего четыре свойства: Name, Type, Value, Attributes. Они задают имя, тип, значение и некоторые дополнительные характеристики. Примеры работы с этими объектами уже приводились.
На этом я завершаю утомительное описание объектов ADO. Но нам предстоит еще хотя бы вкратце познакомиться с объектами ADOX.
Клиентское приложение, использующее объекты ADO, получает необходимые ему данные, обращаясь к Провайдеру. Провайдер или Поставщик данных - это приложение, обеспечивающее интерфейс между конкретным источником данных и клиентским приложением. Провайдер способен корректно транслировать вызовы методов ADO в запросы к источнику данных, выполнять эти запросы и передавать результаты клиентскому приложению. Несмотря на многообразие источников данных, многие из них имеют единый интерфейс запросов, что позволяет иметь одного Провайдера на все источники данных, экспонирующие единый интерфейс запросов. Конечно же, для уникальных источников данных может понадобиться разработка собственного Провайдера. Но для наиболее известных источников данных Провайдеры уже разработаны как Microsoft, так и другими фирмами, поддерживающими работу с источниками данных. Рассмотрим особенности ряда стандартных Провайдеров.
Полное имя этого Провайдера - Microsoft OLE DB Provider for ODBC. Он является Провайдером по умолчанию для объектов ADO, так что если не задать аргумент Provider в строке соединения, то связь по умолчанию будет осуществляться с этим Провайдером. Этот Поставщик данных позволяет связаться со всеми СУБД с интерфейсом ODBC. Все СУБД, поставляемые Microsoft - Microsoft SQL Server, Microsoft Access (Microsoft Jet database engine), Microsoft FoxPro - обладают этим интерфейсом. Но и СУБД других фирм, например Oracle, обладают, как правило, этим интерфейсом, так что Провайдер ODBC реально позволяет связаться с любой профессиональной базой данных.
Провайдер ODBC, являясь Провайдером по умолчанию, поддерживает все зависящие от Провайдера свойства и методы объектов ADO. Он поддерживает транзакции, в том числе и гнездованные транзакции. Однако различные СУБД могут обеспечивать различный уровень поддержки транзакций, например, Microsoft Access поддерживает гнездованные транзакции на глубину не более пяти уровней.
Для этого Провайдера аргумент Provider свойства ConnectionString следует установить как MSDASQL. Типичная строка соединения имеет вид:
"Provider = MSDASQL; DSN = dsnName; UID = userName; PWD = userPassword;"
Аргумент DSN (Data Source Name), задает имя источника данных. Это имя должно быть зарегистрировано в Администраторе источников данных ODBC, добраться до которого можно из панели управления. Вот как выглядит окно Администратора, в котором я установил DSN для тестовой базы данных.
(рис 6.5) Установка DSN в окне Администратора ODBCПриведу пример работы с этой базой данных:
Public Sub CreateODBCConnect()
'Создание соединения с тестовой базой данных Access
Dim strConnStr As String
Dim Start As Single, Finish As Single
If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение
'Вариант1: Провайдер ODBC
Con1.Provider = "MSDASQL"
strConnStr = "DSN=dbTestingADO; DATABASE =c:\dbPP2000.mdb;"
'Вариант2: Провайдер Microsoft Jet
'Con1.Provider = "Microsoft.jet.oledb.4.0"
'strConnStr = "Data Source=c:\dbPP2000.mdb;"
Start = Timer
Con1.Open strConnStr
'Создать команду
'задание свойств объекта Command
Cmd1.ActiveConnection = Con1
Cmd1.CommandText = "Select * From [Книги]"
Cmd1.CommandType = adCmdText
'Открытие обновляемого объекта Recordset
With Rst1
.Open Source:=Cmd1, CursorType:=adOpenDynamic, _
LockType:=adLockOptimistic
recExist = False
.MoveFirst
Do While Not .EOF
'Проверка существования записи
If !Название = "Война и мир" Then recExist = True
.MoveNext
Loop
If Not recExist Then
.AddNew Array("Автор", "Название", "Год издания", _
"Число страниц", "Цена"), _
Array("Лев Толстой", "Война и мир", 2001, 799, 220)
End If
End With
Con1.Close
Finish = Timer
Debug.Print "Время работы с таблицей:", Finish - Start
End Sub
Хочу обратить внимание на два момента:
Время работы с таблицей: 5,878906 Время работы с таблицей: 0,0390625
Заметьте, время работы с ODBC Провайдером для базы данных Access в данном случае существенно больше, - на два порядка, чем время работы с "родным" Провайдером.
DATABASE, указывающий путь к базе данных. Эта информация является дублирующей, поскольку реальный путь к базе данных задается в момент определения DSN. Тем не менее, это полезно делать, чтобы явно указать, с какой базой данных пользователь намеревается работать.Провайдер ODBC добавляет в коллекцию Properties объектов Connection, Command, Recordset ряд свойств, часть из которых является специфической для данного Провайдера. Некоторые из этих свойств добавляются к не открытым объектам, другие - при открытии объектов. Для всех этих свойств, доступных в ADO, есть аналог в модели OLE DB. Перечисление всех этих свойств заняло бы слишком много места. Чтобы дать некоторое представление, укажу несколько свойств объекта Connection:
Accesible Tables - булево свойство, указывающее, имеет ли пользователь разрешение на выполнение запросов (SQL-операторов) над таблицами базы данных.File Usage - указывает, как драйвер воспринимает источник данных, - как файл или как каталог.Special Characters - указывает, какие символы имеют специальный смысл для ODBC драйвера.Stored Procedures - определяет доступность использования хранимых процедур.Этот Провайдер позволяет получить доступ к Microsoft Jet базам данных, а, следовательно, к базам данных, созданным в приложении Access. В большинстве приведенных примерах использовался именно этот Провайдер. Типичная строка соединения имеет вид:
"Provider=Microsoft.Jet.OLEDB.4.0; Data Source=databaseName; User ID =userName; Password=userPassword;"
В строке соединения Провайдеру могут быть переданы и специфические свойства, например, параметр Jet OLEDB: Database Password позволяет задать пароль к базе данных. В одном из последующих примеров я использую этот способ, чтобы указать имя системной базы данных. Другой способ передачи специфических для Провайдера параметров - через коллекцию Propertieis объекта Connection. Заметьте, большую часть этой коллекции для объектов Connection, Command и Recordset Провайдер формирует динамически.
Полное имя этого Провайдера - Microsoft OLE DB Provider for Internet Publishing. Он позволяет получить доступ к ресурсам Web-серверов, работающих под управлением Internet Information Server. Типичная строка соединения имеет вид:
"Provider= MSDAIPP.DSO; Data Source=ResourceURL; User ID =userName; Password=userPassword;"
или
"URL =ResourceURL; User ID =userName; Password=userPassword;"
Параметр Data Source | URL задает источник данных - URL файла или каталога, расположенного в Web-парке на сервере. Заметьте, во втором случае, когда используется имя URL для второго параметра, первый параметр, определяющий Провайдера, задавать не следует. Ошибка возникнет и в том случае, если предварительно будет задано свойство Provider.
Примеры использования этого Провайдера уже были приведены.
Полное имя этого Провайдера - Microsoft OLE DB Provider for SQL Server. Он позволяет получить доступ к данным Microsoft SQL Server. Типичная строка соединения имеет вид:
"Provider= SQLOLEDB; Data Source=ServerName; Initial Catalog =databaseName; User ID =userName; Password=userPassword;"
Параметр Data Source или Server задает имя сервера. Параметр Initial Catalog или Database - имя базы данных на сервере.
В строке соединения Провайдеру могут быть переданы и специфические свойства, например, параметр Current Language позволяет задать язык, используемый для системных сообщений. Язык должен быть инсталлирован на SQL Server, иначе возникнет ошибка в момент открытия соединения. Другой способ передачи специфических для Провайдера параметров - через коллекцию Propertieis объекта Connection. Большую часть этой коллекции для объектов Connection, Command и Recordset Провайдер формирует динамически.
Полное имя этого Провайдера - Microsoft OLE DB Provider for Oracle. Он позволяет получить доступ к базе данных Oracle. Типичная строка соединения имеет вид:
"Provider= MSDAORA; Data Source=ServerName; User ID =userName; Password=userPassword;"
Параметр Data Source задает имя сервера. В строке соединения Провайдеру могут быть переданы и специфические свойства, например, параметр OLE DB Services позволяет задать маску, каждый бит которой позволяет включить или выключить соответствующую OLE DB службу. Другой способ передачи специфических для Провайдера параметров - через коллекцию Propertieis объекта Connection. Большую часть этой коллекции для объектов Connection, Command и Recordset Провайдер формирует динамически.
Этот Провайдер поддерживает работу только со
Краткие данные о других Провайдерах приведу в таблице.
| Название Провайдера | Строка соединения | Назначение |
|---|---|---|
| >Microsoft OLE DB Remoting Provider | "Provider=MS Remote" |
Позволяет удаленному пользователю подключиться к источнику данных так, как если бы он был локальным пользователем. |
| Microsoft OLE DB Simple Provider (OSP) | "Provider=MSDAOSP;Data Source=serverName" |
Позволяет ADO получить доступ к любым данным, для которых Провайдер написан с использованием специального инструментария - OLE DB Simple Provider Toolkit. Такие источники данных требуют лишь базисных средств поддержки OLE DB. Типичным примером данных являются - XML документы. |
| Microsoft OLE DB Persistence Provider | "Provider=MSPersist" |
Этот Провайдер позволяет сохранить в файле данные и всю необходимую информацию об объекте Recordset, в том числе и иерархическом. Затем, при необходимости, объект Recordset может быть восстановлен. |
| Microsoft Data Shaping Service for OLE DB | "Provider=MSDataShape" |
Поддерживает конструирование иерархических объектов Recordset. |
| Microsoft OLE DB Provider for Microsoft Indexing Service | "Provider=MSIDXS;Data Source=myCatalog;Locale Identifier=nnnn;"Типично указывается только первый параметр. Второй параметр задает имя каталога специальной поисковой службы - Indexing Service. Если параметр опущен, то по умолчанию используется системный каталог. Параметр |
Поддерживает программный доступ со статусом "только для чтения" к файловой системе и Web-страницам, индексируемых службой Microsoft Indexing Service. |
| Microsoft Active Directory Service Interfaces (ADSI) Provider | "Provider=ADSDSOObject;User ID=userName;Password=userPassword;" |
to connect to Позволяет связаться с гетерогенными службами каталогов - Windows NT 4.0, Windows 2000, Novell Directory Services, LDAP Directory Services. |
| Microsoft Cursor Service for OLE DB | Строки соединения нет, поскольку это специальная служба, вызываемая автоматически в зависимости от расположения курсора. | Эта специальная служба обеспечивает функции поддержки курсора для различных Провайдеров данных, что обеспечивает единую функциональность в работе ADO. Вызывается служба автоматически, если соответствующим образом задать положение курсора объектов Connection или Recordset:connection.CursorLocation=adUseClient recordset.CursorLocation=adUseClient |
До появления объектов ADO при работе с базой данных Access использовались объекты
В объектной модели Workspaces, каждый элемент которой - объект Workspace - задает сеанс работы, являясь неким эквивалентом объекта Session в модели OLE DB. В модели ADO сеанс работы связывается с объектом Connection, задающим соединение с базой данных. Заметьте, модель ADO во многом упрощена в сравнении с моделью Database, являющегося одним из основных объектов в модели
Конечно, главное достижение в модели ADO связано с возможностью работы с разнообразными источниками данных и введением такого понятия как Провайдер. В модели WorkSpace, задающий рабочее пространство, вкладываются две достаточно разные совокупности объектов. Я ограничусь рассмотрением лишь одной половинки этой модели и рассмотрю модель
(рис 6.6) Объектная модель DAO для рабочей области Microsoft JetОбратите внимание, чтобы не усложнять схему, на нижних уровнях иерархии ряд объектов опущен. Вот как следует дополнить эту схему:
Container содержит коллекцию Documents с элементами Document,QueryDef содержит коллекцию Parameters с элементами Parameter,OueryDef, Recordset, Relation, TableDef содержат коллекцию Fields c элементами Field.Я уже говорил, что объектная модель
Connection позволяет связаться с базой данных и заменяет, в определенной степени, объекты Workspace и Database. Объект Command, позволяющий выполнять SQL-операторы, заменяет объект QueryDef, который выполняет эти действия в модели Recordset присутствует в обеих моделях, выполняя, в общем, аналогичные действия.QueryDef, TableDef используются для этих целей, создавая или модифицируя стандартные запросы и таблицы базы данных. Объекты User, Group и их коллекции, присутствующие в моделях Replica , задающий репликацию базы данных. Свойства и методы этого объекта используются для создания новой репликации, получения и модификации свойств уже существующих реплик и синхронизации реплик. В модели Database. Еще один объект JetEngine из модели JRO позволяет выполнять операции по сжатию базы данных и обновлению данных, хранимых в кэш-памяти.Я не буду останавливаться на деталях сходства и различия в моделях ADO и
Workspace. Но поскольку в этот объект вложена коллекция объектов Databases, то в транзакции участвуют все открытые базы данных, запоминаются все изменения и при откате восстанавливается состояние всех баз данных. В модели ADO каждая транзакция связана только со своей базой данных, открытой заданным соединением.Объекты этой группы, как уже было сказано, играют важную роль, позволяя работать с метаданными баз данных. С их помощью можно программно создать новую базу данных, создать или модифицировать элементы существующей базы данных, создать новые или модифицировать существующие таблицы, запросы, хранимые процедуры. Кроме того, эти объекты предназначены для работы с пользователями, группами пользователей, задания их прав доступа к данным, обеспечивая, тем самым, нужный уровень безопасности.
Рассмотрим объектную модель ADOX и начнем со схемы, отражающей взаимосвязи между объектами модели:
(рис 6.7) Объектная модель ADOXНа схеме не отражен тот факт, что объекты Table, Index, Column имеют стандартную коллекцию Properties с элементами Property.
Заметьте, в этой модели есть центральный объект Catalog, в который и встроены все основные коллекции: Tables, Procedures, Views, Groups, Users. Эти коллекции определяют различные метаэлементы базы данных. Давайте подробно рассмотрим свойства и методы объектов модели.
Центральный объект модели ADOX, задает каталог базы данных. Его метод Create позволяет создать новый источник данных, а свойства позволяют добраться до элементов, определяющих схему базы данных. Не все Провайдеры поддерживают полный набор возможностей этого объекта, определяемых его свойствами и методами. Так что источник данных и его Провайдер определяют, допустимо ли для него использование того или иного свойства или метода объектов ADOX.
У объекта Catalog 6 свойств: ActiveConnection, Tables, Procedures, Views, Groups, Users. Пять последних свойств возвращают одноименные коллекции. А о первом свойстве поговорим чуть подробнее.
Свойство имеет статус "чтение/запись". Синтаксически определяется следующим образом:
Property ActiveConnection As Variant
Позволяет установить ссылку на объект Connection или задать строку, определяющую соединение. Возвращает ссылку на активный объект Connection.
С объектной точки зрения все эти коллекции устроены одинаково. Они хранят соответствующие элементы классов: Table, Procedure, View, Group, User. У них есть три метода и два свойства. Для добавления нового элемента в коллекцию применяется метод Append(item), удаления - метод Delete(item), для обновления состояния коллекции, учитывающего текущее состояние базы данных - метод Refresh(). Свойство Item позволяет добраться до нужного элемента коллекции, а свойство Count возвращает число элементов в коллекции.
Некоторая особенность связана с коллекциями Groups и Users. Коллекция Groups объекта Catalog задает все группы базы данных. С другой стороны, как показано на схеме, отражающей связи между объектами (Рис. 7), коллекция Groups связана и с отдельным объектом User. Коллекция групп, связанная с отдельным пользователем, возвращает все группы, в которые входит этот пользователь. Заметьте, прежде чем добавить новую группу с именем name в коллекцию Groups объекта User, элемент с таким именем уже должен находиться в коллекции Groups объекта Catalog. Совершенно симметрично дело обстоит и с коллекцией Users, которая в объекте Catalog описывает всех пользователей, а в объекте Group - пользователей данной группы. Аналогично, прежде чем добавить пользователя в отдельную группу, он должен быть добавлен в коллекцию Users объекта Catalog
.
У этого объекта три метода:
Function Create(ConnectString As String). Позволяет создать новую базу данных. Параметр ConnectString задает строку соединения. Если создание базы данных прошло успешно, то в качестве результата возвращается объект Connection из библиотеки ADODB, задающий соединение с базой. Этот же объект становится значением свойства ActiveConnection объекта Catalog. Естественно, при выполнении метода возникнет ошибка, если Провайдер не поддерживает создание нового источника данных.Function GetObjectOwner(ObjectName As String, ObjectType As ObjectTypeEnum, [ObjectTypeId]) As String.Sub SetObjectOwner(ObjectName As String, ObjectType As ObjectTypeEnum, UserName As String, [ObjectTypeId]).Эти два метода позволяют определить и, соответственно, установить собственника объекта. Параметр UserName метода Set задает имя собственника, которое возвращает метод (функция) Get. Параметры ObjectName и ObjectType задают имя и тип объекта, для которого устанавливается собственник. Наличие этих параметров связано с тем, что методы применимы не только к объекту Catalog. Тип объекта задается константами из перечисления ObjectTypeEnum. Вот их значения: adPermObjColumn, adPermObjDatabase, adPermObjProcedure, adPermObjTable, adPermObjView. Еще одно возможное значение - adPermObjProviderSpecific указывается тогда, когда объект не принадлежит ни к одному из указанных типов и зависит от Провайдера, в этой ситуации тип задается последним параметром ObjectTypeId.
Чтобы оживить изложение, давайте перейдем к рассмотрению примеров. Для экспериментов с объектами ADOX я создал новый модуль. Глобальные переменные этого модуля будут определять уже знакомые объекты - Connection, Command, Recordset. Но к ним теперь добавится и новый объект, задающий Catalog.
Option Explicit 'Модуль TestingADOX 'Глобальные переменные Public Con1 As New ADODB.Connection Public Cmd1 As New ADODB.Command Public Rst1 As New ADODB.Recordset Public Strm1 As New ADODB.Stream Public Cat1 As New ADOX.Catalog
Вот как выглядит первая процедура этого модуля, создающая новую базу данных:
Public Sub CreateNewDB() 'Создание новой базы данных и открытие соединения с ней Dim strConnStr As String strConnStr = "Provider=Microsoft.jet.oledb.4.0;" _ "Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb" Set Con1 = Cat1.Create(strConnStr) Debug.Print Cat1.ActiveConnection Dim Con2 As Connection Set Con2 = Cat1.ActiveConnection Debug.Print Con2.ConnectionString End Sub
В этой процедуре я создал новую базу данных Access и установил с ней соединение. Заметьте, я ввел два объекта класса Connection, - один из них определяется при вызове метода Create объекта Cat1 класса Catalog, а второй определяется, используя значение свойства ActiveConnection.
Provider=Microsoft.Jet.OLEDB.4.0;Password="";User ID=Admin;Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database="";Jet OLEDB:Registry Path="";Jet OLEDB:Database Password="";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password="";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False
В ней отражены все свойства объекта Connection.
Этот объект определяет таблицу базы данных. Является элементом коллекции Tables, вложенной в объект Catalog. Создается конструктором New и добавляется в коллекцию Tables методом Append. Вот типичная схема программного создания новой таблицы:
Dim NewTable As New ADOX.Table 'Формирование объекта Table … Call Cat1.Tables.Append(NewTable)
Чуть позже я приведу полный пример программного создания объекта Table, а пока займемся рассмотрением его свойств, методов и событий. Задача облегчается тем, что у объекта Table нет методов и событий, - есть только свойства, рассмотрением которых сейчас и займемся.
Свойства объекта позволяют определить новую таблицу или модифицировать уже существующую. Свойств у объекта сравнительно немного. Вот они:
Columns, Keys и Indexes возвращают одноименные коллекции, которые и задают основные элементы таблицы - ее поля, ключи и индексы. Все три коллекции имеют одинаковый и уже описанный набор из трех методов: Append, Delete, Refresh и двух свойств - Item и Count. Создаются элементы коллекций конструктором New, а добавляются в коллекцию методом Append, в полном соответствии с выше приведенной схемой для объекта Table.Property DateCreated As Variant, Property DateModified As Variant. Свойства имеют статус только для чтения и возвращают дату создания и дату последней модификации таблицы.Property Name As String. Задает имя таблицы.Property ParentCatalog As Catalog. Задает родительский каталог, - объект, метод Create которого создал базу данных, которой и принадлежит таблица.Property Properties As Properties. Возвращает Property Type As String. Свойство задает тип, но не, как обычно, константой, а строкой, значения которой могут быть, например, "TABLE", "SYSTEM TABLE " или "GLOBAL TEMPORARY".Таким образом, для создания таблицы или ее модификации приходится, главным образом, работать с объектами Column, Key, Index и их коллекциями. Рассмотрим устройство этих объектов.
Коллекция Columns с элементами Column возвращается при вызове одноименного свойства объекта Table, в этом случае она задает поля таблицы. Но эта коллекция появляется и в других случаях. Напомню, ключи и индексы таблицы могут быть составными и состоять из нескольких полей, по этой причине объекты Key и Index также обладают свойством Columns, которое возвращает коллекции полей, задающих составной ключ или составной индекс. Так что объект Column может задавать поле таблицы, ключа или индекса. Этот объект является центральным объектом в мире, связанном с таблицами. Устроен он достаточно просто, у него нет ни методов, ни событий, - есть только свойства.
Свойства этого объекта определяют различные характеристики поля, включенного в таблицу:
Property Name As String. Задает имя поля.Property Type As DataTypeEnum. Задает тип поля. Возможные значения определяются константами из уже упоминавшегося перечисления DataTypeEnum.Property DefinedSize As Long. Задает определяемый размер поля. Заметьте, фактический размер может быть меньше и зависит от значения, хранимого в поле.Property Precision As Long, Property NumericScale As Byte. Эти два свойства определены только для полей, хранящих числовые значения. Они определяют общее число хранимых цифр и число цифр после запятой.Property SortOrder As SortOrderEnum. Записи в таблице могут быть упорядочены по тому или иному полю. Данное свойство определяет порядок сортировки, - по возрастанию или убыванию, что задается его возможными значениями: adSortAscending, adSortDescending. Свойство применимо только для индексируемых полей.Property ParentCatalog As Catalog. Свойство задает родительский каталог, позволяя подняться при необходимости к корневому объекту ADOX. Включение корневого объекта в объекты нижних уровней типично для объектных моделей, используемых Microsoft. Такое включение облегчает программистам работу, упрощая навигацию между объектами.Property RelatedColumn As String. Это свойство применимо только для ключевых полей, оно указывает имя поля, являющегося внешним ключом в связываемой таблице. Для уже присоединенных полей к коллекции Columns свойство имеет статус "только для чтения".Property Attributes As ColumnAttributesEnum. Как и для других объектов ADO, обладающих подобным свойством, данное свойство задает набор дополнительных атрибутов объекта. Значение свойства представляет маску, каждый бит которой определяет, включен или выключен тот или иной атрибут. Возможных атрибутов, которые можно установить, всего два - adColFixed и adColNullable. Они указывают, имеет ли поле фиксированную длину и может ли использоваться значение Null. Значение этого свойства, принимаемое по умолчанию и равное 0, означает, что упомянутые атрибуты не включены.Property Properties As Properties. Задает В заключение отмечу, что конкретный Провайдер может не поддерживать всех свойств объекта Column.
Напомню, ключом таблицы называется совокупность полей, однозначно идентифицирующая каждую запись таблицы. Если для идентификации записи требуется несколько полей, то такой ключ называется составным. Среди возможных ключей выделяется один, называемый первичным ключом (primary key). Access при построении таблицы, если не задается ее ключ, предлагает включить в число полей специальное поле типа "Счетчик". Это поле объявляется ключевым, а его значения строятся автоматически, чаще всего путем увеличения предыдущего значения счетчика на 1 при каждом добавлении новой записи, что и обеспечивает уникальность значений ключа. Однако, заметьте, среди типов полей, задаваемых перечислением DataTypeEnum, значения, соответствующего полю "Счетчик", нет.
Таблицы в реляционных базах данных связываются между собой, за счет того, что они имеют общие ключевые поля. Поле в связанной таблице называется внешним ключом, если это поле является частью ключа другой таблицы. Таблица с первичным ключом называется основной или базисной, а таблица, содержащая внешний ключ, связанной или связующей. В предыдущих версиях Access все таблицы обязаны были иметь первичный ключ. В Access 2000 связующие таблицы могут и не иметь первичного ключа. Разумно, однако, задавать ключи для всех таблиц базы данных. Ключ, который не является первичным, имеет статус "уникальный" (Unique).
Объект Key представляет первичный, внешний или уникальный ключ. Создается объект конструктором New, а добавляется в коллекцию методом Append, в соответствии с приведенной выше схемой. С объектной точки зрения объект Key устроен достаточно просто. Также как и многие другие объекты ADOX, он не имеет ни событий, ни методов, - только свойства. Их немного, - всего 6. Вот их описание:
Property Name As String. Задает имя ключа.Property Type As KeyTypeEnum. Задает тип ключа. Возможные значения задаются константами перечисления: adKeyPrimary, adKeyForeign, adKeyUnique, определяющими, соответственно, первичный, внешний и уникальный ключ.Property Columns As Columns. Возвращает коллекцию полей для составного ключа.Property DeleteRule As RuleEnum, Property UpdateRule As RuleEnum. Эти два свойства определяют правила (процедуры), выполняемые при удалении или изменении первичного ключа.Property RelatedTable As String. Задает таблицу, которой принадлежит ключ, что позволяет подняться по иерархии объектов.Если таблица имеет первичный ключ, то она имеет и индекс, который автоматически создается по данному ключу. Однако индексы могут создаваться и независимо от создания ключевых полей. На индексы не накладывается жесткое условие уникальности значений индекса в таблице. Индексы служат для того, чтобы ускорить операции по поиску и сортировке данных таблицы. Платой за это является увеличение памяти, требуемой для индексов и увеличение времени, требуемое на перестроение индексов, когда записи в таблицу добавляются или удаляются. Для больших, редко обновляемых таблиц, к которым часто приходится обращаться для поиска данных, построение индексов может существенно ускорить работу с базой данных. Для больших, часто обновляемых таблиц задание индексов может приводить к потере эффективности. Поэтому разумное введение индексов требует, как правило, проведения специальных исследований и опыта работы у администратора базы данных.
Объект Index задает индекс таблицы. Он создается с использованием конструктора New и добавляется в коллекцию Indexes методом Append, в соответствии с ранее приведенной схемой. Как и объекты Table и Key он имеет только свойства. Рассмотрим их:
Property Name As String. Свойство по умолчанию, задает имя индекса.Property PrimaryKey As Boolean, Property Unique As Boolean, Property Clustered As Boolean. Булевы свойства, позволяющие определить, является ли Property Columns As Columns. Для составного индекса возвращает коллекцию полей, входящих в индекс.Property IndexNulls As AllowNullsEnum. Позволяет указать, как обрабатываются значения Null, если они встречаются в поле индекса.Property Properties As Properties. Возвращает Пора привести пример программного создания таблицы и работы с объектами, связанными с таблицами базы данных. В предыдущих примерах уже была создана программно база данных с именем NewDB. Рассмотрим еще одну процедуру из модуля "TestingADOX", в которой в эту базу данных будет добавлена таблица "Книги", уже появлявшаяся в наших примерах. Но теперь создадим эту таблицу программно, используя возможности объектов ADOX. Вот текст процедуры, решающей эту задачу:
Public Sub CreateTable() 'Создание таблицы "Книги" и присоединение ее к базе данных NewDB 'Создание объектов - таблицы, ее полей и ключа Dim myT As New ADOX.Table 'таблица Dim myC(1 To 6) As New ADOX.Column 'поля таблицы Dim myK As New ADOX.Key 'ключ таблицы 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 'Создание объекта Table 'Создание полей таблицы myC(1).ParentCatalog = Cat1 myC(1).Name = "Код_книги" myC(1).Type = adInteger Call myT.Columns.Append(myC(1)) myC(2).ParentCatalog = Cat1 myC(2).Name = "Автор" Call myT.Columns.Append(myC(2)) myC(3).ParentCatalog = Cat1 myC(3).Name = "Название_книги" Call myT.Columns.Append(myC(3)) myC(4).ParentCatalog = Cat1 myC(4).Name = "Год_издания" Call myT.Columns.Append(myC(4)) myC(5).ParentCatalog = Cat1 myC(5).Name = "Число_страниц" myC(5).Type = adSmallInt Call myT.Columns.Append(myC(5)) myC(6).ParentCatalog = Cat1 myC(6).Name = "Цена" myC(6).Type = adCurrency Call myT.Columns.Append(myC(6)) 'Создание ключа myK.Columns.Append (myC(1)) myK.RelatedTable = "myT" myK.Type = adKeyPrimary myK.Name = "Код_книги" Call myT.Keys.Append(myK) 'Присоединение таблицы myT.Name = "Книги" Call Cat1.Tables.Append(myT) End Sub
А теперь комментарии к этой процедуре:
Table, Column и Key. Работа с ними ведется в полном соответствии со схемой, которую я приводил выше. Заметьте, я ввел массив объектов Column, число элементов которого определяется по числу полей, добавляемых в таблицу.Append в момент присоединения поля к коллекции не увенчались успехом в данном контексте. В этом случае все поля создаваемой таблицы в Access имели один и тот же текстовый тип.myK я добавил к его коллекции Columns ключевое поле - уже созданный объект myC(1). Заметьте, добавление в коллекцию Columns объекта Key возможно только после того, как объект добавлен в коллекцию Columns объекта Table.myT, после чего мне осталось только присоединить объект к коллекции Keys.Tables и Keys уже сформированы, к коллекции Tables объекта Catalog, для которого установлено соединение с ранее созданной базой данных Access с именем NewDB.В следующем примере в таблицу "Книги" я добавляю два индекса, один из них связан с полем "Автор", другой - "Название_книги". Приведу процедуру, решающую задачу:
Public Sub AddIndex()
'Процедура добавляет индексы в таблицу "Книги"
Dim myT As ADOX.Table 'таблица
Dim myC1 As Column, myC2 As Column 'поля таблицы
Dim myInd1 As New Index, myInd2 As New Index 'индексы таблицы
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'Связывание объекта Table с таблицей "Книги"
Set myT = Cat1.Tables("Книги")
Set myC1 = myT.Columns("Автор")
Set myC2 = myT.Columns("Название_книги")
'Формирование индексов
myInd1.Columns.Append (myC1)
myInd1.Name = "Author"
myInd1.PrimaryKey = False
myInd1.Unique = False
myInd1.Clustered = False
myInd2.Columns.Append (myC2)
myInd2.Name = "Book"
myInd2.PrimaryKey = False
myInd2.Unique = False
myInd2.Clustered = False
'Добавление индексов
Call myT.Indexes.Append(myInd1)
Call myT.Indexes.Append(myInd2)
'Отладочная печать
Debug.Print "Число индексов =", myT.Indexes.Count
For Each myInd1 In myT.Indexes
Debug.Print "Имя индекса -", myInd1.Name
Next myInd1
End Sub
Результаты отладочной печати для нее следующие:
Число индексов = 3 Имя индекса - Код_книги Имя индекса - Author Имя индекса - Book
Заметьте, индексов три, поскольку первый из них был создан автоматически в момент добавления ключа в таблицу.
Думаю, что в особых комментариях данная процедура не нуждается, поскольку работа с индексами ведется по той же схеме, что и при работе с ключами таблицы. Чтобы убедиться, что все работает должным образом, взгляните, как выглядит таблица "Книги", открытая в режиме конструктора:
(рис 6.9) Программно созданная таблица "Книги"
В следующей процедуре в уже созданную таблицу добавляется новое поле и новый составной ключ. Приведу текст процедуры:
Public Sub InsertItems()
'Эта процедура добавляет элементы в уже созданную таблицу
Dim myT As Table
Dim myC As New Column
Dim myK As New Key
Dim myInd As Index
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'Связывание объекта Table с таблицей "Книги"
Set myT = Cat1.Tables("Книги")
'Добавление поля
myC.ParentCatalog = Cat1
myC.Name = "Художник"
myC.Attributes = adColNullable
Call myT.Columns.Append(myC)
'Добавление составного ключа
myK.Columns.Append (myT.Columns("Автор"))
myK.Columns.Append (myT.Columns("Название_книги"))
myK.RelatedTable = "myT"
myK.Type = adKeyUnique
myK.Name = "AandB"
Call myT.Keys.Append(myK)
End Sub
Все эти добавления в таблицу возможны и проходят без препятствий. Заметьте, добавление составного ключа приведет к добавлению элемента в коллекцию индексов. Взгляните, как выглядит измененная таблица "Книги". Здесь же на рисунке можно увидеть и открытое окно с индексами таблицы, в котором отображается введенный составной ключ.
(рис 6.10) Изменения полей и индексов таблицы "Книги"
К сожалению, в Access попытка программно изменить характеристики полей таблицы или ее ключей и индексов невозможна. Я специально написал процедуру, в которой делается такая попытка:
Public Sub Modify()
'Изменение характеристик таблицы в Access невозможно!
Dim myT As Table
Dim myC As Column
Dim myK As Key
Dim myInd As Index
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'Связывание объекта Table с таблицей "Книги"
Set myT = Cat1.Tables("Книги")
Set myC = myT.Columns("Название_книги")
'myC.DefinedSize = 150
Set myC = myT.Columns("Число_страниц")
'myC.Type = adVarWChar
Set myK = myT.Keys("Код_книги")
'myK.Type = adKeyUnique
Set myK = myT.Keys("AandB")
'myK.Type = adKeyPrimary
End Sub
Как видите, все операторы, производящие изменения закомментированы, поскольку выполнение любого из них приводит к появлению ошибки. Хотя модификация характеристик полей, ключей и индексов таблицы базы данных Access невозможна, удаление самих элементов происходит без затруднений. Это позволяет при необходимости внесения изменений в характеристики элемента, вначале удалить его, а затем ввести заново с новыми характеристиками. Приведу процедуру, выполняющую удаление поля, ключа и индекса:
Public Sub Removing()
'Удаление элементов таблицы
Dim myT As Table
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'Связывание объекта Table с таблицей "Книги"
Set myT = Cat1.Tables("Книги")
myT.Columns.Delete ("Художник")
myT.Indexes.Delete ("AandB")
'myT.Keys.Delete ("AandB")
End Sub
Заметьте, когда я вводил ключ "AandB", то был создан индекс с этим же именем. Удалив этот индекс, я автоматически удалили и ключ. По этой причине последний оператор процедуры закомментирован.
На этом я закончу рассмотрение объектов, связанных с таблицами базы данных и займусь другими объектами модели ADOX, которые позволяют работать с запросами, представлениями и пользователями.
Объект Procedure представляет собой хранимую процедуру. Хранимые процедуры, текст которых, чаще всего, пишется на SQL, хранятся на сервере в оттранслированном виде, что ускоряет их выполнение. Еще одно немаловажное достоинство состоит в том, что облегчается задача программиста, вызывающего хранимую процедуру, когда он пишет свой код на VBA или VBScript, поскольку ему не нужно вникать в тонкости операторов SQL.
Коллекция Procedures объекта Catalog включает в себя все хранимые процедуры базы данных. Как и у всех коллекций ADOX у нее три метода: Append, Delete, Refresh и два свойства - Item и Count. Тем не менее, есть особенность в создании объекта Procedure и добавлении его в коллекцию. Прежняя схема, используемая при создании объектов Table, Key, Index для него не применима. Этот объект не может быть создан с помощью конструктора New. Для того чтобы добавить в коллекцию новый объект Procedure, следует создать объект Command, задать текст процедуры, используя свойство CommandText, а затем применить метод Append коллекции Procedures. Заметьте, у этой коллекции метод Append имеет два параметра, - первый задает имя хранимой процедуры, второй объект Command, определяющий, по существу, саму процедуру. Используя объекты Procedure, Procedures и Command можно программно создать хранимую процедуру, модифицировать существующую процедуру или удалить процедуру из коллекции.
Объект Procedure устроен очень просто, - у него всего 4 свойства:
Property Name As String. Задает имя объекта. Имеет статус "только для чтения", что объясняется особенностями создания этого объекта.Property DateCreated As Variant, Property DateModified As Variant. Эти два свойства, также имеющие статус "только для чтения", задают дату создания и последней модификации процедуры.Property Command As Variant. Задает объект Command из библиотеки ADODB. В предыдущей главе этот объект был достаточно подробно описан и приведено большое число примеров работы с ним. Свойство является центральным, поскольку именно объект Command определяет суть хранимой процедуры и позволяет создать процедуру.В базе данных Access нет понятия хранимых процедур, их роль играют хранимые запросы. Было бы хорошо, если бы Провайдер Microsoft Jet при создании объектов коллекции Procedures создавал вместо этого хранимые запросы. Однако этого не происходит, - запросы не создаются. Поэтому, казалось бы, при работе с базой данных Access создавать программно процедуры невозможно и, главное, бесполезно. Тем не менее, при попытке создать коллекцию Procedures никаких ошибок не возникает и, по крайней мере, одна процедура создается и с ней можно впоследствии работать, вызывая ее на исполнение.
Я приведу сейчас пример, в котором демонстрируется сам способ создания коллекции Procedures. Этот способ не зависит от Провайдера и базы данных, он может с успехом использоваться при работе с Access
Public Sub CreateQuery()
'Создание хранимых процедур
Dim myCmd1 As New Command, myCmd2 As New Command
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
'SQL-запросы, представляющие тексты хранимых процедур
myCmd1.CommandText = "Select [Книги.Автор],[Книги.Название_книги]" _
"From [Книги]" _
"WHERE ((([Книги.Год_издания])= 1999))" _
"ORDER BY [Книги.Автор];"
'Запрос с параметром
myCmd2.CommandText = "Parameters [Avtor] Text(50);" _
"Select [Книги.Автор],[Книги.Название_книги]" _
"From [Книги]" _
"WHERE ((([Книги.Автор])= [Avtor]));"
'Удаление процедур
Dim i As Integer
For i = 0 To Cat1.Procedures.Count - 1
Cat1.Procedures.Delete (i)
Next i
'Добавление процедур
Debug.Print Cat1.Procedures.Count
Call Cat1.Procedures.Append("Books1999", myCmd1)
Debug.Print Cat1.Procedures.Count
Call Cat1.Procedures.Append("BooksOfAuthor", myCmd2)
Debug.Print Cat1.Procedures.Count
End Sub
Сама процедура и то, как она выполняется, нуждается в подробных комментариях:
Command.CommandText этих объектов позволяет задать текст команд, представляющих в данном случае SQL-запросы. Второй из этих запросов представляет запрос с параметром.Append коллекции Procedures, которому в качестве одного из параметров передаются созданные объекты Command. По завершении работы процедуры создается впечатление, что коллекция Procedures с двумя элементами успешно создана. Однако это не совсем так.Count в конце работы этой процедуры будет возвращать число 1, а сама коллекция будет хранить второй Books1999 ", хотя коллекция и пуста.Не следует особенно расстраиваться, что хранимые процедуры не работают корректно в Access, - они и не должны работать. Можно, однако, использовать тот факт, что с одной процедурой все-таки работать разрешается. Вот пример работы, демонстрирующий работу с сохраненной в предыдущем примере процедурой с именем BooksOfAuthor, имеющей имя автора в качестве параметра запроса:
Public Sub WorkWithProcs()
'работа с хранимыми процедурами
Dim myCmd As Command
'Установить соединение с базой NewDB
CreateConnection
Cat1.ActiveConnection = Con1
Set myCmd = Cat1.Procedures("BooksOfAuthor").Command
Set Rst1 = myCmd.Execute(, "Пушкин")
With Rst1
.MoveFirst
Do While Not .EOF
Debug.Print .Fields(0), .Fields(1)
.MoveNext
Loop
End With
End Sub
В результате работы этой процедуры на печать были выданы все книги, написанные Пушкиным, записи о которых хранились в базе данных.
Объекты View задают так называемые представления. Они подобны объектам Procedure, хранятся на сервере в виде оттранслированных запросов и задают отфильтрованное множество записей. Представления являются элементами таких баз данных как Microsoft SQL Server , а, следовательно, и View имеет те же свойства, что и объект Procedure и соответствующие коллекции устроены одинаково, то я не буду задерживаться на более подробном описании этих объектов.
Для того чтобы обеспечить защиту баз данных от неавторизованного доступа, для того чтобы разным пользователям предоставить разные права доступа к тем или иным объектам базы данных - таблицам, запросам, полям таблицы, одним из основных используемых механизмов является введение учетных записей для пользователей и групп пользователей базы данных. На объектном уровне защита баз данных поддерживается объектами и их Users и Groups объекта Catalog возвращают одноименные коллекции всех пользователей и всех групп, связанных с базой данных - объектом Catalog. Но поскольку каждая группа содержит одного или нескольких пользователей, то свойством Users обладает и объект Group, для него свойство возвращает всех пользователей, входящих в группу. Groups обладает объект User, для него свойство возвращает все группы, в которые входит данный пользователь.
Коллекции Users и Groups устроены, как и все коллекции ADOX, у них три метода - Append, Delete, Refresh и два свойства: Item и Count. Создаются объекты и присоединяются к коллекции также согласно общей схеме, - при создании используется конструктор New, для присоединения - метод Append. Замечу, что, наконец-то, хотя бы в ADOX достигнута желаемая общность в организации коллекций.
Объект определяет учетную запись пользователя, позволяет задать имя пользователя, пароль, права доступа к объектам базы данных и группы, в которые входит этот пользователь. У этого объекта два свойства:
Property Name As String. Задает имя пользователя. Является свойством по умолчанию.Property Groups As Groups. Возвращает одноименную коллекцию, содержащую все группы, в которые входит данный пользователь.Пароль пользователя и разрешения на право доступа к тем или иным объектам базы данных задаются методами объекта User. Методов всего три:
Sub ChangePassword(OldPassword As String, NewPassword As String). Чтобы задать новый пароль, нужно знать и старый пароль. В случае, когда пароль задается в первый раз, старым паролем является пустая строка.Function GetPermissions(Name, ObjectType As ObjectTypeEnum, [ObjectTypeId]) As RightsEnum. Метод (функция) возвращает разрешения на то, что может делать пользователь с объектом базы данных, чье имя задается параметром Name, а тип - параметром ObjectType. Третий параметр указывается лишь в тех исключительных ситуациях, когда речь идет об объектах, специфических для Провайдера. Права пользователя на объект задаются константами из перечисления RightsEnum. Возвращаемое значение может быть суммой отдельных констант и представляет маску, задающую отдельные разрешения. Различных констант в перечислении достаточно много, приведу лишь некоторые из них: adRightCreate, adRightDelete, adRightMaximumAllowed, adRightNone.Sub SetPermissions(Name, ObjectType As ObjectTypeEnum, Action As ActionEnum, Rights As RightsEnum, [Inherit As InheritTypeEnum = adInheritNone], [ObjectTypeId]). Метод позволяет установить разрешения на возможные действия с элементом базы, чье имя задано параметром Name, а тип - параметром ObjectType. Параметр Rights задает маску, определяющую право на те или иные действия, значением параметра является сумма констант из перечисления RightsEnum. Параметр Action задает действия, выполняемые при установке разрешений. Параметр Inherit указывает, как объекты наследуют разрешения.Группы облегчают управление правами доступа. Проще, быстрее и правильнее с содержательной точки зрения задать права группы, чем задавать их индивидуально для каждого пользователя. Хотя, конечно же, есть пользователи, для которых права задаются индивидуально.
С объектной точки зрения объект Group, во многом, устроен аналогично объекту User, - у него нет метода ChangePassword, поскольку у групп нет паролей, а свойство Groups заменяет симметричное свойство Users. Таким образом, у объекта Group два свойства: Name, Users и два метода - GetPermissions, SetPermission.
База данных Microsoft Jet позволяет устанавливать защиту с использованием механизма учетных записей пользователей и групп пользователей. По умолчанию, когда создается новая база данных, автоматически создаются две типичные группы пользователей - Admins и Users. Первая из них включает администраторов базы, обладающих максимальными правами, другая - "обычных" пользователей. В специальной системной базе данных Access хранится информация о группах, о пользователях, их именах и паролях. Эта база данных хранится в файле с именем System и расширением - mdw. Обычный путь к этому файлу - "c:\Program Files\Microsoft Office\Offce\system.mdw". Фактический путь можно посмотреть и при необходимости изменить в реестре, где он находится в разделе:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\9.0\Access\Jet\4.0\Engines Параметр, задающий путь, имеет имя SystemDB.
В нижеследующем примере я ввел в дополнение к существующим группам еще две группы - читателей и писателей - с именами, соответственно, Readers и Writers и четырех новых пользователей, входящих по-разному в эти группы. Группам заданы различные права на доступ к таблицам базы данных. Но, прежде чем давать другие пояснения приведу соответствующий программный код:
Public Sub CreateUsersАndGroups()
'Cоздание пользователей и групп
Dim myUs(1 To 4) As New User 'учетные записи пользователей
Dim myGr(1 To 2) As New Group 'учетные записи групп
'Установить соединение с базой NewDB
'и системной базой данных System.mdw
CreateConnectionSystemDB
Cat1.ActiveConnection = Con1
'Пользователи
myUs(1).Name = "Vladimir"
myUs(1).ChangePassword "", "Old111"
Cat1.Users.Append myUs(1)
myUs(2).Name = "Nina"
Call myUs(2).ChangePassword("", "Best111")
Call Cat1.Users.Append(myUs(2))
myUs(3).Name = "Ilya"
Call myUs(3).ChangePassword("", "Prim111")
Call Cat1.Users.Append(myUs(3))
myUs(4).Name = "Yuly"
Call myUs(4).ChangePassword("", "Clev111")
Call Cat1.Users.Append(myUs(4))
'Группы
myGr(1).Name = "Readers"
Call Cat1.Groups.Append(myGr(1))
myGr(1).Users.Append (myUs(1))
myGr(1).Users.Append (myUs(2))
myGr(1).Users.Append (myUs(3))
myGr(1).Users.Append (myUs(4))
myGr(2).Name = "Writers"
Call Cat1.Groups.Append(myGr(2))
myGr(2).Users.Append (myUs(1))
'Установление прав
Call myGr(1).SetPermissions("Книги", _
adPermObjTable, adAccessSet, adRightRead, adInheritBoth)
Call myGr(1).SetPermissions("Заказчики", _
adPermObjTable, adAccessSet, adRightRead, adInheritBoth)
Call myGr(2).SetPermissions("Книги", _
adPermObjTable, adAccessSet, adRightFull, adInheritBoth)
Call myGr(2).SetPermissions("Заказчики", _
adPermObjTable, adAccessSet, adRightFull, adInheritBoth)
Call myUs(3).SetPermissions("Заказчики", _
adPermObjTable, adAccessSet, adRightMaximumAllowed, adInheritBoth)
End Sub
Прежде всего, хочу обратить внимание на главную особенность работы с объектами и их коллекциями, когда речь идет о Провайдере Microsoft Jet. При установлении соединения необходимо связаться не только с самой базой данных, но и с системной базой данных. По этой причине вызывается специальная процедура, устанавливающая такое соединение:
Public Sub CreateConnectionSystemDB() 'Создание соединения с базой данных Access - NewDB ' и системной базой данных - System.mdw Dim strConnStr As String If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.ConnectionString = _ "Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb;" _ "Jet OLEDB:System database =" _ "c:\Program Files\Microsoft Office\Office\system.mdw" 'Открытие соединения Con1.Open End Sub
Заметьте, я задаю специфическое для Провайдера свойство "Jet OLEDB:System database" непосредственно в строке соединения, хотя мог бы это сделать, используя коллекцию Properties.
Возвращаясь к процедуре CreateUsersAndGroups, приведу еще несколько комментариев к ее работе:
User и Group, присоединение их к коллекциям идет по традиционной схеме.Users для каждой группы. Заметьте, при этом автоматически будут созданы коллекции Groups для каждого объекта User."Ilya".В заключение пара экранных снимков. Взгляните, как выглядит результаты одного из запросов к системной базе данных, после выполнения процедуры CreateUsersAndGroups:

(рис 6.12) Результаты запроса к системной базе данных Access(рис 6.11) Показ прав доступа к элементам базы данных AccessКонечно, все, что я сделал программно, можно сделать и руками, выбрав пункт меню "Сервис | Защита" и открыв соответствующее окно, например, окно разрешений, показанное на рис. 6.12.
Последний пример в этой главе я хочу посвятить хотя бы беглому знакомству использования объектов ADO в сценариях, написанных на VBScript. Я рассмотрю пример, представляющий фрагмент создания Web-узла. Создаваемый узел является активным, он может реагировать на действия пользователя, используя для этих целей технологию ASP(Active Server Page) страниц. Напомню, эти страницы содержат сценарии, которые собственно и определяют реакцию на действия пользователя. Особенностью ASP-страниц является то, что код сценариев выполняется на сервере и пользователю пересылается статическая страница, содержащая не код, а результат его выполнения.
В рассматриваемом фрагменте рассматривается типичная для активных Web-узлов ситуация, когда пользователь заполняет некоторую форму и отсылает ее на сервер. ASP-страница обрабатывает данные формы, сохраняет их в базе данных, добавляя новые записи в таблицы, и формирует новую страницу, пересылаемую пользователю. Вот как выглядит страница узла, на которой пользователь заполняет форму:
(рис 6.13) Web-страница, содержащая форму, отсылаемую на серверПриведу теперь текст ASP-страницы, которая выполняется на сервере:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title>vbazu</title>
<meta name="GENERATOR" content="Microsoft FrontPage 4.0">
<meta name="ProgId" content="FrontPage.Editor.Document">
<meta name="Microsoft Theme" content="none, default">
<meta name="Microsoft Border" content="none, default">
</head>
<body>
<table border="0" width="100%" bgcolor="#0000FF" cellspacing="0" cellpadding="0" height="81">
<tr>
<td width="100%" height="62">
<p align="center">
<b><font face="Bradley Hand ITC" size="7" color="#FFFFFF">BiVANTnbsp;nbsp;</font></b></td>
</tr>
<tr>
<td width="100%" bgcolor="#FFFF00" height="19">
<font color="#0000FF"><b><a href="index.htm">BiVANT</a>nbsp;
<a href="книги.htm">Книги</a>nbsp; <a href="статьи.htm">Статьи</a>nbsp;
<a href="учебники.htm">Учебники</a>nbsp; <a href="обучение.htm">Обучение</a>nbsp;
<a href="чайничек.htm">Чайничек</a>nbsp;
<a href="Дело%20отца/дело_отца.htm">Дело_Отца</a>nbsp;
<a href="онас.htm">О нас</a>nbsp; <a href="авторы.htm">Авторы</a></b></font></td>
</tr>
</table>
<p>
<%
'Переменные, задающие поля формы
Dim DAvtor, DFam, DName, DVid, DGorod, Dmail, DOtziv, DVopros, DPriob
'Передача значений полей формы
DFam= Request.Form("Fam")
DName = Request.Form("Name")
DVid= Request.Form("Vid")
DGorod =Request.Form("Gorod")
Dmail= Request.Form("mail")
DOtziv= Request.Form("Otziv")
DPriob= Request.Form("Priob")
DVopros =Request.Form("Vopros")
DAvtor = Request.Form("Avtor")
'Объявление объектов ADO - Connection, Command, Recordset
Dim Cconnect, Cmd, Records
Dim StrSQL 'строка запроса
' Установить связь с базой данных
Set Cconnect =Server.CreateObject("ADODB.Connection")
Cconnect.ConnectionString= "provider=microsoft.jet.oledb.4.0;"
"data source ='d:\Bivant\svyaz.mdb'"
Cconnect.Open
'Формирование строки запроса
StrSQL = "Select * From VoprosOtvet"
'Создание команды и задание свойств объекта Command
Set Cmd =Server.CreateObject("ADODB.Command")
Cmd.ActiveConnection = Cconnect
Cmd.CommandText = StrSQL
Cmd.CommandType =1
'Работа с RecordSet
Set Records =Server.CreateObject("ADODB.Recordset")
With Records
'Открытие обновляемого объекта RecordSet
.Open Cmd,,2,3
' Добавление записи
.MoveFirst
.AddNew
.Fields("Автор")=DAvtor
.Fields("Вопрос") = DVopros
.Fields("Отзыв") = DOtziv
.Fields("Фамилия") =DFam
.Fields("Имя") =DName
.Fields("Город") = DGorod
.Fields("Вид_деят") =DVid
.Fields("Почта") =Dmail
.Fields("Приобрести") =DPriob
.Update
End With
%>
<b>Спасибо за Ваш интерес к нашей тематике!</b>
</p>
<p><b>Мы постараемся учесть Ваши пожелания и
своевременно ответить на вопрос. </b>
</p>
</body>
</html>
Приведу теперь комментарии к этому тексту:
Variant, поэтому для переменных, соответствующих полям формы не задан тип.ASP Request позволяет передать значения из полей формы в переменные VBScript.CreateObject объекта Server.Recordset и обновление базы данных, выполняется на VBScript совершенно аналогично тому, как это делалось на VBA. В результате выполнения этого сценария база данных действительно изменяется и данные, посланные в форме, будут сохранены в соответствующей таблице базы данных.На этом я закончу разговор о компонентах ADO, представляющих универсальный способ взаимодействия с самыми разными источниками данных.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.