ADO и DAO
До появления объектов ADO при работе с базой данных Access использовались объекты DAO (Data Access Objects), являющиеся предшественниками ADO. Они и сейчас, естественно, могут использоваться в уже разработанных и действующих приложениях. В новых разработках следует отдать предпочтение объектам ADO. Объектные модели ADO и DAO хотя и имеют много общего, но имеют и существенные различия. Так что вряд ли целесообразно переходить к новым объектам в уже работающих приложениях, если это не вызвано особыми причинами. В этом разделе я приведу сравнение объектных моделей и другие сведения, облегчающие переход от DAO к ADO в случае, если такая необходимость возникла.
Анализ ошибок в обработчике событий
Я хочу теперь рассмотреть ситуации, когда при выполнении операции по тем или иным причинам возникают ошибки. Мы рассмотрим, как выглядят обработчики ошибок в таких ситуациях, как можно проанализировать содержимое объектов 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
В этой процедуре следует обратить внимание на следующие моменты:
"Правильный" Провайдер, позволяющий работать с базой данных Access закомментирован. Указан Провайдер "MSDASQL", - его вызов приведет к ошибке при установлении соединения. Для удобства восприятия текст этой строки и других строк, требующих внимания, подсвечен.Чтобы иметь возможность написать обработчики событий, возникающих при соединении, введен соответствующий объект 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
Прокомментирую работу этой процедуры:
Вначале создается диалоговое окно функции MsgBox, в котором выводится информация о входных параметрах обработчика соединения. На рис. 6.3 можно увидеть, как выглядит это окно и следующие два окна сообщений, связанных с открытием соединения.
![](image/6-3.jpg)
Рис. 6.3. Окна сообщений, появляющиеся при установлении соединения
Обратите внимание, статус показывает, что в момент соединения возникла ошибка, поэтому строка соединения короткая и не содержит информации, добавляемой Провайдером. Следующая часть обработчика ConnectComplete работает тогда, когда обработчику передается объект Error. В функции MsgBox, окно которой также показано на рис. 6.3, формируется информация о свойствах объекта Error, в частности, описание ошибки показывает, что ODBC-Провайдер не сумел найти источник данных.В обработчике события "исправляется" ошибка и устанавливается "правильный" Провайдер. Конечно, в реальной ситуации для этого мог понадобиться диалог с пользователем, но я решил упростить демонстрационный пример. В процедуре есть еще одна часть, связанная с выдачей отладочной информации, но о ней чуть позже, а пока скажу, что произойдет по окончании работы обработчика событий. Понятно, что когда управление вернется в процедуру ConnectionWithEvents в оператор Con1.Open, вызвавший событие, то из-за возникновения ошибки в охраняемом блоке управление будет перехвачено и передано на метку Check. Оператор Resume заставит повторить открытие соединения, но теперь уже свойства соединения заданы корректно. Снова работает процедура 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, при попытке изменить поле "Цена":
![](image/6-4.jpg)
Рис. 6.4. Серия окон, появляющихся при попытке изменить значение поля.
Возможно, полезно взглянуть и на информацию, появляющуюся в окне отладки:
0 1 Operation was canceled. 1 Operation was canceled. 1 Multiple-step operation generated errors. Check each status value.
Добавление индексов
В следующем примере в таблицу "Книги" я добавляю два индекса, один из них связан с полем "Автор", другой - "Название_книги". Приведу процедуру, решающую задачу:
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
Заметьте, индексов три, поскольку первый из них был создан автоматически в момент добавления ключа в таблицу.
Думаю, что в особых комментариях данная процедура не нуждается, поскольку работа с индексами ведется по той же схеме, что и при работе с ключами таблицы. Чтобы убедиться, что все работает должным образом, взгляните, как выглядит таблица "Книги", открытая в режиме конструктора:
![](image/6-9.jpg)
Рис. 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
Все эти добавления в таблицу возможны и проходят без препятствий. Заметьте, добавление составного ключа приведет к добавлению элемента в коллекцию индексов. Взгляните, как выглядит измененная таблица "Книги". Здесь же на рисунке можно увидеть и открытое окно с индексами таблицы, в котором отображается введенный составной ключ.
![](image/6-10sm.jpg)
увеличить изображение
Рис. 6.10. Изменения полей и индексов таблицы "Книги"
Другие объекты модели ADO
Свойства, методы и события основных объектов ADO - Connection, Command, Recordset, Error уже рассмотрены. В примерах появлялись и другие объекты - Field, Parameter, Property и их коллекции. Рассмотрим свойства и методы этих и других объектов ADO, но уже с меньшей степенью детализации.
Другие Провайдеры
Краткие данные о других Провайдерах приведу в таблице.
>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. Если параметр опущен, то по умолчанию используется системный каталог. Параметр Locale Identifier указывает предпочтения при форматировании данных, заданные пользовательским языком. | Поддерживает программный доступ со статусом "только для чтения" к файловой системе и Web-страницам, индексируемых службой Microsoft Indexing Service. | |
Microsoft Active Directory Service Interfaces (ADSI) Provider | "Provider=ADSDSOObject;User ID=userName;Password=userPassword;" |
to connect to heterogeneous directory services through ADSI. This gives ADO applications read-only access to the Microsoft Windows NT® 4.0 and Microsoft Windows 2000 directory services, in addition to any LDAP-compliant directory service and Novell Directory Services. ADSI itself is based on a provider model, so if there is a new provider giving access to another directory, the ADO application will be able to access it seamlessly. Позволяет связаться с гетерогенными службами каталогов - 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 |
Коллекции Tables, Procedures, Views, Groups, Users
С объектной точки зрения все эти коллекции устроены одинаково. Они хранят соответствующие элементы классов: 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 .
Коллекция Errors и объекты Error
Напомню, что элементы коллекции Errors появляются при выполнении очередной команды, когда Провайдер сталкивается с определенными трудностями и не может нормальным образом завершить выполнение команды. Коллекция Errors всегда связана только с одной командой, - при первой ошибке в новой команде происходит чистка старого содержимого коллекции. Свойство Errors объекта Connection возвращает эту коллекцию. Добавлять программно элементы в коллекцию невозможно, это делается автоматически, когда Провайдер обнаруживает новую ошибку.
Коллекция Fields и объект Field
Свойство 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]). Позволяет обновить значения всех полей у записей, хранящихся в буфере.
Коллекция Parameters и объект Parameter
Объект Command может определять запрос или хранимую процедуру с параметрами. А посему этот объект имеет свойство Parameters, возвращающее одноименную коллекцию, элементами которой являются объекты Parameter. Содержательно, каждый из этих объектов определяет параметр - входной или выходной, который передается или возвращается при вызове хранимой процедуры. Я уже приводил пример вызова запроса с параметром, где появлялись эти объекты.
Коллекция Parameters устроена достаточно просто. У нее всего два типичных свойства - Item и Count. У коллекции три метода, которые также появлялись в нашем рассмотрении. Вот эти методы:
Sub Append(Object As Object). Метод позволяет присоединить объект Parameter, заданный аргументом, к коллекции. Обычно присоединяемый объект создается методом CreateParameter объекта Command. В процедуре CreateCommands, приведенной в предыдущей главе, продемонстрировано применение методов CreateParameter и Append.Sub Delete(Index). Метод позволяет удалить из коллекции параметр, по имени или порядковому номеру, указываемому в аргументе Index.Sub Refresh(). Позволяет обновить содержимое коллекции, получая информацию о параметрах из хранимой процедуры.
Коллекция Properties и объект Property
Многие из объектов ADO имеют свойство Properties, возвращающее одноименную коллекцию элементов Property. Поскольку каждый объект обладает набором свойств, то возникает естественный вопрос, зачем необходимо еще и свойство Properties, которое тоже задает свойства. Дело в том, что свойства разделяются на встроенные и динамические. Динамические свойства зависят от специфики Провайдера, - они то и составляют коллекцию Properties. Каждый объект этой коллекции описывает то или иное динамическое свойство объекта ADO, зависящее от специфики Провайдера и доступное, естественно, только тогда, когда объект открыт. Добавлять или удалять элементы этой коллекции невозможно, - это прерогатива Провайдера.
С объектной точки зрения коллекция Properties устроена очень просто - у нее всего два типичных свойства: Item, Count и один метод Refresh. Также просто устроен и объект Property - у него всего четыре свойства: Name, Type, Value, Attributes. Они задают имя, тип, значение и некоторые дополнительные характеристики. Примеры работы с этими объектами уже приводились.
На этом я завершаю утомительное описание объектов ADO. Но нам предстоит еще хотя бы вкратце познакомиться с объектами ADOX.
Методы объекта 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.
Методы объекта Field
У этого объекта имеется всего два метода:
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, что может быть полезно при динамическом конструировании форм.
Методы объекта Stream
Рассмотрим теперь методы объекта 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(). Позволяет пропустить одну строку при чтении текстового файла. Используется в цикле, когда нужно пропустить несколько строк.
Модификация характеристик
К сожалению, в 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, которые позволяют работать с запросами, представлениями и пользователями.
Несколько примеров программной работы с таблицами
Пора привести пример программного создания таблицы и работы с объектами, связанными с таблицами базы данных. В предыдущих примерах уже была создана программно база данных с именем 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.
Объект Catalog
Центральный объект модели ADOX, задает каталог базы данных. Его метод Create позволяет создать новый источник данных, а свойства позволяют добраться до элементов, определяющих схему базы данных. Не все Провайдеры поддерживают полный набор возможностей этого объекта, определяемых его свойствами и методами. Так что источник данных и его Провайдер определяют, допустимо ли для него использование того или иного свойства или метода объектов ADOX.
Объект Column
Коллекция Columns с элементами Column возвращается при вызове одноименного свойства объекта Table, в этом случае она задает поля таблицы. Но эта коллекция появляется и в других случаях. Напомню, ключи и индексы таблицы могут быть составными и состоять из нескольких полей, по этой причине объекты Key и Index также обладают свойством Columns, которое возвращает коллекции полей, задающих составной ключ или составной индекс. Так что объект Column может задавать поле таблицы, ключа или индекса. Этот объект является центральным объектом в мире, связанном с таблицами. Устроен он достаточно просто, у него нет ни методов, ни событий, - есть только свойства.
Объект Group
Группы облегчают управление правами доступа. Проще, быстрее и правильнее с содержательной точки зрения задать права группы, чем задавать их индивидуально для каждого пользователя. Хотя, конечно же, есть пользователи, для которых права задаются индивидуально.
С объектной точки зрения объект Group, во многом, устроен аналогично объекту User, - у него нет метода ChangePassword, поскольку у групп нет паролей, а свойство Groups заменяет симметричное свойство Users. Таким образом, у объекта Group два свойства: Name, Users и два метода - GetPermissions, SetPermission.
Объект Index
Если таблица имеет первичный ключ, то она имеет и индекс, который автоматически создается по данному ключу. Однако индексы могут создаваться и независимо от создания ключевых полей. На индексы не накладывается жесткое условие уникальности значений индекса в таблице. Индексы служат для того, чтобы ускорить операции по поиску и сортировке данных таблицы. Платой за это является увеличение памяти, требуемой для индексов и увеличение времени, требуемое на перестроение индексов, когда записи в таблицу добавляются или удаляются. Для больших, редко обновляемых таблиц, к которым часто приходится обращаться для поиска данных, построение индексов может существенно ускорить работу с базой данных. Для больших, часто обновляемых таблиц задание индексов может приводить к потере эффективности. Поэтому разумное введение индексов требует, как правило, проведения специальных исследований и опыта работы у администратора базы данных.
Объект 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. Возвращает коллекцию свойств, специфических для используемого Провайдера.
Объект Key
Напомню, ключом таблицы называется совокупность полей, однозначно идентифицирующая каждую запись таблицы. Если для идентификации записи требуется несколько полей, то такой ключ называется составным. Среди возможных ключей выделяется один, называемый первичным ключом (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. Задает таблицу, которой принадлежит ключ, что позволяет подняться по иерархии объектов.
Объект Procedure и коллекция Procedures
Объект 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 определяет суть хранимой процедуры и позволяет создать процедуру.
Объект Record
Этот объект представляет отдельную запись набора 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 закрывается и заново открывается, но теперь у него другой источник данных и его значением становится каталог. Число полей в данном контексте уменьшилось до 18, все они определяют характеристики ресурса. Приведу имена и значения тех же полей в этом новом контексте:
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
Этот объект представляет двоичный поток данных или текст. Методы и свойства этого объекта позволяют выполнять операции над этим потоком, позволяя читать, писать, копировать и сохранять данные потока. Объекты Stream обычно используются при работе с данными, сохраненными в XML-формате, при работе с текстовыми файлами и сообщениями электронной почты, во всех случаях, когда данные рассматриваются как поток байтов. Объект Stream может быть получен тремя способами:
Заданием URL-адреса, указывающего на объект, обычно файл, содержащий двоичные данные или текст. Этот объект может быть простым документом, объектом Record, представляющим структурированный документ, или папкой.Заданием потока, ассоциированного с объектом Record.Быть созданным непосредственно в приложении, не будучи связанным ни с каким источником данных.
Объект Table
Этот объект определяет таблицу базы данных. Является элементом коллекции Tables, вложенной в объект Catalog. Создается конструктором New и добавляется в коллекцию Tables методом Append. Вот типичная схема программного создания новой таблицы:
Dim NewTable As New ADOX.Table 'Формирование объекта Table … Call Cat1.Tables.Append(NewTable)
Чуть позже я приведу полный пример программного создания объекта Table, а пока займемся рассмотрением его свойств, методов и событий. Задача облегчается тем, что у объекта Table нет методов и событий, - есть только свойства, рассмотрением которых сейчас и займемся.
Объект User
Объект определяет учетную запись пользователя, позволяет задать имя пользователя, пароль, права доступа к объектам базы данных и группы, в которые входит этот пользователь. У этого объекта два свойства:
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 указывает, как объекты наследуют разрешения.
Объект View и коллекция Views
Объекты View задают так называемые представления. Они подобны объектам Procedure, хранятся на сервере в виде оттранслированных запросов и задают отфильтрованное множество записей. Представления являются элементами таких баз данных как Microsoft SQL Server , а, следовательно, и MSDE. Поскольку с объектной точки зрения объект View имеет те же свойства, что и объект Procedure и соответствующие коллекции устроены одинаково, то я не буду задерживаться на более подробном описании этих объектов.
Объекты ADOX
Объекты этой группы, как уже было сказано, играют важную роль, позволяя работать с метаданными баз данных. С их помощью можно программно создать новую базу данных, создать или модифицировать элементы существующей базы данных, создать новые или модифицировать существующие таблицы, запросы, хранимые процедуры. Кроме того, эти объекты предназначены для работы с пользователями, группами пользователей, задания их прав доступа к данным, обеспечивая, тем самым, нужный уровень безопасности.
Рассмотрим объектную модель ADOX и начнем со схемы, отражающей взаимосвязи между объектами модели:
![](image/6-7.jpg)
Рис. 6.7. Объектная модель ADOX
На схеме не отражен тот факт, что объекты Table, Index, Column имеют стандартную коллекцию Properties с элементами Property.
Заметьте, в этой модели есть центральный объект Catalog, в который и встроены все основные коллекции: Tables, Procedures, Views, Groups, Users. Эти коллекции определяют различные метаэлементы базы данных. Давайте подробно рассмотрим свойства и методы объектов модели.
Объекты User, Group и их коллекции
Для того чтобы обеспечить защиту баз данных от неавторизованного доступа, для того чтобы разным пользователям предоставить разные права доступа к тем или иным объектам базы данных - таблицам, запросам, полям таблицы, одним из основных используемых механизмов является введение учетных записей для пользователей и групп пользователей базы данных. На объектном уровне защита баз данных поддерживается объектами User, Group и их коллекциями. Свойства Users и Groups объекта Catalog возвращают одноименные коллекции всех пользователей и всех групп, связанных с базой данных - объектом Catalog. Но поскольку каждая группа содержит одного или нескольких пользователей, то свойством Users обладает и объект Group, для него свойство возвращает всех пользователей, входящих в группу. Симметрично, свойством Groups обладает объект User, для него свойство возвращает все группы, в которые входит данный пользователь.
Коллекции Users и Groups устроены, как и все коллекции ADOX, у них три метода - Append, Delete, Refresh и два свойства: Item и Count. Создаются объекты и присоединяются к коллекции также согласно общей схеме, - при создании используется конструктор New, для присоединения - метод Append. Замечу, что, наконец-то, хотя бы в ADOX достигнута желаемая общность в организации коллекций.
Особенности работы с хранимыми процедурами в Access
В базе данных Access нет понятия хранимых процедур, их роль играют хранимые запросы. Было бы хорошо, если бы Провайдер Microsoft Jet при создании объектов коллекции Procedures создавал вместо этого хранимые запросы. Однако этого не происходит, - запросы не создаются. Поэтому, казалось бы, при работе с базой данных Access создавать программно процедуры невозможно и, главное, бесполезно. Тем не менее, при попытке создать коллекцию Procedures никаких ошибок не возникает и, по крайней мере, одна процедура создается и с ней можно впоследствии работать, вызывая ее на исполнение.
Я приведу сейчас пример, в котором демонстрируется сам способ создания коллекции Procedures. Этот способ не зависит от Провайдера и базы данных, он может с успехом использоваться при работе с Access MSDE или, например, с Microsoft SQL Server. Но, главная суть примера в том, чтобы показать недокументированную возможность программной работы с хранимой процедурой в 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 с двумя элементами успешно создана. Однако это не совсем так. Прежде всего, заметьте, что никакие запросы в базе данных Access не появляются. Коллекция процедур действительно создается, но в ней присутствует только один элемент. При добавлении нового элемента он записывается на место ранее существовавшего. Так что свойство 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
В результате работы этой процедуры на печать были выданы все книги, написанные Пушкиным, записи о которых хранились в базе данных.
End Sub
Сама процедура и то, как она выполняется, нуждается в подробных комментариях:
Поскольку целью работы, которую я поставил перед собой, являлось создание двух хранимых процедур, то в процедуре создаются два объекта Command. Свойство CommandText этих объектов позволяет задать текст команд, представляющих в данном случае SQL-запросы. Второй из этих запросов представляет запрос с параметром. Успешно выполняется метод Append коллекции Procedures, которому в качестве одного из параметров передаются созданные объекты Command. По завершении работы процедуры создается впечатление, что коллекция Procedures с двумя элементами успешно создана. Однако это не совсем так. Прежде всего, заметьте, что никакие запросы в базе данных Access не появляются. Коллекция процедур действительно создается, но в ней присутствует только один элемент. При добавлении нового элемента он записывается на место ранее существовавшего. Так что свойство 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
В результате работы этой процедуры на печать были выданы все книги, написанные Пушкиным, записи о которых хранились в базе данных.
Построение обработчиков событий и обработка ошибок
Давайте разберемся с деталями построения обработчиков событий объектов ADO. Прежде всего, рассмотрим, как они создаются, в какой модуль проекта их следует поместить. Хотя объекты Recordset и Connection обладают событиями, но появляются они как объекты без событий. Необходимо предпринять обычные для VBA в таких ситуациях действия, чтобы создать объекты с событиями и написать процедуры, обрабатывающие события, - обработчики событий. В первом томе и других книгах серии "Офисное программирование" я подробно рассказывал о том, как это делается. Напомню, что для решения этой задачи нужно выполнить три шага:
Создать на 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, одно из которых открывается перед выполнением этого оператора, а второе в обработчике события в момент выполнения оператора:
![](image/6-1.jpg)
Рис. 6.1. Окна, открываемые перед изменением поля и в момент изменения
Соответствующие сообщения появляются и при создании новой записи, причем столько раз, сколько полей имеет запись.
Важную роль в этой процедуре играет и оператор On Error Resume Next. Всякий раз, когда в обработчике события изменяется значение переменной adStatus, возникнет ошибка при выполнении оператора, изменяющего значение поля и послужившего причиной возникновения события. Оператор OnError позволяет в этом случае обойти выполнение оператора и, тем самым, избежать изменения значения, что и хотелось. Взгляните, как выглядит окно, уведомляющее об ошибке, появляющееся, если в процедуре закомментировать оператор On Error:
![](image/6-2.jpg)
Рис. 6.2. Окно, уведомляющее о возникновении ошибки при изменении значения
Хочу теперь рассмотреть пример построения обработчика события, возникающего после завершения операции. Пример хочу построить такой, чтобы при выполнении операции Провайдер столкнулся с трудностями и сформировал объекты Error, а обработчик события соответственно обрабатывал эти ошибки. Но прежде чем перейти к рассмотрению этого примера, есть смысл более подробно познакомиться с объектом Error - его свойствами и методами.
Первым делом я включил в обработчике события 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, одно из которых открывается перед выполнением этого оператора, а второе в обработчике события в момент выполнения оператора:
![](image/6-1.jpg)
Рис. 6.1. Окна, открываемые перед изменением поля и в момент изменения
Соответствующие сообщения появляются и при создании новой записи, причем столько раз, сколько полей имеет запись.
Важную роль в этой процедуре играет и оператор On Error Resume Next. Всякий раз, когда в обработчике события изменяется значение переменной adStatus, возникнет ошибка при выполнении оператора, изменяющего значение поля и послужившего причиной возникновения события. Оператор OnError позволяет в этом случае обойти выполнение оператора и, тем самым, избежать изменения значения, что и хотелось. Взгляните, как выглядит окно, уведомляющее об ошибке, появляющееся, если в процедуре закомментировать оператор On Error:
![](image/6-2.jpg)
Рис. 6.2. Окно, уведомляющее о возникновении ошибки при изменении значения
Хочу теперь рассмотреть пример построения обработчика события, возникающего после завершения операции. Пример хочу построить такой, чтобы при выполнении операции Провайдер столкнулся с трудностями и сформировал объекты Error, а обработчик события соответственно обрабатывал эти ошибки. Но прежде чем перейти к рассмотрению этого примера, есть смысл более подробно познакомиться с объектом Error - его свойствами и методами.
Пример программной работы в Access с пользователями и группами
База данных 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
Прежде всего, хочу обратить внимание на главную особенность работы с объектами User, Group и их коллекциями, когда речь идет о Провайдере 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:
![](image/6-11.jpg)
Рис. 6.11. Результаты запроса к системной базе данных Access
![](image/6-12.jpg)
Рис. 6.12. Показ прав доступа к элементам базы данных Access
Конечно, все, что я сделал программно, можно сделать и руками, выбрав пункт меню "Сервис | Защита" и открыв соответствующее окно, например, окно разрешений, показанное на рис. 6.12.
'Группы 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
Прежде всего, хочу обратить внимание на главную особенность работы с объектами User, Group и их коллекциями, когда речь идет о Провайдере 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:
![](image/6-11.jpg)
Рис. 6.11. Результаты запроса к системной базе данных Access
![](image/6-12.jpg)
Рис. 6.12. Показ прав доступа к элементам базы данных Access
Конечно, все, что я сделал программно, можно сделать и руками, выбрав пункт меню "Сервис | Защита" и открыв соответствующее окно, например, окно разрешений, показанное на рис. 6.12.
Пример работы с объектами ADO при создании Web-документов
Последний пример в этой главе я хочу посвятить хотя бы беглому знакомству использования объектов ADO в сценариях, написанных на VBScript. Я рассмотрю пример, представляющий фрагмент создания Web-узла. Создаваемый узел является активным, он может реагировать на действия пользователя, используя для этих целей технологию ASP(Active Server Page) страниц. Напомню, эти страницы содержат сценарии, которые собственно и определяют реакцию на действия пользователя. Особенностью ASP-страниц является то, что код сценариев выполняется на сервере и пользователю пересылается статическая страница, содержащая не код, а результат его выполнения.
В рассматриваемом фрагменте рассматривается типичная для активных Web-узлов ситуация, когда пользователь заполняет некоторую форму и отсылает ее на сервер. ASP-страница обрабатывает данные формы, сохраняет их в базе данных, добавляя новые записи в таблицы, и формирует новую страницу, пересылаемую пользователю. Вот как выглядит страница узла, на которой пользователь заполняет форму:
![](image/6-13sm.jpg)
увеличить изображение
Рис. 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">BiVANT </font></b></td> </tr> <tr> <td width="100%" bgcolor="#FFFF00" height="19"> <font color="#0000FF"><b><a href="index.htm">BiVANT</a> <a href="книги.htm">Книги</a> <a href="статьи.htm">Статьи</a> <a href="учебники.htm">Учебники</a> <a href="обучение.htm">Обучение</a> <a href="чайничек.htm">Чайничек</a> <a href="Дело%20отца/дело_отца.htm">Дело_Отца</a> <a href="онас.htm">О нас</a> <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>
Приведу теперь комментарии к этому тексту:
Текст написан на HTML и представляет содержание ASP-страницы, в задачу которой входит обработать посылаемую пользователем форму. Большая часть текста задает сценарий, написанный на VBScript, и именно эту часть я и буду комментировать. Для удобства восприятия код сценария подсвечен. Язык VBScript является нетипизированным языком, все переменные принадлежат к одному универсальному типу Variant, поэтому для переменных, соответствующих полям формы не задан тип.Объект ASP Request позволяет передать значения из полей формы в переменные VBScript.Затем начинается работа с объектами ADO, которые создаются на сервере методом CreateObject объекта Server.Вся остальная часть работы с объектами ADO - создание соединения с базой данных, формирование команды, набора записей, добавление новой записи к объекту Recordset и обновление базы данных, выполняется на VBScript совершенно аналогично тому, как это делалось на VBA. В результате выполнения этого сценария база данных действительно изменяется и данные, посланные в форме, будут сохранены в соответствующей таблице базы данных.
На этом я закончу разговор о компонентах ADO, представляющих универсальный способ взаимодействия с самыми разными источниками данных.
<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>
Приведу теперь комментарии к этому тексту:
Текст написан на HTML и представляет содержание ASP-страницы, в задачу которой входит обработать посылаемую пользователем форму. Большая часть текста задает сценарий, написанный на VBScript, и именно эту часть я и буду комментировать. Для удобства восприятия код сценария подсвечен. Язык VBScript является нетипизированным языком, все переменные принадлежат к одному универсальному типу Variant, поэтому для переменных, соответствующих полям формы не задан тип.Объект ASP Request позволяет передать значения из полей формы в переменные VBScript.Затем начинается работа с объектами ADO, которые создаются на сервере методом CreateObject объекта Server.Вся остальная часть работы с объектами ADO - создание соединения с базой данных, формирование команды, набора записей, добавление новой записи к объекту Recordset и обновление базы данных, выполняется на VBScript совершенно аналогично тому, как это делалось на VBA. В результате выполнения этого сценария база данных действительно изменяется и данные, посланные в форме, будут сохранены в соответствующей таблице базы данных.
На этом я закончу разговор о компонентах ADO, представляющих универсальный способ взаимодействия с самыми разными источниками данных.
![](../../../../img/empty.gif)
![](../../../../img/empty.gif)
![]() | © 2003-2007 INTUIT.ru. Все права защищены. |
Провайдер Internet Publishing
Полное имя этого Провайдера - 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 Jet
Этот Провайдер позволяет получить доступ к 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 Провайдер формирует динамически.
Провайдер ODBC
Полное имя этого Провайдера - 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 для тестовой базы данных.
![](image/6-5.jpg)
Рис. 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
Хочу обратить внимание на два момента:
В предыдущих примерах я работал с базой данных Access, используя Провайдер Microsoft.jet.oledb.4.0, специально разработанный для этой базы. Сейчас же для работы с этой же базой данных я использую общий Провайдер, позволяющий работать с любыми базами, допускающими интерфейс ODBC. База данных Access, конечно же, позволяет такой способ работы. Однако время работы зависит от выбора Провайдера. Этот пример позволяет сравнить два варианта, в каждом из которых устанавливается связь с одним из выше упомянутых Провайдеров. Соответствующие операторы подсвечены в тексте процедуры. Поочередно комментируя операторы, задающие вариант соединения, я запускал процедуру на выполнение и получал время ее выполнения для того или иного Провайдера. Приведу временные характеристики, полученные при двух запусках этой процедуры:
Время работы с таблицей: 5,878906 Время работы с таблицей: 0,0390625
Заметьте, время работы с ODBC Провайдером для базы данных Access в данном случае существенно больше, - на два порядка, чем время работы с "родным" Провайдером.
При работе с ODBC Провайдером, обратите внимание, помимо DSN я задал аргумент DATABASE, указывающий путь к базе данных. Эта информация является дублирующей, поскольку реальный путь к базе данных задается в момент определения DSN. Тем не менее, это полезно делать, чтобы явно указать, с какой базой данных пользователь намеревается работать.
Провайдер ODBC добавляет в коллекцию Properties объектов Connection, Command, Recordset ряд свойств, часть из которых является специфической для данного Провайдера. Некоторые из этих свойств добавляются к не открытым объектам, другие - при открытии объектов. Для всех этих свойств, доступных в ADO, есть аналог в модели OLE DB. Перечисление всех этих свойств заняло бы слишком много места. Чтобы дать некоторое представление, укажу несколько свойств объекта Connection:
Accesible Tables - булево свойство, указывающее, имеет ли пользователь разрешение на выполнение запросов (SQL-операторов) над таблицами базы данных.File Usage - указывает, как драйвер воспринимает источник данных, - как файл или как каталог.Special Characters - указывает, какие символы имеют специальный смысл для ODBC драйвера.Stored Procedures - определяет доступность использования хранимых процедур.
Провайдер Oracle
Полное имя этого Провайдера - 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 Провайдер формирует динамически.
Этот Провайдер поддерживает работу только со статическим курсором.
Провайдер SQL Server
Полное имя этого Провайдера - 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 Провайдер формирует динамически.
Провайдеры
Клиентское приложение, использующее объекты ADO, получает необходимые ему данные, обращаясь к Провайдеру. Провайдер или Поставщик данных - это приложение, обеспечивающее интерфейс между конкретным источником данных и клиентским приложением. Провайдер способен корректно транслировать вызовы методов ADO в запросы к источнику данных, выполнять эти запросы и передавать результаты клиентскому приложению. Несмотря на многообразие источников данных, многие из них имеют единый интерфейс запросов, что позволяет иметь одного Провайдера на все источники данных, экспонирующие единый интерфейс запросов. Конечно же, для уникальных источников данных может понадобиться разработка собственного Провайдера. Но для наиболее известных источников данных Провайдеры уже разработаны как Microsoft, так и другими фирмами, поддерживающими работу с источниками данных. Рассмотрим особенности ряда стандартных Провайдеров.
Сравнение объектных моделей ADO и DAO
В объектной модели DAO есть корневой объект - DBEngine, определяющий машину баз данных. В этот объект вложена коллекция объектов Workspaces, каждый элемент которой - объект Workspace - задает сеанс работы, являясь неким эквивалентом объекта Session в модели OLE DB. В модели ADO сеанс работы связывается с объектом Connection, задающим соединение с базой данных. Заметьте, модель ADO во многом упрощена в сравнении с моделью DAO, - в ней нет даже такого объекта как объект Database, являющегося одним из основных объектов в модели DAO.
Конечно, главное достижение в модели ADO связано с возможностью работы с разнообразными источниками данных и введением такого понятия как Провайдер. В модели DAO, в которой можно работать только с двумя Провайдерами, отсутствие этого понятия и универсального подхода, характерного для ADO, привело к существенному осложнению самой модели. Модель DAO позволяет в течение одной сессии, или, другими словами, в рабочем пространстве работать либо с источником данных Microsoft Jet, либо с ODBC - источниками данных. С объектной точки зрения это означает, что в объект WorkSpace, задающий рабочее пространство, вкладываются две достаточно разные совокупности объектов. Я ограничусь рассмотрением лишь одной половинки этой модели и рассмотрю модель DAO, связанную с источниками данных Microsoft Jet. Взгляните, как выглядит объектная модель DAO для этого случая:
![](image/6-6.jpg)
Рис. 6.6. Объектная модель DAO для рабочей области Microsoft Jet
Обратите внимание, чтобы не усложнять схему, на нижних уровнях иерархии ряд объектов опущен. Вот как следует дополнить эту схему:
Объект Container содержит коллекцию Documents с элементами Document,Объект QueryDef содержит коллекцию Parameters с элементами Parameter,Объекты OueryDef, Recordset, Relation, TableDef содержат коллекцию Fields c элементами Field.
Я уже говорил, что объектная модель DAO намного сложнее, чем модель ADO. Чтобы в полной мере реализовать функциональность, обеспечиваемую объектами DAO, представленными на рис. 6.6, в модели АДО потребуется использовать три группы объектов и, соответственно, подключить три различные библиотеки объектов:
Объекты ADO, находящиеся в библиотеке ADODB. Их подробному описанию посвящена предыдущая и эта глава. Объекты этой библиотеки позволяют манипулировать с данными базы данных Access (Microsoft Jet). Объект Connection позволяет связаться с базой данных и заменяет, в определенной степени, объекты Workspace и Database. Объект Command, позволяющий выполнять SQL-операторы, заменяет объект QueryDef, который выполняет эти действия в модели DAO. Наконец, объект Recordset присутствует в обеих моделях, выполняя, в общем, аналогичные действия.Объекты из библиотеки ADOX позволяют работать с метаданными. Эту библиотеку следует подключать, если соответствующие объекты DAO - QueryDef, TableDef используются для этих целей, создавая или модифицируя стандартные запросы и таблицы базы данных. Объекты User, Group и их коллекции, присутствующие в моделях DAO и ADOX, выполняют аналогичную работу по обеспечению безопасности и прав доступа пользователей к данным.Еще одну библиотеку объектов - Microsoft Jet and Replication Objects (JRO) следует подключать, если объекты DAO используются для создания реплик (копий базы данных) и их синхронизации. Ранее, рассматривая объекты ADO, я не упоминал эту библиотеку, поскольку в отличие от остальных объектов ADO, объекты этой библиотеки не носят общий характер, - они могут использоваться только при работе с одним источником данных - Microsoft Jet. Основным объектом модели JRO является объект Replica, задающий репликацию базы данных. Свойства и методы этого объекта используются для создания новой репликации, получения и модификации свойств уже существующих реплик и синхронизации реплик. В модели DAO эти функции выполняет объект Database. Еще один объект JetEngine из модели JRO позволяет выполнять операции по сжатию базы данных и обновлению данных, хранимых в кэш-памяти.
Я не буду останавливаться на деталях сходства и различия в моделях ADO и DAO. Отмечу еще только два момента:
В модели DAO транзакции связываются с сеансом работы, роль которого играет объект Workspace. Но поскольку в этот объект вложена коллекция объектов Databases, то в транзакции участвуют все открытые базы данных, запоминаются все изменения и при откате восстанавливается состояние всех баз данных. В модели ADO каждая транзакция связана только со своей базой данных, открытой заданным соединением.Модель ADO гораздо компактнее модели DAO. Пожалуй, она проще для понимания, что тоже имеет немаловажное значение. В ряде случаев она требует и меньших ресурсов, например, когда требуется только манипулирование данными, вместо подключения многофункциональной и более объемной библиотеки объектов DAO можно подключить более компактную библиотеку объектов ADO. Главный выигрыш в использовании ADO, конечно же, состоит в универсальном характере объектов этой библиотеки, позволяющих работать с разнообразными источниками данных. Эти объекты можно использовать не только при работе с языками VB и VBA, но и в языке сценариев - VBScript, что позволяет создавать, как обычные приложения, так и Web-приложения, в которых используются источники данных. Один пример использования объектов ADO для создания Web-документа будет приведен в конце этой главы.
Свойства и методы коллекции Errors
У коллекции два свойства:
Count - возвращает число элементов коллекции,Item - свойство по умолчанию позволяет по индексу получить необходимый элемент коллекции - объект Error.
У коллекции два метода:
Clear - позволяет принудительно очистить содержимое коллекции,Refresh - обновляет коллекцию.
Свойства и методы объекта Parameter
Поскольку все свойства и методы объекта 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 для получения нужной информации от Провайдера и тем самым снизить нагрузку на передачу данных.
Свойства объекта Catalog
У объекта Catalog 6 свойств: ActiveConnection, Tables, Procedures, Views, Groups, Users. Пять последних свойств возвращают одноименные коллекции. А о первом свойстве поговорим чуть подробнее.
Свойства объекта 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.
Свойства объекта Error
У объекта 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, вызвавшее ошибку.
Свойства объекта Field
Объект 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. Возвращает коллекцию свойств поля.
Свойства объекта Stream
Объект 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.
Свойства объекта 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. Возвращает коллекцию свойств, стандартную для многих объектов ADO. Property Type As String. Свойство задает тип, но не, как обычно, константой, а строкой, значения которой могут быть, например, "TABLE", "SYSTEM TABLE" или "GLOBAL TEMPORARY".
Таким образом, для создания таблицы или ее модификации приходится, главным образом, работать с объектами Column, Key, Index и их коллекциями. Рассмотрим устройство этих объектов.
Свойство ActiveConnection
Свойство имеет статус "чтение/запись". Синтаксически определяется следующим образом:
Property ActiveConnection As Variant
Позволяет установить ссылку на объект Connection или задать строку, определяющую соединение. Возвращает ссылку на активный объект Connection.