том1 а л ь м а н а х программиста Тематический сборник материалов MSDN Library и MSDN Magazine Microsoft ADO.NET ...
-- [ Страница 6 ] --Модификация приложения для отображения данных в Web Рис. 13. Метаданные для набора записей '- Получаем метаданные для набора, записей Wltft ofts ReDira vRecordsetM@taData(0 To 2) vRecordsetHetaData(geBl_RecordsetProperty_fiecordCount) _ = oFts.RecordComt vBecQr6settetaDataCgeBL_ReGor(JsetProperty_PageCoimt) _ = PageCount(oRs.RecordCount, IPageSize) vRecorusetM8taData(seBL-RecordsetProperty_PageHui8ber) = IPageNumber End With vRsCgeEU-JlSArray.ftecordsetMetaBata) * vRecordsetHetaSata GetRecorcfs * vfls Главный массив (vBs) Двухмерный массив данных Массивы метаданных для кален Массивы метаданных Мзеснвы мега^аннык для пелкй Метаданные для набора записей Массив vRs Рис. 14.
Универсальные ASP-страницы Теперь вы можете скомпилировать ActiveX-сервер и поместить его в MTS пакет. Для запуска приложения все готово. Но сначала обсудим универ сальные ASP-страницы, обеспечивающие чтение данных и форматирова ние Web-страницы. Эти страницы разбиты на три части, каждая из кото рых включается в главный файл (в нашем случае Ч во View_Authors.asp).
Первая страница во View_Authors.asp называется GenericDisplayToolPa gelofS.asp. Она задает константы и переменные для ASP-приложения.
Кроме того, определяет порядок сортировки и отображения данных и со храняет, информацию в локальных переменных ASP, которые потом пере Доступ к данным из приложений даются ActiveX-серверу. После включения этого файла страница View_ Authors.asp получает массив данных через ActiveX-сервер.
Затем включается вторая страница, GenericDisplayToolPage2of3.asp. Она извлекает данные из массива (рис. 15). Эта страница получает массив дан ных и метаданные для полей и набора записей. Далее, чтобы упростить восприятие исходного кода, я помещаю метаданные для набора записей в локальные переменные.
Рис. 15. Чтение данных '- *И-вние данных vQata * vRSArray(ge8lJi$Array,J)ata) vFieldHeta&ata - vRSArray(ge8L_f!SArray_FieldltetaData) vRSNetaData * vR8Array(geBl_RSArray_ReGordsetKetaQata) oft error resune next bDataExists = UBound(vData, 2) bDataExists = CBool(err 0) if Not bDataExists then IRecordCount л IPagedount Щ else IReeordCourvt * vRSHeta0ata(geBL_Reoord89tProperty_RecordCoufit) IPageCount = vRSHetaBata(geBL_8ecord8etProperty_PageGount) ICurrentPage * vfl^etaData{geBI,_RecortfsetProperty_PageNijfflber) end if on error goto if LerKlLongestFieldLenstt*} * 0 then '- Длина по умолчанию - 30 символов 1LongestFieldLength = end if %> Третья и последняя страница, включаемая во View_Authors.asp, называет ся GenericDisplayToolPageSofS.asp. Она генерирует HTML-код, отобража ющий данные на Web-странице. Несколько нестандартных (custom) сти лей из каскадных таблиц стилей позволяют слегка украсить страницу.
Модификация приложения для отображения данных в Web Заключение Как вы видели, мое решение позволяет легко отображать любой набор дан ных на Web-странице и предоставляет простые в использовании механиз мы сортировки и разбиения на страницы. Все, что требуется для исполь зования этого кода, Ч написать код ActiveX-сервера для бизнес-объектов и настроить главные страницы.
Есть несколько способов улучшить этот код. Например, можно добавить фильтры, отбрасывающие определенные записи из набора данных. Или ввести механизм поиска, который выдает первую запись, где содержится искомое значение. Я предусмотрел возможность перехода на другую стра ницу с передачей ей первичного ключа записи, выбранной пользователем.
Эта функциональность позволяет открывать другое окно для редактиро вания данных. Но мой код Ч не более чем базис.
Полный исходный код, включая хранимую процедуру, можно скачать по ссылке, которую я приводил в начале статьи. Вам понадобится лишь база данных Pubs, поставляемая с SQL Server, и IIS. Разумеется, код можно перенастроить на другую базу данных.
Джонни Папа (Johnny Papa) Ч вице-президент компании MJM Investigations по разработке программного обеспечения (Роли, штат Северная Каролина), автор нескольких книг по ADO, XML и SQL Server. Часто выступает на конфе ренциях, в том числе на VSLive. С мим можно связаться по адресу datapoints@lancelotweb.com.
Джонни Папа Доступ к данным ADO и XML: создание уровня доступа к данным на основе компонента DataManager По той или иной причине информацию часто приходится представлять в разных форматах. И было бы неплохо создать единый компонент доступа к данным, который инкапсулирует все механизмы управления данными и возвращает информацию в нескольких форматах. В статье показано, как добиться этого, создав СОМ+-компонент, который применим в любом Windows-приложении и позволяет получать данные в различных форматах, в том числе как ADO-набор записей, двухмерный массив или XML.
Ваш босс говорит: Приложение должно работать с XML. Конечно, XML Ч хороший формат данных, но как быть с форматами, уже применяемыми в компании? В ваших бизнес-приложениях может быть уровень доступа к данным, который отвечает за чтение/запись информации из базы данных и ее передачу на уровень бизнес-сервисов. В каком формате эти данные?
Если вы работаете с ADO-наборами записей, значит, ваш бизнес-компо нент опирается на объектную библиотеку ADOR, использование которой может быть нежелательным. Или же вы сделали ставки на ASP-код, при нимающий данные в виде массива, что обеспечивает независимость биз нес-компонента от ADO и ускоряет навигацию по данным. Но, возможно, вы хотите использовать и XML, чтобы упростить передачу данных на дру гие платформы. Таким образом, по той или иной причине информацию часто приходится представлять в разных форматах. И было бы неплохо создать единый компонент доступа к данным, который инкапсулирует все * Публиковалось в MSDN Magazine. 2001. №8 (август). Ч Прим. изд.
ADO и XML;
создание уровня доступа к данным механизмы управления данными и возвращает информацию в нескольких форматах.
Я покажу, как этого добиться, создав компонент СОМ+, который приме ним в любом Windows-приложении и позволяет получать данные в раз личных форматах, в том числе как ADO-набор записей, двухмерный мас сив или XML. Компонент DataManager, о котором и пойдет речь в этой статье, можно использовать из нескольких бизнес-компонентов. Сначала я продемонстрирую, как DataManager получает данные. Затем опишу ос новные блоки кода, чтобы вы поняли, на чем он держится. Компонент DataManager, весь исходный код и интерфейс для тестирования можно скачать по ссылке (в раз деле за август). Этот код был написан в среде Windows NT 4.0 и поэтому использует Microsoft Transaction Server (MTS) и Shared Property Manager (SPM). Мой код работоспособен и в Windows 2000, но тогда эти два ком понента заменяются службами СОМ+.
Применение DataManager Начнем с простого приложения, которое я создал в Visual Basic специаль но для тестирования компонента DataManager. Оно предоставляет интер фейс для просмотра данных в трех форматах. Если данные представлены в виде ADO-набора записей или массива, они отображаются в таблице.
Данные в формате XML загружаются в элемент управления WebBrowser.
Кроме того, приложение содержит крошечный компонент бизнес-уровня, размещаемый между интерфейсом и компонентом доступа к данным. В конце концов, когда данные отображаются в привычном виде, управлять ими гораздо легче.
Запустив приложение, вы увидите поле со списком (combobox), где мож но выбрать представление отображаемых данных. Данные выводятся в сетке (grid) на первой вкладке или в элементе управления WebBrowser на второй вкладке. (Все элементы управления этого приложения поставля ются с Visual Basic, так что дополнительные файлы вам не понадобятся.) Сообщения об ошибках показываются в текстовом поле на третьей вклад ке. Рис. 1 демонстрирует информацию из базы данных Pubs (SQL Server), представленную в виде ADO-набора записей.
Чтобы получить данные из двухмерного массива выберите 2-D Array в списке и щелкните ОК. Вы увидите ту же информацию, что и на рис. 1.
Однако в отличие от рис. 1, на котором показаны заголовки (имена полей) для каждого столбца (ProductID, ProductName и т. д.), теперь эти заголов ки не отображаются. При передаче массива метаданные, содержащиеся в ADO-наборе записей, не предоставляются, и поэтому имена полей неизве стны.
Доступ к данным из приложений Рис. 1. SQL-данные в виде ADO-набора записей Вы могли бы усовершенствовать этот подход, добавив в массив метадан ные. И вообще, DataManager можно модифицировать так, чтобы он воз вращал данные в любом нужном вам формате. Я покажу, как это сделать, когда мы перейдем к исходному коду.
Если выбрать представление данных в формате XML, результат будет ана логичен тому, что вы видите на рис. 2. Заметьте;
данные в формате XML содержат элементы field, описывающие заголовки полей таблицы. Сами данные тоже содержатся в XML (они расположены за метаданными).
ъ Dai^Acreti I ifii I r;
st ( mi XML IbUi clield 1пйвк*"0" Narae="F NumeiricC3le-"255" Precisian--" 10" islcientity^True" }3rjLj!Jabie-:'False" Uptlatisbiw='False" FixedL^tigth--"True1 l.onc)8iiiary="Felse",- Type- = 202" Defined'iize^HD" создание уровня доступа н данным Теперь, когда вы знаете, в каком виде отображаются данные, рассмотрим сам DataManager. Внутреннее устройство DataManager Компонент DataManager включает открытый класс C_JDataManager. Его метод ExecuteSQL позволяет выполнить любой SQL-оператор. ExecuteSQL открывает соединение с соответствующей базой данных, выполняет SQL команду, форматирует данные и возвращает их вызвавшему приложению. Метод ExecuteSQL принимает четыре параметра: Х sSQL Ч SQL-команда, которую вы хотите выполнить; Х sINIAppName Ч имя бизнес-компонента, вызывающего DataManager; Х sINIFullPath Ч полный путь к этому бизнес-компоненту; Х iDataReturnType Ч формат, в котором следует возвращать данные. DataManager предполагает, что вызывающий бизнес-компонент предос тавляет INI-файл, содержащий специфичные для этого компонента пара метры базы данных. (Поскольку к одному компоненту DataManager могут обращаться несколько приложений, нужно же как-то сообщать Data Manager, какой набор параметров ему следует использовать.) В INI-файле указываются тип базы данных, имя ее сервера, имя самой базы данных и учетные данные для подключения (идентификатор и пароль пользовате ля) Ч фактически все элементы строки подключения в ADO. (Пример та кого INI-файла с именем TestBL.ini входит в набор для скачивания.) В параметрах sINIAppName и sINIFullPath передается имя INI-файла (без расширения.ini) и полный путь к нему, в параметре sSQL Ч исполняемая SQL-команда, а в параметре IDataReturnType Ч формат, в котором следу ет возвращать данные. Для упрощения выбора формата DataManager пре доставляет открытое перечислимое, члены которого можно передавать методу ExecuteSQL: Public Enum enumDataReturnType geDataReturnType_2DArray geDataReturnType_ADOR geDataReturnType_XML End Enum Установление соединения Метод ExecuteSQL запускает процедуру SetConnectionParameters (рис. 3), которая считывает INI-файл, получает параметры базы данных и присва ивает их локальной переменной-члену, чтобы потом можно было открыть соединение. 364 Доступ к данным из приложений Рис. 3. Процедура SetConnectkmParameters Private Sub SetConnectlonPararoeters(Optional sIHXAppName As Variant,. Optional slNIFullPath As Variant) With UKudtCormeetionPararaeters '- Источник данных.sDataSource * SetlNIValueCn.oError,.sINIConnectlonSection, "BataSouree", я.зШАррНаве, m.sIKlFullPath) '- Имя базы данных. sDatabaseNairte * GetI8IValua(m.oError,.sINIConnectiofiSeetion, "DatabaseName", m.sINIAppName, m.sINIFullPath) '- Провайдер базы данных.eDatatmseProvider = GInt(GetISIValue(ai,oError, _.sINIConftectionSection, "DatabaseProvider", _ ffl.slлIAppName, ra.sIHIFuUPath 'Х Идентификатор пользователя,sUserI& * GetIHIValue(m.oError,.sINICdnnectionSectiDn, "AppIP", н.зШАррлате, m.sINIFullPatft) '- Пароль.sPassword = GetINIValue(m.orror,.sINIConneGtionSection, "AppFassword", л.sINIAppNaiee, m.sINIFullPath) Функция GetlNIValue (рис. 4) сначала проверяет, не сохранен ли пара метр в MTS SPM. Если да, он считывается оттуда, и INI-файл не откры вается. Иначе GetlNIValue считывает параметр из INI-файла, закрывает файл, сохраняет параметр в SPM и передает его через возвращаемое зна чение. Применение SPM устраняет издержки, связанные с чтением INI файла при каждом вызове ExecuteSQL. Рис. 4. Функция GetlNIValue Public Function Set INIValue(оError As C.ErrorManager, ByVal sSection As String, SyVal sKey AS String, ByVal slNIAppName _ As String, Optional sINIFtillPatfi As Variant) As Variant Const sfft0C_NAME As String = isOW.NANE & ".GetlHIValue" On Error GoTo Err Const sPRpPHT_HANAGER As String ~. "HTxSpffl. SharsdPropert^roupManager, 1" Dim oINI As CJNIHanager Dim oSharedPropertyGroupManager As SharedPropertySroup%nager Dim oSharedPropertyGrotip As SharedPropertySroup Dim oSharedPropsrty As SharedProperty Dim bGroupAlreadyExists As Boolean Dim bPropertyAlreadyExists As Boolean см. след. стр. ADO и XML: создание уровня доступа к данным Рис. 4. Функция GetlNIValue (окончание.} Diai vtfalue As Variant "- Получаем группу свойств Set oSharedPropartySroupManager * ereateGb3eet(sPfi0PERTY_MAKASEfl) Set GSbaredPropertyGroup * _ o8haredFroperty6roupHanager,CFeatePropertyGroup(sINIAppNarae & "." & "IHIValues", LoeHSetGet, Process, bGrowAlreadyExists) Set QSharetJProperty * eStiaredPrGpertyGroup.CreateProperty _ (sSection & "," & sKey, bPropartyAlreadyExlsts) If bPropertyAlreaSyExists Then SetlNlValue * oSftaredProperty.Value Else Set oIHI - New ELININamger *- сяи в INI передан путь, заненявн им значение по умолчанию If HasValue{slлlFuXlPath) Then GlNl.FilePath * sINlFullPatft & "\" & slHXAppName & Mni" ::Х; . End If Walue = oINI. Value (sSection, sKey) oSftared?roperty. Value - Walue QetlNIValue = Walue End If '- Очистка Set oIHI => Nottiifig Set oSharedProperty = Nothing Sлt oSharedPropertyGroup = NotMRg Set oSharedProperty - Nothing Exit Ftmctlon Err; oError.Push Err, Err,Source, Error, Выполнение SQL-команды Как только подпрограмма SetConnectionParameters получает параметры базы данных, метод ExecuteSQL создает и открывает ADO-объект Connec tion. Затем, используя открытый объект Connection, метод создает и от крывает ADO-набор записей. Набор записей открывается как клиентс кий набор и сразу же отсоединяется от объекта Connection (рис. 5). В заключительной части кода на рис. 5 данные форматируются в соответ ствующую структуру. Если вызывающему приложению требуется ADO набор данных, то существующий на этом этапе набор просто передается приложению. В ином случае вызывается функция ConstructReturnData (рис. 6), которая возвращает форматированные данные. 366 Доступ к данным из приложений Рис. 5- Открытие и отсоединение набора записей *- Select или Insert для созданий отсоединенного набора записей S^t oRs = ttew ADQDB.Recordset With oRs '- Получаем отсоединенный набор записей вместо обычного, "- так как для него предоставляются все метаданные Set.ActiveConnection * oCn.Cursorlocation * adUseClient.CtirsorType = adDpenStatlc.LockType = adLockBatchOptimlstic '- Открываем набор записей.Open Source:=sSQL, Options:=adCmdText '- Отсоединяем его Set.ActiveConnection * Nothing btf With '- Возвращаем набор данных If ISataReturRType = geData8eturnType_ADOFt Then Set ExecuteSQl * oRs Else & ADO и XML: создание уровня доступа к данным Рис, 6. Функция ConstructReturnData Exit Funetion Err; ai.oError.pysh l-rr, Err.Source, Error, sPflQCJIAM la.oirror.Raiselast End Function Функция ConstructReturnData просто определяет необходимый формат данных и создает двухмерный массив методом GetRows набора записей или форматирует данные в XML через функцию BuildXML. Функция BuildXML вызывает метод BuildXML объекта C_XML, который формати рует ADO-набор записей в XML. Для этого он просматривает поля на бора записей, считывая метаданные, а затем перебирает строки и поля, считывая сами данные. Для создания XML метод использует объект MSXML.DOMDocument. Форматирование в XML Форматируя выходной XML, BuildXML сначала создает корневой тэг '- Создаем элемент XML-документа Set m.oXML.documentElement = m.oXML.createElementC'xil") Х- With Далее следует тэг с тэгом Доступ к данным из приложений Рис. 7. Создание атрибутов тэга ADO и XML: создание уровня доступа к данным Рис, 8. Создание элементов данных в XML (ок&тазшё) Х- With Ucp End With End With 8XHL - m.oXHL.xml Set ofis = Nothing. BulldXHl SXML Помните, что вы вправе изменять формат XML по своему усмотрению. Я выбрал простой формат с отделением метаданных от самих данных. Одна ко вы можете избавиться от тэга он позволяет определять любой формат возвращаемых данных! Заключение В итоге у вас появился компонент для доступа к данным, абсолютно неза висимый от вызывающих приложений. Вы можете использовать Data Manager в нескольких бизнес-компонентах, которые будут работать с раз ными серверами баз данных и получать данные в разных форматах. Чтобы поэкспериментировать с моим кодом, вам понадобится лишь Visual Basic и база данных Pubs из SQL Server. Разумеется, вы можете модифи цировать этот код под другие базы данных. Джонни Папа (Johnny Papa) Ч вице-президент компании MJM Investigations по разработке программного обеспечения (Роли, штат Северная Каролина), автор нескольких книг по ADO, XML и SQL Server. Часто выступает на конфе ренциях, в том числе на VSLive. С ним можно связаться по адресу datapoints@lancelotweb.com. 13- Марк Герлах Сбор информации от Web-сайтов и каталогов с применением Visual Basic.NET и ADO.NET Visual Basic.NET просто напичкан возможностями, которых не было в прежних версиях этого языка программирования. В частности, он поддер живает новую модель потоков (threading model), создание нестандартных классов (custom> Учитесь использовать эти возможности на примере приложения, извлекаю щего информацию с Web-страниц для последующего индексирования. В статье также обсуждаются доступ к базам данных, файловый ввод-вывод, расширение классов объектов и применение на формах свойств прозрачнос ти (transparency) и непрозрачности (opacity). Когда начинаешь работать с новым или с сильно изменившимся языком, какое-то время уходит на освоение новых интерфейса, синтаксиса, функ ций и других средств. Чтобы приобрести опыт в использовании Visual Basic.NET, я решил переписать приложение из каталога своей компании, а заодно расширить его функциональность и на практике продемонстри ровать некоторые новые языковые средства. В этой статье описывается переработанная версия робота Ч приложения, изначально написанного на Visual Basic 6.0. Исходное приложение собира ло необработанный HTML-текст из различных источников в Web, после * Публиковалось в MSDK Magazine/Русская Редакция. 2002. №4 (октябрь). Ч Прим. изд. Сбор информации от Web-сайтов и каталогов синтаксического разбора помещало информацию в набор таблиц базы дан ных и определяло, какая информация может пригодиться пользователю. Приложение следовало инструкциям, считываемым из файла robots.txt на сайте, сохраняло полный HTML-код сайта на локальном жестком диске и вело журнал любых ошибок и существенных изменений в структуре сай та. В новой программе, названной мной Spider (лпаук), я стремился сохранить как можно больше функциональности прежней версии и, кро ме того, хотел хорошенько проверить возможности Microsoft,NET Frame work. Заметьте, что я создавал Spider во второй бета-версии Visual Studio.NET, а тестировал уже в финальном выпуске Visual Studio. Хотя исходный продукт был неплохо продуман, я все же хотел дополнить его возможностями, которые трудно реализовать на Visual Basic 6.0. Одна из них Ч истинно многопоточный объект (multithreaded object), способ ный ползать по страницам независимо от родительского приложения (и от остальных потоков) и периодически сообщающий приложению инфор мацию о своем состоянии. Создание такого объекта Ч прекрасное приме нение.NET-моделей потоков. В этой статье мы обсудим лишь ключевую функциональность приложе ния, демонстрирующую приемы, специфичные для Visual Basic.NET: об ход страниц с применением многопоточности, управление через файл robots.txt, исключение файлов с неизвестными расширениями, обработка потоковой информации, передаваемой через Интернет, модификация пользовательского интерфейса (UI), протоколирование ошибок и опера ции с базами данных. Ясно, что в окончательной версии программы воз можностей будет куда больше. Кроме того, интерфейс и функции программы зависят от конкретного при менения робота. Так, модуль синтаксического анализа (parsing module) бесполезен, если от программы требуется лишь запись копии HTML-по тока на локальный диск. Если робот планируется использовать в много процессорной системе, введение лимита на пять потоков явно ограничит его возможности. Так что приложения подобного типа обычно приходится создавать в нескольких версиях, отвечающих разным (иногда противопо ложным) требованиям. Я решил предусмотреть максимум возможностей настройки, при этом поставив себе задачу обеспечить поддержку разнооб разных файловых форматов, HTML-типов и инструкторов синтаксичес кого анализа (parsing instructors). Кодирование приложения Я начал с того, что открыл Visual Studio.NET, создал новый проект Visual Basic на основе шаблона Windows Application, ввел название нового про Доступ к данным из приложений екта (Spider) и щелкнул ОК. Потом создал UI. Я решил заняться сначала им, чтобы четко понять, каким увидят робот мои пользователи; к тому же он послужит мне отправной точкой. Процесс создания формы не сильно отличался от такового в предыдущих версиях Visual Basic. Но, поскольку Visual Basic.NET показывает исход ный код, используемый им для построения элементов управления на фор ме (который прежде прятался в начале FRM-файла), я обнаружил, что теперь, скопировав исходный код формы FormA и вставив его в FormB, можно немедленно воссоздать элементы управления формы FormA на форме FormB. Visual Basic 6.0 тоже позволял переносить исходный код, но элементы управления надо было копировать отдельно. Создав формы, я добавил 'к ним элементы управления, придав главному окну приложения вид как на рис. 1, а окну Options Ч как на рис. 2. Рис. 1. Главное окно приложения Наконец, я добавил необходимые классы и модули. Щелкнув правой кнопкой мыши имя проекта в Solutions Explorer, я создал модуль modUtils и два класса; clsBoxOverride и clsSpider. Разделавшись со структурами можно было приступать к написанию кода. Сбор информации от Web-сайтов и каталогов QK Cancel Files d FromParse Files Included In Perse : 'fi Х*.; <,: Рис. 2. Окно Options Создание потоков Как я уже говорил, приложение было решено переделать в основном ради многопоточности, вот и займемся сю. Потоки создаются двумя главными компонентами: самим приложением и классом, предназначенным для по лучения страниц с Web-сайта. Общая архитектура Spider и взаимосвязи его компонентов показаны на рис. 3. clsSpider clsSpider clsSpider clsSpidet clsSpider лоток поток 1 поток 2 поток 3 ПОТОК Интернет Рис. 3. Модель потоков Spider Spider дает возможность пользователю выбирать, сколько активных пото ков будет порождено при обработке. В этом приложении число потоков ограничено пятью, поскольку вряд ли кто из читателей будет тестировать мою программу на четырехпроцессорной системе. Spider создает столько индивидуальных объектов clsSpider, сколько запрашивает пользователь, и следит за состоянием каждого потока. В свою очередь потоки извлекают HTML с заданных страниц (при необходимости проводя синтаксический анализ), записывают его в локальную базу данных и возвращают приложе 374 Доступ к данным из приложений нию новые ссылки, которые нужно обработать. В ходе работы каждый по ток периодически обновляет в главном приложении сведения о своем со стоянии через обработчик события eh Thread Status, который отражает на главной форме сведения о потоках и ход обработки больших файловых потоков (file streams), а также сообщает, какие файлы уже обработаны. По окончании обработки все потоки автоматически завершаются, поэтому самостоятельно вызывать метод Thread,Abort не требуется. Безопасность кода в многопоточной среде (thread-safe code)* крайне важ на для подобных приложений, иначе потоки перепутаются, и програм ма не то что будет работать медленнее однопоточной, а просто рухнет. Чтобы не допустить этого, команда разработчиков ввела в Visual Basic,NET поддержку блокировки SyncLock. Функция SyncLock принимает выражение, содержащее объект, который нужно блокировать. Для примера взгляните на код обработчика события ehFinished в frmMain (исходный код к этой статье можно скачать с msdn.microsoft.com/msdnmag/code02.asp в разделе за октябрь). В данном случае он заставляет систему проверить, можно ли получить блокировку на массив m_sPages (в нем хранится список еще не полученных страниц): SyncLock (m_sPages) ' Сюда вставляется код, имеющий дело с блокировкой SyncLock End SyncLock Поскольку все три обработчика событий в Spider срабатывают из-за вызо ва метода RaiseEvent, они выполняются в том же потоке, что и вызываю щий (в данном случае Ч в классе clsSpider), а не в потоке главного прило жения. Поэтому очень важно использовать метод SyncLock, чтобы к мас сиву m_sPages единовременно обращался только один поток, иначе возможна взаимная блокировка потоков. Если блокировку SyncLock нельзя получить немедленно, приложение ждет, пока не получит ее. Как только SyncLock попадает во владение про граммы, остальным потокам запрещается обращение к массиву m_sPages до освобождения блокировки SyncLock. Эта блокировка выполняет ряд вспомогательных функций, в частности определяет, присутствует ли рас ширение возвращаемых страниц в списке распознаваемых суффиксов. Но ее самая главная функция Ч добавление к массиву m_sPages новых стра ниц, найденных текущим потоком и подлежащих извлечению и синтакси ческому анализу. Без SyncLock два потока могли бы одновременно попы таться добавить в массив новые элементы и записать в его верхнюю часть Речь идет о синхронизации доступа потоков к общим ресурсам. Ч Прим. сост. Сбор информации от Web-сайтов и каталогов дублирующиеся значения. А это чревато системной ошибкой и даже поте рей еще необработанных страниц. Важно отметить, что ни в коем случае нельзя использовать SyncLock для блокирования потоков или процессов, изменяющих форму или элемент управления. Дело в том, что формы и элементы управления иногда ответ но обращаются к вызывающей процедуре. Такая ситуация скорее всего закончится взаимной блокировкой потоков. Однако обработчик ehThread Status использует SyncLock для блокирования формы перед обновлением ее элементов управления линдикатор хода операции (progress bar cont rols). Поскольку два потока (или более) могут заблокировать друг друга при одновременном обращении к окну списка (listbox), важно блокиро вать форму на время ее обновления. Эта ситуация отлична от той, где SyncLock применяется к потоку, обновляющему форму. Применяя Sync Lock к самой форме, я добиваюсь того, чтобы два потока не мешали друг другу при обращении к ней. Блокировать поток при этой операции нет смысла, так как другие потоки в это время не обращаются к форме. При щелчке кнопки Process приложение создает массив объектов Thread, Первый поток из массива извлекает первую страницу с целевого сайта, и обработчик ehFinished обрабатывает полученную страницу, В это время Spider исполняет цикл, ожидая возврата управления от первого потока или появления новых значений в массиве m_sPages. Если первый поток возвращает управление, не найдя новых страниц, приложение переходит к следующему выбранному сайту. Как только в массиве m_sPages появляются значения, из него выделяется страница неактивному потоку clsSpider; при этом страница, переданная на обработку, удаляется из m_sPages и добавляется в массив m_sPagesDone. Последний тоже проверяется перед запуском новых потоков, чтобы избе жать повторного извлечения уже обработанной страницы. Этот процесс продолжается, пока не будут просмотрены все страницы всех выбранных сайтов. Поиск в потоке данных С появлением.NET в Visual Basic пришла концепция поточной обработ ки (streaming). Поток данных (stream) Ч это последовательность байтов, поступающая в результате запроса из Интернета, файла или от устройства ввода-вывода, например от клавиатуры или мыши. Spider имеет дело с двумя типами потоков данных: потоками из Интернета (Интернет-потока ми) и файловыми потоками. Концепция Интернет-потока сравнительно проста. Все Интернет-серверы передают клиенту потоки данных в виде TCP/IP-пакетов в ответ на зап Доступ к данным из приложений росы, посылаемые серверам. Чтобы дополнить робот этой функционально стью, я прежде всего импортировал библиотеку System.Net, поместив в начало своего класса строку: Imports System.Net Затем создал Web-запрос: Dim myReq As HttpWebRequest = HttpWebRequest.Create(m_sURLToProcess) Приложение посылает каждому потоку строку m_sURLToProcess с URL, который должен быть обработан этим потоком. Далее я создал объект response: Dim myResponse As HttpWeDResponse = myReq.GetResponse С этого момента у меня есть объект response, способный хранить поток HTML-данных с целевого сайта. Но мне нужно знать, какую страницу вер нул Web-сервер. Например, в некоторых случаях, когда я запрашивал кор невой каталог сайта Web-сервер возвращал страницу по умолчанию ( Мне нужно знать эту страницу, чтобы отметить ее в базе данных и не обрабо тать повторно, если ссылка на нее встретится в другом потоке HTML-дан ных. Поэтому я делаю такой вызов: m_sPageResponded = myResponse. ResponseUri.ToStringO Потом мне пришлось решать еще одну проблему. Дело в том, что в теку щем состоянии поток данных непригоден к использованию. Входящие данные должны быть помещены в нечто вроде массива байтов или строки Ч во что-то, с чем я мог бы работать. Чтобы перевести их в такую форму, я создал для потока данных несколько хранилищ и вспомогательный объект BinaryReader; Dim iContentLengtn As Integer, sTotalBuffer() As Byte tContentLength = myResponse.ContentLength Dim br As New BinaryReader(myResponse.GetResponseStreamO) Содержимое потока данных поблочно извлекается в цикле: ReDim sTotalfiyfferfiContentLength - 1) Dim sBuffer() As Byte m_iBytesRead = 1: iTotalBytes = Do Until m_iBytesReatJ = ReDim sBufferfiContentLength - 1) Ti_iBytesRead = br.ReadfsBuffer, 0, iContentLength) ReDim Preserve sBuffer(m_iBytesRead - 1) Сбор информации от Web-сайтов и каталогов If m_iBytesRead > 0 Then Array,Copy(sBuffer, 0, sTotalBuffer, iTotalBytes, sBuffer.Length) iTotalBytes += mj.BytesRead RaiseEvent evThreadStatus(iTotalBytes, iContentLength, m_iThreadIndex) Loop Обратите внимание на строку с RaiseEvent в конце цикла. Она сообщает программе, какая часть страницы уже преобразована из потока данных. Помните: этот обработчик вызывается в том потоке, где выполняется clsSpider. Также обратите внимание на функцию Аггау.Сору: Array.Copy(sBuffer, 0, sTotalBuffer, iTotalBytes, sBuffer.Length) Эта строка заменяет устаревший API-вызов memcpy, использовавшийся в прежних версиях Visual Basic. С методом Аггау.Сору намного проще рабо тать. Указав исходный массив, начальный элемент, целевой массив, на чальную точку и число копируемых байтов, я могу легко скопировать бло ки памяти без всякой возни с API-функциями и внешними DLL, в кото рых они содержатся. В реальной программе вы найдете оператор If, определяющий длину пото ка данных. Интернет-потоки бывают двух типов: с поддержкой поиска и без. В данном случае я предпочитаю рассматривать поиск как прокрутку: в потоке с поддержкой поиска можно перемещаться по объекту потока вперед и назад, извлекая байты из любой позиции. Поток, не поддержива ющий поиск, допускает только однонаправленное перемещение. Код клас са clsSpider определяет, каков тип полученного потока и как обращаться с ним, чтобы извлечь из него информацию. После выполнения этого кода в моем распоряжении остается строка sTo talBuffer с полным листингом HTML-кода страницы, и я передаю эту стро ку различным функциям для обработки. Обработка ошибок Теперь, когда базовая функциональность робота и класса clsSpider готова, пора заняться обработкой ошибок. В Visual Basic.NET появились новые средства обработки ошибок, которые намного превосходят неуклюжие On Error Goto из прежних версий. Блоки Try, Catch и Finally позволяют ис пользовать в Visual Basic.NET структурную обработку ошибок, Эти сред ства оказались весьма полезны в некоторых частях приложения Spider. Так, функция ProcessRobots ищет в корне целевого Web-сервера файл robots.txt и возвращает код ошибки 5, если не может найти его. Было бы неплохо поместить в код этой функции нечто вроде: Доступ к данным из приложений Try Catch When Err.Number = ' Этот код просто сообщает, что файл Robots.txt не найден Debug.WriteLirie("ProcessRobots: " & Err.Number & ": " & Err.Description & " " & Err.Erl) WriteErrorLog("ProcessRotiots: " & Err.Number & ": " & Err.Description & " " & Err.Erl) End Try Блоки Try/Catch можно вкладывать в другие блоки. Это удобно, когда заранее известно о вероятности некоторых ошибок (в частности, из-за про блем с Web-серверами под управлением Unix или с некоторыми типами HTML-потоков). Хороший пример Ч метод StartThreading. Я создаю по ток в первом блоке Try. Если при этой ответственной операции возникнет какая-нибудь ошибка, следует завершить все активные потоки приложе ния и выйти из функции. Б первый блок Catch включен код: Debug.WriteLine("StartThreading: " & Err.Number & ": " & Err.Description & " " & Err.Erl) WriteErrorLogC'StartThreading: " & Err.Number 4 ": " & Err.Description & " " & Err.Erl) Сначала я добавляю код, обрабатывающий известные мне ошибки, затем создаю вложенный блок Try/Catch для завершения индивидуальных по токов: Try Try For i = LBound(m_oThread) To U0ound(m_oThread) If Not IsNothing(n_oThread(i)) Then m^oThread(i).Abort() Next i Catch Debug.WriteLineC'StartThreading: " i Err.Number & "; " & Err.Description & " " & Err.Erl) WriteErrorLogC'StartThreading: " & Err.Number & ": " & _ Err.Description 4 " " & Err.Erl) End Try End Try В прежних версиях Visual Basic такое было попросту невозможно без на писания крайне запутанного кода. Протоколирование ошибок Помимо блоков Try/Catch, обрабатывающих ошибки, мне нужен файл журнала для регистрации ошибок. Поэтому я создал функцию, записыва ющую сообщения об ошибках в файл в корневом каталоге приложения, Сбор информации от Web-сайтов и каталогов Это самый обычный текстовый файл, в котором для каждой ошибки со храняются дата и время, номер ошибки, описание, вызвавшая ее функция и номера строк, ссылавшихся на строку, где возникла ошибка. Я поместил функцию WriteErrorLog в файл modUtils.vb. Эта функция прекрасно иллюстрирует файловый ввод-вывод. Сначала я присваиваю имя файлу журнала: Dim sErrFileName As String = Application.StartupPath 4 "\Spider_" & Format(Now(),"yyyMMdd") & ".txt" Обратите внимание на метод Application. Startup Path: он заменил метод App.Path, применявшийся в прежних версиях Visual Basic. Затем я открыл объект Text Writer, который позволяет добавлять текст к файловому потоку: Dim wt As TextWriter = File.AppendText(sErrFileName) Следующая строка создает безопасную в многопоточной среде оболочку объекта TextWriter, чтобы одновременное обращение нескольких потоков к этому объекту не закончилось их крахом: TextWriter.Synchronized(wt) Далее я записываю в файл строку и закрываю объект TextWriter: wt.WriteLine(Format(Now, "hh:mm:ss tt") & " " & sErrString) wt,Close(} В прежней версии робота возникали многочисленные ошибки, когда не сколько процессов одновременно пытались записать в файл сведения об ошибках, и в конечном счете это приводило к зависанию приложения. Те перь функция TextWriter.Synchronized избавит меня от подобных ошибок. Мой маленький репозитарий Полная версия Spider поддерживает запись в ТХТ-файлы, базы данных SQL Server и уйму других форматов. Версия, описываемая в этой статье, ведет простую базу данных Microsoft Access. Для этого я должен был им портировать соответствующую базовую функциональность: Imports System.Data Imports System.Data.OleDb Imports System.Data.SqlClient Потом я создал переменную, содержащую строку подключения к моей базе данных: Доступ к данным из приложений Public g_sConn As String = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & Application.StartupPath 4 "\spider.fndb" Для доступа к базе данных все готово. Поскольку мне не требовалось ни чего экстраординарного, я создал пару функций: drGetReadOnly (возвра щает объект DataReader только для чтения) и lExecuteSQL (выполняет строку с SQL-запросом к выбранной мной базе данных и возвращает вы зывающей функции число обработанных строк). Функции drGetReadOnly и lExecuteSQL показаны на рис. 4 и рис. 5 соот ветственно. Они почти не отличаются друг от друга: drGetReadOnly ис пользует метод ExecuteReader OLE-объекта DBCommand, a lExecuteSQL Ч метод ExecuteNonQuery. Рис. 4. drGetReadOnly ' Возвращает DataReader из базы данных Public Function drGetReadGnly(ByVal sSQL As String) As OleDbDataReader Try 1:. Bint cn As New QleDbCortnection(g_sCQnn) 2: cn.OpenO 3: Dim cind As OleOSCoBintand = Hew OleDbGommand(sSQt, cn) 4: Dim dr As QleDbDatafleader = cmd.ExecuteReader() 5: Return dr Catch 'HsgBoxfErr,Number & ": " & Err.Description & " " 4 Err.Erl) 10: Debug.Wr:LteLine("dr6etReadOnIy: " & Err.Number & ": " & _ Err.Description & " " & Err.Erl) 500; WriteErrorLogC'drGetReadGnly; " & Err.Rtfffiber & ": " & Д Err.Description & " " & Err.Erl) End Try: End Fynctiorv Рис. 5. (ExecuteSQL Выполняет переданный SQL-оператор и возвращает число обработанных строк Function ISxecuteSQLCByVal sSQi. As String) As Long Try Dim en As New OleDbConnection(g^sGonn) cn.0pen() Dim end As OleDtiCasimand = New 01eDbCoлtmand(sSQLt cn) Dim IRecsAffected As Long = cmd.ExecuteNonQueryO cn.CloseC) Return IRecsAffected Catch "KsgBoxCErr.Number & "; " & Err.Description & " " & Err.Erl) 10: Debug.WriteLineC'lExecuteSQL: " 4 Err.Nufliber & "; " & _ см. след. стр. Сбор информации от Web-сайток и каталогов Рис. 5, (ExeeuteSQL (окончание) Err.Description 4 " " & Err.Erl & vbCrLf & sSQl) 500: WriteErrorLogC'lExeeuteSQLi " i Err,Number & ": " Err.Description & " " & Err.Erl & vbCrLf & sSQL) End Try End Function Что случилось с ItemData? В Visual Basic 6.0 я использовал свойство ItemData списков (listboxes) и полей со списками (comboboxes) для хранения информации о каждой строке. Чаще всего я помещал в него идентификаторы всех записей. Если мне нужно было хранить больше информации, приходилось создавать на бор (collection) и связывать каждый элемент набора с элементом списка или поля со списком. В Visual Basic.NET этот подход заменен на другой, гораздо более удобный. В Visual Basic.NET я могу создать нестандартный класс (рис. 6). В опре делении класса clsBoxOverride я замещаю метод ToString, указывая, какое значение он должен возвращать, а также создаю в clsBoxOverride метод New, позволяющий передавать аргументы экземпляру этого класса. Рис, 6. Класс clsBoxOverride clsboxQverride Этот класс используется для передачи данных списку сайтов в главном окне, Похож на функцию в стиле ItemData (ее больше нет в Visual Basic.NET), но позволяет добавлять дополнительные элементы, которые можно хранить с элементами списка. Раньше для этого приходилось использовать, набор или массив. Точка входа: New Option Explicit On Public> 382 Доступ к данным из приложений Рис. 6. Класс clsBoxOverride ByVal sSSteURL As String, ByVal ISitelD As Long) BLsSiteNarne = sSiteName m_sSlteURL = sSitem m_iSiteID = iSitelB End Sub Public Property SiteNaroeC) As String Set(ByVal Value As String) ie_sSitePfame == Value End Set Get Return m_sSiteNarae End Set End Property Public Property SiteL/RLO As String SeUByVal Value As String) flLsSiteURL = Value End Set Get Return Bi_sSitel/Rt End Get End Property Public Property SiteJBO As Long Set(ByVal Value As Long) flt_iSiteID Value End Set Set Return m_iSiteID End Get End Property Public Overrides Function ToStringO As String Return H]_sSiteNauie nd Function End"> lstSites.Items(lIndex).SiteURL.ToStringO Сбор информации от Web-сайтов и каталогов Обратите внимание, как определяются теперь свойства в классе (в данном случае Ч в clsBoxOverride): Set и Get входят в тело каждого свойства; Public Property SiteNameO As String Set(ByVal Value As String) m_sSiteName = Value End Set Set Return m_sSiteName End Get End Property В Spider я использую эту функциональность в обработчике событии MouseMove, чтобы в списке IstSites вывести URL, сопоставленный с опи санием сайта. Прозрачность и непрозрачность И последнее (по порядку, а не по важности). Благодаря двум новым сред ствам Visual Basic.NET Ч прозрачность (transparency) и непрозрачность (opacity) Ч я смог украсить свое приложение ранее недоступными при мочками. Я всегда мечтал о классном экране-заставке, который появлялся бы при запуске моего приложения, но все, что я мог бы сделать, ограничивалось двухмерными прямоугольными окнами. И вот наконец-то команда разра ботчиков Visual Basic.NET дала мне возможность управлять прозрачнос тью формы. По сути, это позволяет сделать один цвет на любой форме прозрачным. Я создал форму для экрана-заставки, поместив цветной ло готип на белый фон, и выбрал для свойства TransparencyKey этой формы значение, соответствующее белому цвету. В итоге получилось то, что надо (рис. 7): белые области моего экрана-заставки стали прозрачными и сквозь них видно окно приложения. Заодно я решил сделать так, чтобы главное окно приложения постепенно проявлялось на экране. Для этого используется новое свойство Opacity форм: ' Изменяем непрозрачность формы, ' чтобы она постепенно проявилась на экране Не.Opacity = О Me.ShowO For i = 1 То 100 Step Me.Opacity = i / Application.DoEvents() Next i He.Opacity = 384 Доступ к данным из приложений ment This is a les! document. This is a lest docunenl Th merit. This is a Issl document This is a lesl document. Th ment This is a test document. This is a test document, Th Хпен! Jjjjjtetf lafiLita rumen! This is a test document. Th щтоситегД This \^s test document Th Л^рйВ jjn^hiyrSS Ж^осиг*№ jSfJf jfyjgfbtffnsnl Th nsnl Thi/*& tttlVc-'ytea^ffiftp'ICtetu'rieril Th ment. ThJs is a test Ibcument./his i; а ЛЕ! document Th ment. F*s is a t?st document*Fhis is э test document Th nt. This is a t?si document. This is a test document Th nen? This i? э test document. This is a test document. Th ment This is a last doc-итэп! This is a test document. Th Рис. 7. Прозрачность формы в действии Значение свойства Opacity может быть дробным и меняется от 0 (форма полностью невидима) до 1 (форма полностью видима). Блок кода, пока занный выше, увеличивает значение Opacity с шагом 1/10 до тех пор, пока форма не станет видимой. Обратите внимание на строку Application.DoEvents. Если ее удалить, фор ма почти моментально станет видимой. Эта строка фактически замедляет процесс проявления формы. Заключение В настоящее время клиенты моей компании используют Spider для сбора информации с локальных, региональных и общенациональных сайтов. После небольшой доводки Spider сможет получать списки адресов из об щедоступного справочника Yellow Pages или собирать статьи по опреде ленной тематике со всей Web. Его можно было бы использовать даже для доставки сообщений, публикуемых в телеконференциях на серверах ново стей. Spider способен извлекать любые Web-страницы (естественно, если у вас есть разрешение на копирование материалов, защищенных авторс ким правом). Полученную информацию можно поместить в базу данных для дальнейшего анализа. В Visual Basic.NET появилась уйма новых и просто замечательных функ ций. Проявив немного настойчивости, со временем вы научитесь пользо ваться каждой из этих функций. Более совершенная обработка ошибок и поддержка массивов, фокусы с III и средства для работы с потоками дан ных Ч лишь часть инструментов, которые дает вам Visual Basic.NET. Вас ждет много потрясающих открытий! Марк Герлах (Mark Gerlach) Ч директор отдела разработки продуктов калифорнийской компании Optigon Technical Associates, поставщика реше ний Microsoft {Microsoft Solution Provider). Марк Ч обладатель сертификатов MCSD и MCDBA. С ним можно связаться по адресу gerlachm@optigon.net или mgerlach@mostwantedsoftware.com. Кен Спенсер гги Управление транзакциями между компонентами.NET* Отвечая на вопросы, автор объясняет, как управлять транзакциями в ADO.NET, не используя СОМ+. Этот вариант применим в том случае, если вы работаете только с одной базой данных. В этом месяце я углублюсь в вопрос одного из читателей о транзакциях, относящийся к приложениям как Web, так и Windows. Рад сообщить, что у меня наконец появилась финальная версия Visual Studio.NET и Micro soft.NET Framework. О приставке бета можно пока забыть! Вопрос Прочитал вашу колонку (за февраль 2002 г.) по СОМ+, ПСОМ и сериализации MSMQ в.NET ( issues/02/02/basics/basics0202.asp). Вы писали, что если компонент всегда выполняет транзакции только в одной базе данных, то для реализации этих транзакций не обязательно применять СОМ+ Ч можно использовать ADO.NET. Это похоже на коренное изменение основного подхода. Не могли бы вы поподробнее рассказать об управлении транзакциями между компонентами.NET, когда работа ведется с одной базой данных? Если я передаю строку подключения и соединение остается открытым, не ведет ли это к дополнительным издержкам? Ответ Замечательный вопрос! Вместо краткого ответа я предлагаю обсу дить созданное мной приложение-пример. После этого мы перейдем к воп росу об эффективности. Публиковалось в MSDK Magazine/Русская Редакция. 2002. Спецвыпуск №2 (апрель июнь). Ч Прим. изд. Доступ к данным из приложений Начнем с создания транзакций в ADO.NET. Я разработал приложение пример, вставляющее от одной до четырех строк в таблицу Order Derails демонстрационной базы данных Northwind. Приложение спроектировано так, что оно или фиксирует все операции вставки, или отменяет их в слу чае ошибки. В приложении используются SQL Server и провайдер данных SQLClient. Почти то же самое можно сделать, используя провайдер OLE ОБ, если ваша СУБД поддерживает транзакции. Вместо того чтобы полагаться на образец кода для работы с транзакция ми, предлагаемый в документации и поясняющий, как реализуются тран закции в ADO.NET, я использую простой принцип распределения компо нентов по уровням. Весь код, работающий с базой данных, находится в классе компонентов, а клиентским интерфейсом служит Windows-форма, вынесенная в отдельный проект. В компоненте для манипуляций с базой данных имеется один класс DBStuffADONET (рис. 1). Заметьте, что строку подключения содержит закрытая переменная. Это полезно, когда компонент не используется с разными базами данных. Однако в компоненте присутствует и перегру женный конструктор, принимающий новую строку подключения, которую можно указать при создании экземпляра класса. Создав неременную строки подключения, я создаю закрытые переменные для экземпляров SqlConnection и Sql Transact ion. Подробности потом. В классе используется функция RunSQLWithDataSet, которую я показы вал в своей колонке за апрель 2002 г. ( mag/issues/02/04/basics/basics02Q4.asp). Она не участвует в транзакциях, а возвращает транзакционный DataSeL Для упрощения я использую ста тический SQL вместо хранимых процедур. Теперь взглянем на функции, участвующие в транзакциях, и еще на не сколько функций, которые в транзакциях не участвуют. OpenConnection Trans создает новое SqlConnection и открывает его. Затем она начинает транзакцию в базе данных, вызывая BeginTransaction, и присваивает пере менной Transaction Current ссылку на транзакцию: Public Sub OpenConnectionTransO ConnectionCurrent = New SqlConnsction(sConnectionString) ConnectionCurrent.Open О TransactionCurrлnt = ConnectionCurrent.BeginTransactionO End Sub Если вам нужно соединение и не требуется поддержка транзакций, може те вызвать OpenConnection: Управление транзакциями между компонентами.NET 38? Public Sub OpenConnectionQ ConnectionCurrent = New SqlConnection(sConnectionString) ConnectionCurrent.Open() End Sub Когда настает пора зафиксировать транзакцию, вызывается процедура: Public Sub Commit!ransaction() TransactionCurrent.Commit {) End Sub Для отката транзакции предназначена процедура: Public Sub Rollback!ransactionO TransactionCurrent.Rollback() End Sub Закончив работу с соединением, его следует закрыть. Следующая проце дура закрывает соединение и удаляет ссылку на объект соединения: Public Sub CloseConnection() If ConnectionCurrent Is Nothing !hen Exit Sub End If If ConnectionCurrent.State = ConnectionState.Open Then ConnectionCurrent.Close() ConnectionCurrent = Nothing End If End Sub Итак, что нужно сделать, чтобы в рамках транзакции выполнить операцию вставки или модификации данных? Как раз для этого и предназначена функция RunSQLN on Query. Она создает новый экземпляр класса SqlCom mand, затем присваивает свойству Connection текущее соединение (Con nectionCurrent), а свойству Transaction Ч текущую транзакцию (Tran sactionCurrent). Далее метод ExecuteNonQuery выполняет SQL-оператор, и издержки этого метода невелики, так как он не возвращает никаких дан ных (рис. 2). Последняя функция класса Ч RunSQLScalar, которая выполняет SQL-опе ратор и возвращает единственное поле данных. Она удобна, если вам надо извлечь один блок данных, скажем, Unit Price для какого-либо товара. При беглом просмотре класса видно, что он поддерживает информацию о состоянии. Вы должны создать его экземпляр, открыть соединение с тран закцией и выполнить код, который должен работать как часть транзакции. Далее нужно зафиксировать или отменить транзакцию и закрыть соедине ние. Это не так сложно, поскольку вы можете создать экземпляр класса, 388 Доступ к данным из приложений проделать работу и закрыть соединение в своем приложении. В моем при ложении-примере используется клиентский интерфейс на основе Win dows и просто создается экземпляр класса на время выполнения этого приложения. Рис. 1. DBStuffADONET.vb Imports System.Data.SqlCllent Public> uid=sa; pwd=; database=NortnWind" Private ConriectionCurrent As SqlConnection Private TransactionCurrent As SqlTransactian Public Sub NewQ End Sub Public Sub NewCSyVal sNewCormectionString As String) If sNewConnectioflString <> "" Then sConnectionString = sNewConnectionStrinfl End If End Sub Public Function HunSQLWithDataSet(ByVal sSQL As String, _ Optional ByVal sTableName As String ~ "Tablel"} As DataSet Dim oDataSet As New DataSetO Dim oOataAdapter As SqlClient.SqlBataAdapter ' Создаем новый DataAdapter oQataAdapter = New _ SqlClient.SqlDataAdapter(sSQL, sConnectionString) Заполняем набор данных из DataAdapter oOataAdapter. Fili(oDataSet, sTableNaeie) oDataAdapter * Nothing ' Возвращаем значение функции Return oOataSet End Function. Public Sub OpenGonnectionTrans(-) ConnectionCurrent * New Sqieonnection(sConnectionString) ConnectionCurrent.OpenO ' Запускаем локальную транзакцию TransactionCurrenta ConnectionCurrent. BeginTra/isactionQ End Sub Public Sub OpenConneetionO ConnectionCurrent = New SqlConnection(sConnectionString) ConnectionCurrent.Open() End Sub см, след. стр. Управление транзакциями между компонентами.NET (продолжение) Рис, 1. DBStuffADONET.vb Public Sub CoiBinltTransactionO TransactionCu rrent. Commit() End Sub Public Sub RollbacKTransactionO TransactionCurrent.Rollback) 'End Sub Public Sub CloseConnectionO If ConnectionCurrent Is Nothing Then Exit Sub End If If ConnectionCurrent.State л ConnectionState.Open Then ConnectionCurrent.Glose{) CormectionCurrent ~ Nothing End If End Sub Public Function RunSQLHonQuerytByVal sSQL As- String) As String ' Указываем объект транзакции ' для выполняемой локальной транзакции Dint cmdLocal As New SqlCommandO cmdLocal.Connection = ConnectionCurrent cmdLocal.Transaction = TransactionCurrent Try cmd Local. CommandText = sSQt cmdLocal. ExecuteNonQueryO Catch exc As Exception Return "Error occurred " & exc.Message Finally End Try End Function Public Function RunSQLScalar(ByVal sSQL As String) As String. ' Указываем объект транзакции ' для выполняемой локальной транзакции Dim cmdLocal As flew SqlCommandO Dim bConnectlonAlreadyOpen As Boolean = True : Dim sTeffip As String If ConnectionCurrent Is Nothing Then bConnectionAlreadyOpen я False ilself ConnectionCurrent.State <> ConnectionState.Open Then bConnectionAlreadyOpen = False End If If bConnectionAlreadyOpen = False Then см. след. стр. 390 Доступ к данным из приложений Рие. 1. DBStuffADONET.vb bConnectionAlreadyOpen = False OpenConnectionC) End If cmdLocal.Connection = ConnectionCurrent Try cmdLocal.CommandText = sSQL sTemp = CStr(cmdLocal.ExecuteScalar()} Catch exc As Exception sTemp = "Error occurred " & exc.Message Finally If Not bConnectionAlreadyOpen Then ConnectionCurrent.Close() ConnectionCurrent = Nothing End If End Try Return sTeuip End Function End> New Order tins items Рис. 3. Форма для транзакций ADO.NET Заполнив строки, щелкните кнопку Insert Ч ADO.NET Тгх дли добав ления новых записей в таблицу Order Details вашей базы данных. Теперь посмотрим на саму форму. Поля со списками, Orders (cboOrders) и Products (cboProducts), обеспечивают доступ к спискам заказов и това ров. Я поясню кое-какие тонкости, которые позволят сэкономить время. Проектируя форму, я скрыл все элементы управления для ввода записей и включал их по мере необходимости. Для этого я присвоил свойству cboProducts.Visible значение False и пытался устанавливать значение True всякий раз, когда включал строку элементов управления. Почему-то это постоянно вызывало ошибку в период выполнения. Тогда мне стало инте ресно, что будет, если поместить cboProducts в элемент управления Panel. Я поместил на форму Panel (pnlProduct), в него Ч cboProducts и устано вил размер панели в соответствии с размером cboProducts; после чего я указывал расположение и видимость панели, а не cboProducts. Это срабо тало. Почему Ч не знаю. Форма на этапе разработки показана на рис. 4. Под полем со списком Orders расположено еще одно Ч cboProducts. Оно динамически помещается поверх первого текстового поля в строке (напри мер, txtProductName_l), когда пользователь вводит данные в эту строку. Остальную часть интерфейса можно изучить, скачав исходный код ( /download.microsoft.com/download/msdnmagazine/code/May02/WXP/EN US/Basics0205.exe). Взглянем на код, имеющий дело с транзакциями на стороне клиента. Когда пользователь щелкает кнопку Insert Ч ADO.NET Тгх для вставки позиций заказа, обрабатывается событие cmdlnsert ADONet Click. 392 Доступ к данным из приложений Рис. 4. Форма для транзакций на этапе разработки В первых трех строках обработчика этого события объявляются переменные: Dim sSQL As String Dim sStatus As String = "" Dim sOrderlD As String Затем вызывается метод CloseConnection. Если соединения нет, ошибка не возникает, а если есть, оно закрывается: oDB.CloseConnection() Далее открывается соединение и начинается транзакция: oDB.OpenConnectionTrans{) Следующие несколько строк кода говорят сами за себя: в них просто на страиваются элементы управления, проверяется, введено ли название то вара, и определяется идентификатор заказа: IblMessage.Text = "" If txtProductName_1.Text <> "" Then sOrderlD = cboOrders.SelectedValue If sOrderlD = "" Then IblMessage.Text = "You must select an order to add line items to" Exit Sub End If Еще несколько строк кода вставляют позиции заказа в базу данных вызо вом InsertOrderDetail: Управление транзакциями между компонентами.NET ' Вставка 1-й записи sStatus = InsertOrderDetail(sOrderID, txtProductID_1.Text, txtUnitPrice_1.Text, txtQuantity_1.Text, txtDiscount_1.Text) Единственное отличие последующих вызовов Ч проверка sStatus (чтобы убедиться, что в этой строке нет сообщения об ошибке и что элемент уп равления txtProductID не пуст). Если эти условия соблюдены, вызывает ся InsertOrderDetail: Вставка 2-й записи If sStatus = "" And txtProductID_2.Text <> "" Then sStatus = InsertOrderDetail(sOrderID, txtProductID_2.Text, txtllnitPrice_2.Text, txtQuantity_2.Text, txtDiscount_2.Text) End If Вызовы функции для третьей и четвертой строк я опустил, так как они отличаются лишь тем, что ссылаются на другие элементы управления. Вызвав Insert Order Detail в последний раз, вы должны освободить транзак цию. Если sStatus что-то содержит, значит, произошла ошибка, и транзак цию нужно откатить методом RollbackTransaction. Сообщения об ошибках показываются элементом управления IblMessage: Освобождение и откат/фиксация транзакции If sStatus <> "" Then IblMessage.Text = "Rolled back transaction due to error " & "on row " & " N - " & sStatus oDB, RollbackTransactionO Exit Sub End If И, наконец, если ошибок не было, вызывается метод CommitTransaction, чтобы выполнить транзакцию. Текст IblMessage изменяется, чтобы пока зать успешность записи в базу данных, и соединение закрывается. oDB.CommitTransactionO IblMessage.Text = "Line items inserted ok" oDB.CloseConnectionC) Функция InsertOrderDetail довольно проста. Вот ее заголовок: Function InsertOrderOetaiKByVal sOrderlD As String, _ ByVal sProductID As String, ByVal sUnitPrice As String, ByVal sQuantity As String, ByVal sDiscount As String) As String 394 Доступ к данным из приложений Далее создаются две переменные: Dim sSQL As String Dim slnsertStatus As String Затем формируется SQL-оператор с использованием параметров, передан ных функции: sSQL = "INSERT INTO [Order Details] " _ "(OrderlD, ProductID, UnitPrice, Quantity, Discount) " sSOL &= "VALUE$<" & sOrderlD & "," & sProductlD & "," _ & sUnitPrice ft "," sSQL 4= sQuantity & ", " & sDiscount & ")" В блоке Try/Catch содержится вызов RunSQLNonQuery, которая и выпол няет SQL-оператор. Заметьте: если slnsertStatus содержит любое непустое значение, генерируется исключение. Это позволяет перехватывать все ошибочные ситуации в блоке Catch: Try slnsertStatus = oDB.RunSQLNonQuery(sSQL) If slnsertStatus <> "" Then Throw New System.Exception(sInsertStatus) End If Catch exc As Exception Return exc.Message End Try End Function Вот и все. Ничего особенного. Как видите, ADO.NET позволяет легко ис пользовать транзакции, если ваша база данных их поддерживает. Вторая часть вопроса относилась к эффективности. Конечно, если соеди нение остается открытым, это может вызвать проблемы. Тем не менее, как показывает мой пример, ADO.NET дает возможность управлять транзак циями, а сколько времени вы будете держать соединение открытым, ре шать вам. Кроме того, ADO.NET поддерживает пулы соединений, поэто му, если вы используете одну и ту же строку подключения, открытие и закрытие соединений увеличивает издержки крайне незначительно. Вопросы и комментарии (на английском языке) присылайте на адрес basics@microsoft.com. Кен Спенсер (Ken Spencer) работает в компании 32Х Tech ( www.32X.com), которая занимается разработкой ПО и обучением, а также предоставляет консалтинговые услуги по технологиям Microsoft. Читайте во II томе АЛЬМАНАХА ПРОГРАММИСТА Х инфраструктура ASP.NET Х использование HTTP-конвейеров Х серверные элементы управления ASP.NET Х написание безопасного кода Х создание Web-сервисов Х разработка приложений электронной коммерции и других Web-приложений ASP.NET в продаже Интернет с сентября 2003 г. приложения Х интернет-магазин издательства www.ITbook.ru Х крупные книжные магазины Москвы издательство компьютерной литературы И РШШ теп.; (095; 142-0571; тел./факс (095) 145-4519; e-mail; info@4usedit.ru; www.ru sedit.ru тел.: [095] 142-0571: телефакс (0951145- e-mail: info@ru5edrt.iu: www rusedn.ru книга серии Учебный курс (лTraining} Microsoft Press - счерпывающая для самостоятельной раооты, видеоролики и вопросы и закрепление знании. Издание подготовлены профессиональных знаний специалистов по конкретной теме, издательство компьютерной литературы Ri Р У С С К И Р Е Д А К - И Я тел.: (095] 142-0571; тел./факс (095)145-4519; e-mail: mfo&fиsedit.ru; tittp:// www.rjsedit.ru Наши книги Вы можете приобрести Х в Москве: Специализированный магазин Компьютерная и деловая книга Ленинский проспект, строенив 38, тег.: (095] 778- Би 6 лив-Глобус- уп. Мясницкая, 6, тел.: (095) 928- Московский дом книги ул. Новый Арбат, 8, тел.: 1095) 290- Дои технической книги Ленинский пр-т, 40, тел.: (095) 137- "Молодая гвардия ул. Большая Полянка, 28, тел.: (095) 238- "Дом книги на Соколе Ленинградский пр-т, 7S, теп. (095) 152- Дом книги на Войковской Ленинградское шД 13, стр. 1. тел. (095)150- "Мир печати ул. 2-я Тверская-Ямская, 54. тел.: (095) 978- Торговый дом книги "Москва уп. Тве нжая. 8, тел.: (095) 229- Х в Санкт-Петербурга: СПб Дом книги, Невский пр-т., тел.: (812)318- СПб Дом военной книги. Невский пр-т., тел.: (812)312-0563.314- Магазин Подписные издания Литейный пр-т., 57, тал.: (812) 273- Магазин Техническая книга, ул. Пушкинская. 2, тел.: (812) 164-6565, 164- Магазин Буквоед, Невский пр-т., тел.: (812) 312- ЗАО Торговый Дом..Диалект. Тел.:(812)247- Оптово-розничный магазин Наука и техника тел.: (812) 567- Х в Екатеринбурге: Магазин Дом книги, ул. Валека, 12, тел,:(3432)59- Х в Великом Новгороде: "Наука и техника, ул Большая Санкт-Петербургская. -14, Дворец Молодежи. 2-й этаж Х в Новосибирске: 000 Ton-книга, тел : (3832) 36- Х в Алнаты (Казахстан): ЧП Болат Ам ре ев. моб. тел.: 8-327-908-28-57, (3272) 76- Х в Киеве (Украина): 000 Издательство Ирина-Пресс тел.: (+1038044) 269- Техническая книга на Петровке, тел.: (+1038044) 268- liiitirm InfipmsfrDft S Х*-ст^т1^|^|ЩшЬф$лиийхх*. Чш Оперативная информация для профессионалов в журнале MSDN Magazine/Русская Редакция Журнал содержит постоянные рубрики Web: вопросы и ответы Ч вопросы и ответы, сеязаиные с разйабЬткой по Web. XMLЧ основные спецификации XML ". " (XML Schema, пространство имен У3т5есш11у. Web сервисы и т. д.). dteerw на вопросы ч*ггатё.1ей. ASPЧ все аспекты программирование с использованием ASP и ASRNT. Доступ к данным Ч проблемы разра эф фиктивных приложений дпн взаимодействий с СУБД, Повышение производительности и обеспечение безопасности', э также рограммирование с использованием ADO.NET. На переднем крае Ч передовые техно тогда разработки I'SOAR !Л'еЬ-сер8ИСы,', NET Fram.NET Ч Джеффри Рихтер Ч-об осноеныхнонцейциях и понятиях.NET и о приемах программирования. ОСНОВЫ и ТОНКОСТИ Ч антуэльные'прсс.яёмы Х' Х' программирования, интересующие в ОСНОЕ.НОМ Отладка и оптимизация Ч эффективь ы и подходы к решению задач отладки и оптимизации -: " : Х'/' приложений. ' := "Своей популярностью этот журнал обязан своему наполнению: читатель получает самую Под капотом ~- особенности-ф!?нлциониэован11я ; . системных компонентов и исполняющих срзд, создание свежую и актуальную информацию как эффективного программного обеспечения. ; ХХ Х : от ведущих разработчиков корпорации Для систкмньк прогрзмь*ипго8 и Microsoft, так и от известных специалистов в сфере разработки приложений. Изощренный код -Ч необычные приемы, програлшировения. Для системных программистов Глубокие и содержательные статьи оказывают и разрабо-тчиксда. неоценимую помощь профессиональному разработчику, регулярно использующему С**: вопросы и ответы Ч вопросы и советы, средства разработки Microsoft." сейзэнньшс программированием наязыкьС++, Из статьи Ольги Дергуновой, Тематические статьи главы представительства Microsoft в СНГ (лMSDN Magazine/Русская Редакция; и подробно раскрывают проблемы спецвыпуск № 1, апрель 2002 г.) Оформить подписку Информационная поддержка журнала и приобрести журнал можно MSDN Magazine/Руесная Редакция- ~ в интернет-магазине w ww.microsoft.com/irii5/msdn/magazitm www.ITbook.ru H t l H l H ШШГН иинэжо1/ис!и си f% iqHHBtf и uAioo"' 13N'OQV euizBgeiAi MQSW и Ajejqn MQSVi аоиендэием минбодэ иихээьихемэ!