С выходом версии SQL Server 2000 корпорация Microsoft стала претворять в жизнь свой новый девиз: обеспечивать доступ к данным и приложениям «в любом месте, в любое время и на любом устройстве». В центре этой стратегии находится интегрированная в SQL Server 2000 поддержка XML. Способность XML к «самоописанию» и совместимость со многими платформами предоставляют SQL Server возможность легко обмениваться данными с приложениями Web и другими системами. В данной статье я расскажу о том, как OLE DB и ADO 2.6 поддерживают три ключевые возможности XML: извлечение XML из хранимых процедур, исполнение шаблонов и выполнение запросов XPath к представлениям XML.
Прежде чем приступить к иследованию механизмов использования ADO для извлечения XML, напомню, как SQL Server 2000 генерирует XML. В SQL Server 2000 для предложения SELECT языка T-SQL введено расширение в виде оборота FOR XML. При добавлении оборота FOR XML в предложение SELECT, возвращаемый SQL Server результатирующий набор данных будет отформатирован как XML с учетом параметров, указанных в предложении. Наиболее важным аргументом для оборота FOR XML является параметр режима XML, XML mode, который может иметь значения RAW (без обработки), AUTO (автоматический), EXPLICIT (с явным указанием параметров). Синтаксис предложения SELECT с оборотом FOR XML выглядит следующим образом:
SELECT ...
FROM ...
[WHERE]
FOR XML RAW | AUTO | EXPLICIT [,XMLDATA] [,ELEMENTS] [,BINARY Base64]
Чтобы увидеть, как работают три упомянутых режима XML, и каковы результаты их применения, пройдем последовательно по каждому режиму.
Режим XML
RAW. Режим RAW генерирует неиерархический текст XML, в котором каждая
строка возвращаемого набора данных преобразуется в один элемент, строку XML,
причем столбцы трансформируются в атрибуты XML. Режим RAW лучше всего
использовать в тех случаях, когда клиентский код ожидает встретить плоские
документы XML и ищет элементы-строки. В отличие от режима AUTO режим RAW
позволяет применять оборот GROUP BY и проводить агрегирование. Все режимы XML
позволяют возвращать схему XML Data Reduced (XDR), для чего после оборота FOR
XML следует поставить аргумент XMLDATA. Схема XDR представляет собой
разновидность XML, в которой описываются структура и содержание документа XML.
Схема XDR позволяет клиентскому коду считывать информацию о типе данных, так что
код может должным образом форматировать данные. Схема XDR предоставляет больше
информации, чем файлы Document Type Definition (DTD), и с ней легче работать
программам синтаксического разбора. Заметим, что схема XDR была предложена
корпорацией Microsoft в качестве определения схемы XML. Однако консорциум World
Wide Web Consortium (W3C) не принял XDR в качестве стандартного определения.
Представители Microsoft заявили, что корпорация будет поддерживать то
определение схемы XML, которое примет консорциум W3C, когда оно станет
доступным. Более подробную информацию об определениях схемы XML можно найти в
статье "XML Schema Part 2: Datatypes" на сайте http://www.w3.org/tr/xmlschema-2.
Режим XML AUTO. В режиме AUTO генерируется иерархический
документ путем преобразования в элемент каждой таблицы, на которую ссылается
предложение SELECT. По умолчанию в режиме AUTO каждый столбец преобразуется в
атрибут, если только вы не примените аргумент ELEMENTS для создания
подэлементов. Не забывайте, что режим AUTO не поддерживает оборота GROUP BY, а
порядок следования столбцов в предложении SELECT задает порядок вложения
атрибутов. Режим XML AUTO позволяет применять псевдонимы таблиц и столбцов в
качестве имен элементов или атрибутов. Однако по умолчанию именами элементов и
атрибутов являются названия таблиц и столбцов, а не их псевдонимы. Использование
аргумента BINARY Base64 обеспечивает возврат образа или двоичных данных в
двоичном формате с кодировкой base64. Если аргумент BINARY Base64 не
используется, то режим XML AUTO возвращает URL, к которому можно обратиться с
запросом для получения двоичных данных.
Режим XML EXPLICIT.
Режим EXPLICIT является наиболее сложным и мощным режимом XML. Как следует из
названия, при работе в режиме EXPLICIT необходимо явным образом определить схему
возвращаемого XML. Это производится путем создания виртуальной таблицы, которую
SQL Server переводит в XML. В силу чрезвычайной гибкости данного режима он
особенно удобен для формирования документов с иерархической структурой. Режим
EXPLICIT позволяет опеределить каждый столбец как атрибут или элемент, и даже
создать элементы, не представленные в базе данных. При работе в этом режиме
необходимо предварять результат двумя столбцами, Tag и Parent, которые и
образуют иерархическую структуру результирующего документа XML. Как и при
использовании двух других режимов, обязательно следует определить названия
элементов и атрибутов в предложении SELECT.
Используя оборот FOR XML, можно легко преобразовать результирующий набор в XML. Однако этот оборот можно применять лишь в предложениях, которые возвращают данные непосредственно клиенту. Нельзя использовать оборот FOR XML в определениях представлений или задаваемых пользователем функций (UDF); во вложенных предложениях SELECT; в хранимых процедурах, которые манипулируют результирующими наборами данных; в предложениях INSERT, а также в предложениях с оборотом COMPUTE BY.
ЛИСТИНГ 1: Хранимая процедура usp_GetCatTotals. CREATE PROCEDURE usp_GetCatTotals @CatID int, @StartDate smalldatetime, @EndDate smalldatetime AS SELECT 1 AS Tag, NULL AS Parent, @CatID AS [OrderTot!1!CatID], @StartDate AS [OrderTot!1!StartDate], @EndDate AS [OrderTot!1!EndDate], NULL AS [Product!2!ProductID], NULL AS [Product!2!ProductName], NULL AS [Product!2!TotalUnits!element] UNION SELECT 2, 1, NULL, NULL, NULL, d.ProductID, d.ProductName, SUM(b.UnitPrice) FROM Orders a JOIN [Order Details] b ON a.OrderID = b.OrderID JOIN Products d ON d.ProductID = b.ProductID WHERE d.CategoryID = @CatID AND a.OrderDate BETWEEN @StartDate AND @EndDate GROUP BY d.ProductID, d.ProductName ORDER BY [Product!2!ProductName] FOR XML EXPLICIT GO
Теперь рассмотрим хранимую процедуру, которая применяет ADO для возврата XML в клиентское приложение. В листинге 1 показана хранимая процедура usp_GetCatTotals, которая возвращает документ XML. В этом документе проводится агрегирование подробной информации о заказах из базы данных NorthWind для указанной категории товара и заданного периода времени. Процедура использует режим EXPLICIT для построения заказной схемы, в вершине которой располагается элемент OrderTot. Этот элемент содержит атрибуты, которые отражают параметры категории товара (categoryID), даты начала и конца временного интервала. Эти параметры клиентское приложение передает хранимой процедуре. Такой подход позволяет каждому клиентскому приложению, которое вызывает эту процедуру, определять, каким образом SQL Server должен формировать результирующий набор. На рисунке 1 показан документ XML, сформированный процедурой usp_GetCatTotals.
На рисунке 2 показано приложение, которое применяет все три подхода, описанные в данной статье, для получения XML из SQL Server 2000 при помощи ADO. Приложение, которое я написал на Visual Basic (VB) 6.0, использует ADO 2.6 (именно эта версия поставляется с SQL Server 2000) и SQLOLEDB в качестве поставщика OLE DB для SQL Server. Это приложение нетрудно переложить для Active Server Pages (ASP) без потери функциональных возможностей.
В листинге 2 продемонстрирован первый поддход, который состоит в том, чтобы просто вызвать хранимую процедуру usp_GetCatTotals для получения документа XML, который можно было бы просматривать с помощью обычного Web-браузера. Как и при вызове любой хранимой процедуры, сначала необходимо создать объект ADO Connection, чтобы установить соединение с базой данных. Затем следует создать объект Command для формирования вызова хранимой процедуры, как показано в метке А листинга 2. В рассматриваемом примере вызывающая процедура OpenProc имеет три входных параметра: pCatID, pStart и pEnd. Эти параметры передаются процедуре через объекты ADO Parameter, которые создаются с помощью метода CreateParameter объекта Command.
ЛИСТИНГ 2: Процедура VB, которая для получения XML вызывает usp_GetCatTotals. Private Sub OpenProc(ByVal pCatID As Long, ByVal pStart As Date, ByVal pEnd As Date) Dim cnConn As Connection Dim strConn As String Dim cmCmd As Command Dim strQuery As String Dim sResponseStream As Stream BEGIN CALLOUT A Set cnConn = New Connection strConn = "Provider=SQLOLEDB;Data Source=ssosa\sql2000;Initial Catalog=Northwind;trusted_connection=yes" cnConn.ConnectionString = strConn cnConn.Open Set cmCmd = New Command Set cmCmd.ActiveConnection = cnConn With cmCmd CommandType = adCmdStoredProc CommandText = "usp_GetCatTotals" Parameters.Append .CreateParameter("@CatID", adInteger, adParamInput, 4, pCatID) Parameters.Append .CreateParameter("@Start", adDBDate, adParamInput, 8, pStart) Parameters.Append .CreateParameter("@End", adDBDate, adParamInput, 8, pEnd) End With END CALLOUT A BEGIN CALLOUT B ` Создать поток для обработки отклика Set sResponseStream = New Stream sResponseStream.Open ` Ответный объект может находиться на странице ASP cmCmd.Properties("Output Stream") = sResponseStream END CALLOUT B cmCmd.Execute , , adExecuteStream BEGIN CALLOUT C ` Загрузить XML в синтаксический анализатор mobjXML.Load sResponseStream Call ShowXML END CALLOUT C Set cnConn = Nothing Set cmCmd = Nothing Set sResponseStream = Nothing End Sub
После того как будет создан объект Command, необходимо создать объект, который будет принимать результирующий документ XML. Поскольку выходной набор данных хранимой процедуры usp_GetCatTotals не является объектом ADO Recordset, а представляет собой документ XML, ADO возвращает данные через потоковый объект Stream. Объект Stream, который появился в версии ADO 2.5 вместе с объектом Record, позволяет ADO манипулировать данными, используя интерфейс IStream COM. Эта возможность позволяет ADO взаимодействовать с источниками данных, которые возвращают полуструктурированную информацию, такую как данные XML или бинарные данные. Чтобы проинструктировать ADO о том, что возвращать данные следует через объект Stream, сначала необходимо создать и открыть экземпляр объекта Stream. Затем следует указать объекту Command на объект Stream, используя для этого динамическое свойство Output Stream объекта Command. Как это сделать, показывает метка В листинга 2. Если используется объект, поддерживающий интерфейс Istream, то можно также применять и любой компонент СОМ, который тоже поддерживает интерфейс Istream. В рассматриваемом примере для отправки результатов работы объекта Command непосредственно в приложение с браузером использован объект ASP Response, который поддерживает интерфейс IStream. После этого процедура вызывает объект Command, передавая ему константу adExecuteStream, которая сообщает ADO о том, что результаты следует пересылать через выходной поток.
Чтобы извлечь документ XML из выходного потока и поместить его в строку, можно было бы воспользоваться принадлежащим объекту Stream методом ReadText. Однако в рассматриваемом примере процедура VB применяет программу синтаксического разбора XML под названием Microsoft XML Parser (MSXML) 2.6 для загрузки документа XML. После этого его можно просматривать при помощи Web-браузера, как показывает метка C листинга 2. Процедура VB воспользовалась объектом DOMDocument26, расположенным на уровне форм в объектной модели документа (Document Object Model, DOM). Экземпляр этого объекта порождается событием Load формы. Затем процедура использует метод Load, чтобы извлечь XML из объекта Stream и поместить в программу синтаксического разбора. Такой метод действует, поскольку можно передать методу Load объекта DOMDocument26 указатель, который поддерживает интерфейс IStream. Затем используется процедура ShowXML, приведенная в листинге 5. Она сохраняет XML во временном файле и загружает его в Web-браузер для просмотра, используя элемент управления OLE custom control (OCX). Обратите внимание на то, что загрузить OCX можно было бы напрямую из объекта, который поддерживает интерфейс IStream, однако VB не позволяет это сделать.
ЛИСТИНГ 5 Процедура ShowXML. Private Sub ShowXML ` Сохранить XML во временном файле mobjXML.Save "temp.xml" ` Открыть временный файл Web-браузером OCX WebBrowser1.Navigate2 "file://" & App.Path & "/temp.xml" End Sub
Версия 2.5 и более поздние версии ADO позволяют применять метод Save объекта Recordset для генерации XML. Однако использование базы данных для выполнения этой задачи упрощает код и позволяет избежать двойной обработки данных. Двойная обработка выполняется в том случае, если сначала возвращается стандартный набор записей ADO, а затем промежуточный слой программного обеспечения генерирует XML.
Генерировать XML из SQL Server 2000 средствами ADO можно также с применением шаблонов. Шаблон представляет собой документ XML, который определяет один или несколько запросов, подлежащих обработке в SQL Server. Запросы могут быть написаны как на SQL, так и на языке XML Path Language (XPath). Хотя обычно выполняются шаблоны, посылаемые клиентами Web (такая технология называется доступом через URL), точно так же можно выполнять шаблоны, посылаемые клиентстким экземпляром ADO.
В листинге 3 показан шаблон, который вызывает хранимую процедуру usp_GetCatTotals и передает ей ряд параметров. Для вызова хранимой процедуры из шаблона применяется такой же синтаксис, как и для вызова хранимой процедуры из T-SQL. Сначала определяются параметры и их значения, затем для передачи параметров хранимой процедуре применяется предложение EXEC. Как показывает метка А листинга 3, шаблон должен обязательно иметь корневой элемент ROOT, хотя назвать его можно не ROOT, а иначе. После того как будет установлено соединение с SQL Server при помощи объекта Connection и после настройки объекта Command, содержимое шаблона записывается в объект ADO Stream. В дальнейшем этот объект будет исполняться объектом Command. Чтобы записать содержимое шаблона в объект Stream, сначала генерируется экземпляр этого объекта, а затем принадлежащий объекту Stream метод WriteText переносит содержимое шаблона в объект Stream. Этому соответствует метка В листинга 3. Последняя строчка кода данного раздела листинга использует свойство Position, чтобы вернуть указатель на начало объекта Stream. Это необходимо для того, чтобы последующие операции чтения информации из потока возвращали данные.
ЛИСТИНГ 3: Template That Calls usp_GetCatTotals to Return XML Private Sub OpenStream() Dim cnConn As Connection Dim strConn As String Dim sStreamQuery As Stream Dim cmCmd As Command Dim strQuery As String Dim sResponseStream As Stream Set cnConn = New Connection strConn = "Provider=SQLOLEDB;Data Source=ssosa\sql2000;Initial Catalog=Northwind;trusted_connection=yes" cnConn.ConnectionString = strConn cnConn.Open Set cmCmd = New Command Set cmCmd.ActiveConnection = cnConn BEGIN CALLOUT A ` Создать шаблон запроса strQuery = "<ROOT xmlns:sql=`urn:schemas-microsoft-com:xml-sql`>" strQuery = strQuery & "<sql:header>" strQuery = strQuery & "<sql:param name=`CatID`>1</sql:param>" strQuery = strQuery & "<sql:param name=`Start`>7/5/96</sql:param>" strQuery = strQuery & "<sql:param name=`End`>7/5/97</sql:param>" strQuery = strQuery & "</sql:header>" strQuery = strQuery & "<sql:query >" strQuery = strQuery & "exec usp_GetCatTotals @CatID, @Start, @End" strQuery = strQuery & "</sql:query>" strQuery = strQuery & "</ROOT>" END CALLOUT A BEGIN CALLOUT B ` Считать шаблон запроса в объект Stream Set sStreamQuery = New Stream sStreamQuery.Open sStreamQuery.WriteText strQuery, adWriteChar sStreamQuery.Position = 0 END CALLOUT B BEGIN CALLOUT C ` Установить связь потока и команды и ` выбрать диалект XML для интерпретации Set cmCmd.CommandStream = sStreamQuery cmCmd.Dialect = "{5D531CB2-E6Ed-11D2-B252-00C04F681B71}" END CALLOUT C ` Создать поток для обработки отклика Set sResponseStream = New Stream sResponseStream.Open ` Ответный объект может находиьтся на странице ASP cmCmd.Properties("Output Stream") = sResponseStream cmCmd.Execute , , adExecuteStream Call ShowXML(sResponseStream.ReadText) End Sub
Перед началом исполнения объекта Command шаблон при помощи свойства CommandStream устанавливает ассоциацию между объектом Stream, содержащим шаблон, и объектом Command. В листинге 3 это место помечено символом С. Свойство CommandStream позволяет объекту Command использовать содержимое объекта Stream в качестве исходного текста команды. Шаблон также присваивает значение свойству Dialect, чтобы сообщить ADO, что содержимое объекта Stream следует интерпретировать как XML. Как и рассмотренная ранее процедура VB OpenProc, шаблон возвращает результаты в выходном потоке.
Третий способ генерации XML при помощи ADO базируется на использовании представлений XML и запросов на языке XPath. Представление XML является схемой XML, содержащей дополнительную информацию, которая позволяет процессору запросов SQL Server определить, какие таблицы надо будет использовать при обработке запроса. Для задания схемы применяется XDR со специальными аннотациями, указывающими, какие элементы схемы соответствуют каким таблицам. Аннотации можно также использовать для определения отношений между элементами, например, отношений внешнего ключа. Они помогают поставщику SQLOLEDB строить обороты JOIN для корректного возврата данных. В листинге 4 приведена схема XDR с аннотациями, которая создает представление XML. В этом представлении у элемента вершины Order имеются подчиненные элементы, содержащие информацию о заказах (Order Details) и заказчиках (Customer). На рисунке 3 показан документ XML, который поставщик SQLOLEBDB генерирует на основании схемы XDR.
ЛИСТИНГ 4: Схема XDR Schema с аннотациями, используемая для создания представления XML. <?xml version="1.0" ?> <Schema xmlns="urn:schemas-microsoft-com:xml-data" xmlns:dt="urn:schemas-microsoft-com:datatypes" xmlns:sql="urn:schemas-microsoft-com:xml-sql"> <ElementType name="OrderDetail" sql:relation="[Order Details]" > <AttributeType name="ProductID" /> <AttributeType name="Quantity" /> <AttributeType name="UnitPrice" /> <attribute type="ProductID" /> <attribute type="Quantity" /> <attribute type="UnitPrice" /> </ElementType> <ElementType name="Customer" sql:relation="Customers"> <AttributeType name="CustomerID" /> <AttributeType name="ContactName" /> <attribute type="CustomerID" /> <attribute type="ContactName" /> </ElementType> <ElementType name="Order" sql:relation="Orders" sql:key-fields="OrderID"> <AttributeType name="OrderID" /> <AttributeType name="OrderDate" /> <attribute type="OrderID" /> <attribute type="OrderDate" /> <element type="OrderDetail"> <sql:relationship key-relation="Orders" key="OrderID" foreign-key="OrderID" foreign-relation="[Order Details]" /> </element> <element type="Customer" > <sql:relationship key-relation="Orders" key="CustomerID" foreign-relation="Customers" foreign-key="CustomerID" /> </element> </ElementType> </Schema>
Обсуждение синтаксиса аннотаций выходит за рамки данной статьи, но комбинирование схем XDR и аннотаций позволяет создавать весьма сложные схемы, не прибегая к синтаксису оборота FOR XML EXPLICIT. SQL Profiler показывает, что когда используются схемы XDR с аннотациями, поставщик SQLOLEDB самостоятельно формирует синтаксис FOR XML EXPLICIT. В дополнение к этому схемы с аннотациями удобно применять в следующих ситуациях. Предположим, что торговый партнер присылает вам для использования свою схему. Можно создать аннотированную версию схемы, которую приложения будут выполнять в SQL Server, создавая документ XML, необходимый вашему партнеру. Этот процесс намного проще применения оборота FOR XML EXPLICIT для генерации корректно отформатированного документа XML.
Как только схема определена, к ней можно обращаться с запросами XPath. Язык XPath предназначен для выборки узлов в документе XML. Более подробная информация о XPath содержится в спецификации "XML Path Language (XPath) Version 1.0: W3C Recommendation 16 November 1999", размещенной по адресу http://www.w3.org/tr/xpath.
ЛИСТИНГ 6: Процедура VB, которая исполняет запрос XPath Query к представлению XML, созданному в листинге 4. Private Sub OpenView(ByVal pOrderID) Dim cnConn As Connection Dim strConn As String Dim cmCmd As Command Dim sResponseStream As Stream Set cnConn = New Connection strConn = "Provider=SQLOLEDB;Data Source=ssosa\sql2000;Initial Catalog=Northwind;trusted_connection=yes" cnConn.ConnectionString = strConn cnConn.Open Set cmCmd = New Command Set cmCmd.ActiveConnection = cnConn BEGIN CALLOUT A cmCmd.CommandText = "Order[@OrderID=" & pOrderID & "]" cmCmd.Dialect = "{EC2A4293-E898-11D2-B1B7-00C04F680C56}" END CALLOUT A BEGIN CALLOUT B cmCmd.Properties("Mapping Schema") = "CustOrders.xml" cmCmd.Properties("Base Path") = App.Path & "\" END CALLOUT B ` Create a stream to handle the response Set sResponseStream = New Stream sResponseStream.Open ` Can also be a response object in an ASP page cmCmd.Properties("Output Stream") = sResponseStream cmCmd.Execute , , adExecuteStream Call ShowXML(sResponseStream.ReadText) End Sub
В листинге 6 приведена хранимая процедура, исполняющая следующий запрос XPath к схеме, созданной в листинге 4, для конкретного значения идентификатора заказа OrderID:
Order[@OrderID=?]
Первая часть запроса, Order, определяет запрашиваемый элемент. Вторая часть, @OrderID=?, задает запрашиваемый атрибут предиката. Знак вопроса показывает, что будет указан параметр, которому должно соответствовать значение OrderID. При использовании ADO для отправки запроса XPath в SQL Server, следует просто воспользоваться свойством CommandText объекта Command и присвоить такое значение свойству Dialect, чтобы ADO «знал», что будет исполняться запрос XPath:
cmCmd.CommandText = "Order[@OrderID=" & pOrderID & "]"
cmCmd.Dialect = "{EC2A4293-E898-11D2-B1B7-00C04F680C56}"
Приведенные операторы передают требуемое значение OrderID в запрос XPath через параметр pOrderID.
Хранимая процедура должна также сообщить ADO о местоположении схемы XDR, которая называется еще схемой отображения. Имя схемы задается в динамическом свойстве Mapping Schema объекта Command, а путь к файлу указывается в свойстве Base Path:
cmCmd.Properties("Mapping Schema") = "CustOrders.xml"
cmCmd.Properties("Base Path") = App.Path & "\"
Эта процедура также применяет объект VB App для идентификации базового пути, поскольку схема отображения размещается в том же каталоге, что и исполняемый файл VB. Как и в приведенных выше примерах, хранимая процедура использует объект Stream для получения возвращаемого документа XML.
Все три рассмотренные методики основаны на использовании возможностей ADO для получения документов XML из баз данных SQL Server. Описанные подходы являются альтернативой как жестко запрограммированной генерации XML в хранимых процедурах и страницах ASP, так и применению менее эффективного метода Save. Использование ADO может значительно повысить производительность труда программистов и облегчить взаимодействие всего окружения баз данных SQL Server с остальным миром, связь с которым осуществляется через сеть.