Опубликован: 13.09.2006 | Уровень: для всех | Доступ: свободно
Лекция 6:

Объекты ADO (продолжение)

В предыдущей главе было дано общее представление об объектах ADO и подробно рассмотрены свойства, методы и события трех основных объектов - Connection, Command, Recordset. Но я хочу продолжить разговор и дать представление не только о главных героях, но и всех остальных объектах этой модели. Но прежде чем рассматривать новые объекты, давайте разберемся в таком важном вопросе, как обрабатываются события, возникающие с объектами ADO, и о том, как справляться с ошибками, возникающими в процессе работы с этими объектами. Я напомню, что событиями обладают лишь два объекта - Recordset и Connection.

Построение обработчиков событий и обработка ошибок

Давайте разберемся с деталями построения обработчиков событий объектов ADO. Прежде всего, рассмотрим, как они создаются, в какой модуль проекта их следует поместить. Хотя объекты Recordset и Connection обладают событиями, но появляются они как объекты без событий. Необходимо предпринять обычные для VBA в таких ситуациях действия, чтобы создать объекты с событиями и написать процедуры, обрабатывающие события, - обработчики событий. В первом томе и других книгах серии "Офисное программирование" я подробно рассказывал о том, как это делается. Напомню, что для решения этой задачи нужно выполнить три шага:

  1. Создать на VBA собственный класс с именем, например MyEventsADO, в котором описать вложенные объекты - Connection WithEvents и Recordset WithEvents.
  2. Создать обработчики нужных событий для данных объектов в классе MyEventsADO. Заметьте, после объявления переменных With Events в этом классе появится возможность написания обработчиков событий, следуя обычной технологии.
  3. В подходящем месте, например в уже существующем модуле TestingADO, создать экземпляры класса MyEventsADO и связать их с глобальными или локальными объектами Rst1 и Con1, задающими набор записей и соединение.

Чтобы сделать дальнейшее описание конкретным, давайте рассмотрим создание обработчика события WillChangeField объекта Recordset, которое, напомню, появляется при попытках изменить значение поля в записи набора, но перед тем, как это изменение реально произойдет. Вот как выглядит модуль класса MyEventsADO, содержащий обработчик этого события:

'Класс MyEventsADO, определяющий события ADO
Public WithEvents EvRst As ADODB.Recordset
Public WithEvents EvCon As ADODB.Connection

Private Sub EvRst_WillChangeField(ByVal cFields As Long, _
 ByVal Fields As Variant, adStatus As ADODB.EventStatusEnum, _
 ByVal pRecordset As ADODB.Recordset)
 'Печать параметров обработчика события
 MsgBox "Номер поля = " & cFields & vbCrLf & _
 "Значение поля = " & Fields(cField) & vbCrLf & _
 "Позиция записи в наборе = " & _
 pRecordset.AbsolutePosition & vbCrLf & _
 "Статус выполняемой операции = " & adStatus
 'Изменение статуса приведет к появлению ошибки
 'при попытке изменить значение поля, обработка которой
 'позволит отменить операцию.
 If IsNumeric(Fields(cField)) Then
 If Fields(cField) > 75 Then adStatus = adStatusCancel
 End If
End Sub

Первым делом я включил в обработчике события WillChangeField печать всех его параметров на входе. Обратите внимание, обработчику события передается указатель на сам объект Recordset, поэтому доступна практически любая информация, связанная с этим объектом. Я, например, использовал его, чтобы вывести на печать позицию записи в наборе, поля которой обновляются. Во второй части обработчика я изменяю значение статуса при выполнении некоторого условия на обновляемое поле. Делаю это для того чтобы реализовать возможность отмены изменения значения, когда в результате анализа, проведенного в обработчике, принимается решение о нежелательности выполнения изменений. Заметьте, для этого свойству статус, которое имело значение adStatusOk, уведомляющее о том, что изменения возможны, я присваиваю значение adStausCancel. В этом случае при выходе из обработчика возникнет ошибка в операторе, производящем изменения. Обработав соответствующим образом эту ошибку, я смогу выполнить поставленную задачу.

Давайте теперь рассмотрим процедуру, в которой происходит обновление полей записей набора, чьи действия будут вызывать появление события WillChangeField. Эта процедура изменяет цену у некоторых книг из таблицы "Книги" нашей тестовой базы данных и добавляет новую запись в эту таблицу. При всех этих изменениях начнут работать события:

Public Sub CreateEvents()
	'Объект с событиями
	Dim imEvRst As New MyEventsADO
	 'добавление и изменение записей	базы данных
	Dim recExist As Boolean
	' Связывание объекта с событиями
	Set imEvRst.EvRst = Rst1
	'Создать соединение
	CreateConnection
	'Создать команду
	'задание свойств объекта Command
	Cmd1.ActiveConnection = Con1
	Cmd1.CommandText = "Select * From [Книги]"
	Cmd1.CommandType = adCmdText
	'Открытие обновляемого объекта Recordset
	With Rst1
		.Open Source:=Cmd1, CursorType:=adOpenDynamic, _
			LockType:=adLockOptimistic
			'Изменение записей
		recExist = False
		.MoveFirst
		Do While Not .EOF
			'Обработка текущей записи
			On Error Resume Next
			If !Название = "Офисное программирование" Then recExist = True
			If ![Год издания] < 2000 And !Цена < 100 Then
				MsgBox "Старая цена =" & Str(!Цена)
				!Цена = !Цена * 2
				.Update
			End If
			.MoveNext
		Loop
		If Not recExist Then
			.AddNew
			!Автор = "Владимир Биллиг"
			!Название = "Офисное программмирование"
			![Год издания] = 2001
			![Число страниц] = 599
			!Цена = 150
			.Update
		End If
	End With
End Sub

Обратите внимание, первым делом я объявляю объект imEvRst созданного класса MyEventsADO и связываю его с глобальным объектом Rst1, после чего последний начнет реагировать на события. Сколько раз будет появляться событие WillChangeField? Очевидно, при каждом выполнении оператора !Цена = !Цена * 2, изменяющего значение соответствующего поля записи. Вот как выглядят окошки функции MsgBox, одно из которых открывается перед выполнением этого оператора, а второе в обработчике события в момент выполнения оператора:

Окна, открываемые перед изменением поля и в момент изменения

Рис. 6.1. Окна, открываемые перед изменением поля и в момент изменения

Соответствующие сообщения появляются и при создании новой записи, причем столько раз, сколько полей имеет запись.

Важную роль в этой процедуре играет и оператор On Error Resume Next. Всякий раз, когда в обработчике события изменяется значение переменной adStatus, возникнет ошибка при выполнении оператора, изменяющего значение поля и послужившего причиной возникновения события. Оператор OnError позволяет в этом случае обойти выполнение оператора и, тем самым, избежать изменения значения, что и хотелось. Взгляните, как выглядит окно, уведомляющее об ошибке, появляющееся, если в процедуре закомментировать оператор On Error:

Окно, уведомляющее о возникновении ошибки при изменении значения

Рис. 6.2. Окно, уведомляющее о возникновении ошибки при изменении значения

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

Коллекция Errors и объекты Error

Напомню, что элементы коллекции Errors появляются при выполнении очередной команды, когда Провайдер сталкивается с определенными трудностями и не может нормальным образом завершить выполнение команды. Коллекция Errors всегда связана только с одной командой, - при первой ошибке в новой команде происходит чистка старого содержимого коллекции. Свойство Errors объекта Connection возвращает эту коллекцию. Добавлять программно элементы в коллекцию невозможно, это делается автоматически, когда Провайдер обнаруживает новую ошибку.

Свойства и методы коллекции Errors

У коллекции два свойства:

  • Count - возвращает число элементов коллекции,
  • Item - свойство по умолчанию позволяет по индексу получить необходимый элемент коллекции - объект Error.

У коллекции два метода:

  • Clear - позволяет принудительно очистить содержимое коллекции,
  • Refresh - обновляет коллекцию.
Свойства объекта 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, вызвавшее ошибку.
Ольга Гафарова
Ольга Гафарова
Непонятен ход решения задачи
Серегй Лушников
Серегй Лушников
Может ли объект Recordset быть потомком объекта Record?
Геннадий Шестаков
Геннадий Шестаков
Беларусь, Орша
Светлана Ведяева
Светлана Ведяева
Россия, Саратов