XML
в MS SQL Server 2000 и технологиях доступа к данным.
Автор: Алексей Шуленин
Несколько слов о том, что за текст попался вам на глаза и
стоит ли вам его читать, а мне, соответственно, браться сейчас
писать. Ну поддерживает SQL Server XML, ну так что с того? Его
сейчас поддерживают все, кому не лень, потому что это круто. А
если разобраться по существу, то с какого бока приличному
серверу баз данных этот XML вообще сдался? Вот с этого
философского вопроса, пожалуй, и начнем. С моей сугубо
прагматичной точки зрения это, конечно, не дань моде. Наши с
вами реалии сегодня таковы, что большинство корпоративных
бизнес-сценариев давно вышли за рамки локальных сетей и
предусматривают работу с базами данных через Интернет. Кроме
того, эта работа ведется чаще всего в гетерогенных средах, где
перемешаны и Windows, и Linux, и Solaris, и FreeBSD, и много
чего еще. Во-первых, представление как запроса, так и его
результатов в виде XML существенно упрощает передачу данных
через межсетевые экраны. Понятно, например, что передать
recordset как COM-объект через брандмауэр скорее всего не
удастся, потому что ни один администратор в здравом уме не
откроет порты для произвольных RPC-вызовов снаружи. Издавна
придумывались лазейки и средства: вспомните, например, Remote
Data Service (RDS), появившуюся еще в составе ADO 1.5 во
второй половине 1997 г. (а ее предшественник Advanced Data
Connector - ADC - и того раньше). Она позволила-таки
маршалировать recordset'ы через HTTP и DCOM, хотя и не скажу,
что это было тривиально. Сериализация объекта в XML решает эту
задачу легко. Во-вторых, XML и HTTP, являясь де юре и де факто
общепринятыми стандартами, упрощают взаимодействие между
базами данных различных производителей, на какой бы платформе
и ОС они ни стояли. Причем, даже не только между базами
данных, но и напрямую с серверами электронной коммерции и
бизнес-интеграции. Например, с Commerce Server, BizTalk Server
и др. Причем не только с серверами промежуточного слоя, но и
вообще между гетерогенными приложениями, поскольку SOAP легко
решает извечную задачу мостования между СОМ и CORBA. Впрочем,
о SQL Server как о веб-сервисе мы еще поговорим. В-третьих,
преимущество поддержки XML в СУБД состоит в том, что на
компьютер конечного пользователя или приложения, работающего с
базой данных, не требуется устанавливать никакой клиентской
части, специфичной для данной СУБД, т.к. все, что ему нужно, -
это стандартные протоколы и форматы Интернета, априори
поддерживаемые практически всеми современными платформами.
| |||
2 Сохранить как XML |
Впервые возможность сохранять (есть еще замечательное слово
"персистить") результаты запроса в виде XML появилась в ADO
2.1 (1999 г.) До этого в ADO 2.0 объект Recordset сохранялся
только в частном бинарном формате ADTG (Advanced Data
TableGram), который использовался для передачи recordset'a при
удаленном доступе с помощью RDS.
Скрипт 1 Получается действительно нормальный XML, как видно на
Рис.1. Его можно открыть при помощи объектной модели DOM,
выполнить XPath-запрос, возвращающий узлы заказов, сделанные
клиентом по имени Maria Larsson, и другие подобающие XML
действия. Что-то можно заложить в первоначальный
SQL-запрос: SELECT count(1) FROM Customers c INNER JOIN Orders o ON
c.CustomerID = o.CustomerID WHERE c. ContactName = 'Maria
Larsson' но согласитесь, чтобы показать работу с сохраненным
recordset'ом как с XML, часть работы для приличия надо
проделать средствами XPath, а не SQL. По соображениям экономии
места данный пример, как и все последующие, написаны на C#.
Обратите внимание, что в нем используется не ADO.Net, а
классическая объектная модель ADO (2.7). Для нее не требуется
делать tlbimp из библиотек классов в ... \Program Files\Common
Files\System\ado, потому что соответствующая обертка
существует изначально как Primary Interop Assembly. |
3 Получить как поток XML |
В рассмотренном примере XML передавался от ADO к DOM через внешний файл. В ADO 2.5 появилась возможность сохранения recordset'a в виде XML не только в файл, но и в любой объект, поддерживающий интерфейс IStream. В Скрипте 2 static void
Save_ADODBRecordset_XMLStream()
{ ... MSXML2.DOMDocument40Class xmlDoc = new MSXML2.DOMDocument40Class(); rst.Save(xmlDoc, ADODB.PersistFormatEnum.adPersistXML); cnn.Close(); MSXML2.DOMDocument40Class xslDoc = new MSXML2.DOMDocument40Class(); xslDoc.loadXML("<?xml version='1.0' ?>" + "<doc xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xsl:version='1.0'>" + "<xsl:copy-of select='//*[@ContactName=\"Maria Larsson\"]'/></doc>"); FileInfo f = new FileInfo("..\\Results\\ADODBRecordset_XMLStream.xml"); StreamWriter sw = f.CreateText(); sw.Write(xmlDoc.transformNode(xslDoc)); sw.Close(); ... }
recordset записывается в поток объекта DOMDocument из
СОМовской библиотеки MSXML4 (WINNT\System32\msxml4.dll).
Полученный из recordset'a xmlDoc затем подвергается
XSLT-преобразованию, заданному в xslDoc, которое выбирает все
элементы, относящиеся к заказчику Maria Larsson. Полученная в
результате преобразования строка записывается в файл, который
отображается в браузере. | |
4 XML на стороне сервера. FOR XML |
Следующая ступень эволюции - ADO 2.6 и SQL Server 2000. В SQL Server 2000 в синтаксис Т-SQL был добавлен предикат FOR XML для оператора SELECT, что позволило получать XML-текст как результаты запроса на стороне сервера. Рассмотрим запрос SELECT c.ContactName, c.ContactTitle, o.OrderDate FROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID FOR XML AUTO. Вначале SQL Server традиционным способом выполняет ту часть запроса, которая находится до FOR XML. Затем к полученному множеству записей сервер применяет преобразование в XML. Если выполнить этот запрос из Query Analyzer, то видно, что содержимое XML-документа разбито по записям длиной 2033 символа Unicode одноколоночного recordset'а. Вообще говоря, это не есть ни recordset, ни XML. Его нельзя использовать как результат подзапросов, хранимых функций и всего остального, что предполагает дальнейшую обработку на SQL Server. Это нечто предназначено только для передачи клиенту, где из него уже происходит сборка полноценного документа. Таким образом, несмотря на то, что в отличие от Скриптов 1 и 2, в Скрипте 3 XML фактически получается на сервере, все XPath-, updategrams- и прочие запросы выполняются на клиенте, поскольку встроенный тип XML в настоящее время в SQL Server отсутствует.
Скрипт 3 выполняет тот же запрос, что и в предыдущих
примерах, за исключением того, что я его слегка разнообразил
передачей параметров: он показывает список всех заказов,
сделанных определенным клиентом за определенный год. Результат
заключается в скобки <Root> … </Root> для
получения well-formed документа. Вместо этого можно
использовать cmd.Properties["xml root"].Value = "Root". XML
возвращается на клиента в объекте ADODB.Stream. Его можно
сохранить сразу в файл - str.SaveToFile(f.FullName,
ADODB.SaveOptionsEnum.adSaveCreateOverWrite); , а можно
передать как поток в документ типа MSXML2.DOMDocument40Class.
В любом случае понятно, что над результатами запроса можно
вести дальнейшую работу средствами DOM. Аналогично Скрипту 2
для работы с XML-документами здесь используется библиотека
СОМ, а не .NET, поскольку ADODB.Stream нельзя преобразовать к
System.IO.Stream, чтобы загрузить в System.Xml.XmlDocument.
Передавать же, сохраняя в промежуточный файл, как делалось в
Скрипте 1, можно, но неизящно. Свойство Dialect класса
ADODB.Command говорит провайдеру, какой тип команды
используется. Возможные значения приведены в Табл.1
Табл.1 Объекты типа Stream появились в ADO 2.5 для поддержки
некоторых типов нереляционной информации. Вспомним, что
первоначальная спецификация OLE DB в 1996 г. описывала
универсальный доступ в рамках прямоугольных recordset'ов для
обеспечения совместимости с ранними API, имевшими дело с
реляционными данными (ODBC, DB-Library, DAO). Однако несмотря
на развитость аппарата реляционной алгебры далеко не все
источники удавалось свести к этому классу. Да и потом,
иерархические базы данных все-таки предшествовали реляционным,
поскольку эта модель, видимо, более естественно отвечает
образу мышления. Время шло, диссонанс между
объектно-ориентированной средой разработки и реляционной
архитектурой хранения (плоские таблицы, связанные отношениями)
проявлялся все более отчетливо. В ADO 1.5 была сделана попытка
сгладить эту проблему с помощью провайдера MSDataShape.
Спецификация OLE DB 1.5 предусматривала новый тип полей -
Chapter column. Предполагалось, что индивидуальные уровни
иерархии можно представить в виде отдельных rowset'ов, и
chapter указывает для родительской записи множество ее детей в
дочернем rowset'e. Однако все это было хорошо в однородных
иерархиях, когда дочерняя запись имеет тот же набор полей, что
и родительская. Например, очевидный негомогенный источник -
файловая система - сюда уже не вписывается. Чтобы уложить
древовидную структуру в прямоугольную с минимальными
переделками и потерями производительности (скажем, не заводя
для каждой записи число полей, соответствующее полному набору
всех возможных атрибутов узла в дереве, большая часть из
которых будет, очевидно, пустовать) в ADO 2.5 были введены
классы Record и Stream. Набор полей (атрибутов) записи (файла)
может разниться не только с родительской записью (папкой), но
и меняться от записи к записи (например, в зависимости от типа
файла). Наименьший общий знаменатель полей, присущих всем
записям, формировал колонки привычного Recordset. Класс Stream
соответствовал содержанию файла. Таким образом, появилась
возможность получения результата запроса в виде потoка, чем мы
и воспользовались в Скрипте 3. XML естественным образом решает
проблему представления древовидных иерархий. Как и RDS,
MSDataShape в настоящее время поддерживается по соображениям
совместимости, но развиваться в дальнейшем не будет. | ||||||||||||||||
5 SQLXML-классы в .NET |
После выхода SQL Server 2000 в августе 2000 г.
дополнительная функциональность, в плане развития поддержки
XML, выпускалась в виде веб-релизов под названием SQLXML.
SQLXML 1.0 содержал Updategrams, XML Bulkload и
усовершенствования в части производительности. В версии 2.0
ввели аннотированные XSD-схемы, FOR XML на стороне клиента,
SQLXMLOLEDB-провайдер и SQLXML Managed Classes (в плане
интеграции с ADO.Net). Наконец, веб-релиз версии 3.0, вышедший
в марте 2002 г. и последний на момент подготовки доклада,
добавляет к этому поддержку SOAP, превращая SQL Server в
Web-сервис. Некоторые из перечисленных возможностей будут
здесь разобраны.
Логика примера не изменилась - выполняется SQL-запрос,
результаты получаются в виде XML и передаются для последующей
возможной работы в System.Xml.XmlDocument (аналог
DOMDocument). Кроме того, для целей демонстрации XML-поток
сохраняется в файл, который затем открывается в браузере.
Класс SqlXml.Command для получения результатов в виде
XML-потока имеет два метода: ExecuteStream() и
ExecuteToStream(). Их отличие состоит в том, что первый
создает новый Stream, а второй пишет в уже существующий. Кроме
того, результаты запроса можно сразу получить как XmlReader
(метод ExecuteXmlReader). В отличие от класса XmlDocument
(DOM) XmlReader является более легковесным, он не требует
загрузки всего документа в память. В терминологии баз данных
его ближайшим аналогом будет forward-only read-only курсор.
Свойство Dialect класса Command в ADO соответствует свойству
CommandType, а Output Encoding и XML Root перешли из
расширенных свойств в стандартные (OutputEncoding и
RootTag). | |
6 FOR XML на стороне клиента |
Провайдер SQLXMLOLEDB является типичным сервисным провайдером, как уже упоминавшиеся ранее MSDataShape и MSPersist, в том плане, что он получает данные от другого провайдера, а не непосредственно из источника. SQLXMLOLEDB позволяет преобразовывать recordset в XML непосредственно на клиенте. Таким образом, провайдер SQLOLEDB, стоящий между SQLXMLOLEDB и SQL Server, получает от сервера не XML-поток, а обычный recordset и передает его SQLXMLOLEDB, который уже занимается превращением реляционной информации в XML. Скрипт 5 демонстрирует FOR XML на стороне клиента.
При помощи SQL Profiler можно оттрассировать и сравнить
запросы, которые в действительности обрабатываются сервером
при выполнении Скриптов 4 и 5. Скрипт 4:
Скрипт 5:
В Скрипте 5 я слегка изменил текст запроса, чтобы показать
использование GROUP BY (этот предикат не разрешается в
серверных FOR XML-запросах). Запрос считает количество
заказов, сделанных каждым клиентом за определенный период
времени. Обратите внимание на отсутствие предиката FOR XML во
втором случае. Это значит, что преобразование recordset'a в
XML действительно происходит в данном случае на
клиенте.
поставить вместо SQL Server соответствующего OLE
DB-провайдера. К сожалению, для этого придется подождать
следующего, 4-го, веб-релиза. Пока SQLXMLOLEDB умеет работать
только с SQL Server 2000. | |
7 XML-представление наборов данных в ADO .NET |
На самом деле даже без провайдера SQLXMLOLEDB и SQLXML веб-релизов в Visual Studio .Net (точнее, в ADO.Net) имеются достаточно мощные средства для представления реляционных наборов данных в виде XML, и наоборот, XML в реляционном виде. Типовой сценарий работы выглядит следующим образом: получить внутри объекта DataSet таблицы как результаты запросов к источнику данных (возможно, к разным), связать их между собой на основе объектов DataRelation и создать XML-представление DataSet'a при помощи XmlDataDocument, как показано в Скрипте 6.
Результирующий XML можно видеть на рис.2.
По умолчанию из DataSet будет сгенерирован документ, в
котором каждой записи DataRow соответствует элемент с именем
DataTable. Значения полей присутствуют в виде подэлементов
DataRow с названиями соответствующих полей DataColumns.
Поскольку DataSet предполагает отсоединенный режим работы,
отношения между таблицами в источнике (в БД на SQL Server) не
принимаются во внимание. Так, несмотря на связывание таблиц в
запросе: (new OleDbDataAdapter("SELECT c.ContactName,
c.ContactTitle, o.OrderDate FROM Customers c INNER JOIN Orders
o ON c.CustomerID = o.CustomerID",
Connection)).Fill(ds); с точки зрения DataSet это плоское множество записей,
потому что связи отработал сервер и прислал в DataSet готовый
табличный результат. Для образования иерархического
XML-документа, где записи дочерней таблицы являются вложенными
элементами родительской, отношение между таблицами нужно
указывать явно в DataSet.Relations, при этом свойство .Nested
объекта DataRelation должно быть выставлено в true. (Иначе
записи из родительской и дочерней таблиц будут перечислены
друг за другом на одном и том же уровне иерархии). Класс
XmlDataDocument является производным от DOMовского
XmlDocument, поэтому с его помощью над DataSet'ом можно
выполнять все стандартные XML-операции: XPath-запросы,
XSL-преобразования и т.д.
Скрипт 7 демонстрирует, что данные источника можно
модифицировать не только напрямую через DataSet
(ds.Tables[<Имя или номер таблицы в
коллекции>].Rows[<Номер записи в коллекции>][<Имя
или номер поля в коллекции>] = …), но и через его
XML-представление. В примере изменяются значений некоторых
XPath-узлов объекта XmlDataDocument. Эти изменения отражаются
в DataSet'е, над которым построен данный XmlDataDocument, а
поскольку у соответствующих DataAdapter'ов таблиц определены
UpdateCommand'ы, то они будут транслированы далее в источник
данных (SQL Server). Обратное тоже верно. Т.е. в DataSet можно
подгрузить XML-документ, который затем читать и модифицировать
реляционными операциями. В Скрипте 8 мы получаем схему
построенного в предыдущем примере XML-файла при помощи утилиты
xsd.exe, входящей в состав .NET Framework, читаем ее в
XmlDataDocument и загружаем туда данные из этого документа. На
основе XSD-схемы ADO.Net создает DataSet эквивалентной
реляционной структуры, так что становится возможным обращаться
и модифицировать XML-документ, как если бы он был
совокупностью связанных таблиц.
Неплохим иллюстративным примером было бы приложение,
которое документирует пользовательские библиотеки классов .Net
в базе данных. Определения классов и объекты сохраняются в
виде XSD-схем и XML-документов (см. System.Xml.Serialization),
а на их основе, в свою очередь, при помощи рассмотренного
соответствия реляционного и XML-представлений, которое
обеспечивает ADO.Net, создается и наполняется БД. В качестве
самостоятельного упражнения вы можете попробовать сами
написать такое приложение и назвать его, скажем,
Cheops. | |
8 Прямые XPath-запросы к объектам SQL Server |
В Скрипте 7 было показано, как осуществлять XPath-навигацию по связанным таблицам в ADO.Net Dataset. Подобным же образом XPath-запросы можно адресовать к SQL Server 2000, как если бы это был XML-ресурс, а не сервер реляционных баз данных. Под словом "прямые" подразумевается, что эти запросы обращаются к объектам базы данных напрямую, а не через аннотированные схемы, о которых речь пойдет в следующем параграфе. В Скрипте 9 приведен запрос, выводящий всех клиентов с именами, начинающимися с букв X, Y, Z. static void
Direct_XPathQuery_SQLXML()
Обратите внимание на разное именование параметра в XPath-запросе и в параметрах объекта команды. Если посмотреть, во что XPath превращается на сервере: exec sp_executesql N' SELECT ContactName FROM Customers WHERE ContactName>=@НачБуква ', N'@НачБуква nvarchar(1)', N'X', то видно, что первая @ автоматически получается из $ при
переводе XPath-запроса в SQL, а о второй нужно позаботиться
самим в приложении (SqlXmlParameter.Name), иначе sp_executesql
его попросту не поймет. | |
9 Аннотированные схемы |
По своей структуре это обычные XSD-схемы, в которых допускаются специальные аннотации, задающие их привязку к сущностям реляционной структуры: таблицам, полям, первичным и внешним ключам, отношениям между таблицами и т.д., благодаря чему данные, хранящиеся на SQL Server, можно привести к желаемой XSD- структуре и запрашивать затем как XML-документ (XPath, XQuery). В VS .Net входит удобный редактор XSD-схем, позволяющий собирать их, натаскивая drag-n-drop'ом элементы, атрибуты, типы и т.д. из панели инструментов. Редактор имеет две панели: одна показывает традиционный XML-код схемы, а другая - ее реляционный эквивалент в виде таблиц и связей между ними. При переключении происходит автоматическая валидация схемы, доступная также из меню (Schema -> Validate). В нем есть цветовое выделение синтаксических конструкций, intelisense-подсказки и многие другие приятные вещи. Итак, с помощью этого замечательного редактора я создаю вид моего XML-документа, который будет содержать, допустим, информацию по клиентам и сделанных ими заказах. <?xml version="1.0" encoding="utf-8" ?> <xs:schema id="XMLSchema1" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="Клиент"> <xs:sequence> <xs:element name="Адрес" type="Адрес" /> <xs:element name="Заказы" type="Заказы" /> </xs:sequence> <xs:attribute name="Имя" type="xs:string" /> <xs:attribute name="Должность" type="xs:string" /> <xs:attribute name="Фирма" type="xs:string" /> </xs:complexType> <xs:complexType name="Адрес"> <xs:sequence> <xs:element name="Страна" type="xs:string" /> <xs:element name="Город" type="xs:string" /> <xs:element name="Улица_дом" type="xs:string" /> <xs:element name="Индекс" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:complexType name="Заказы"> <xs:sequence> <xs:element name="Заказ" type="Заказ" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> <xs:complexType name="Заказ"> <xs:sequence> <xs:element name="Дата" type="xs:date" /> <xs:element name="Стоимость" type="xs:float" /> </xs:sequence> </xs:complexType> </xs:schema>
Теперь, чтобы по этой схеме представить данные из SQL Server, сопоставим их элементам и атрибутам при помощи аннотаций - см. рис.4 - для поддержки которых в схеме делается ссылка на соответствующее пространство имен (xmlns:ms="urn:schemas-microsoft-com:mapping-schema"). <?xml version="1.0" encoding="utf-8" ?> <xs:schema id="SQLSchema1" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ms="urn:schemas-microsoft-com:mapping-schema"> <xs:annotation> <xs:appinfo> <ms:relationship name="CustOrds" parent="Customers" parent-key="CustomerID" child="Orders" child-key="CustomerID" /> </xs:appinfo> </xs:annotation> <xs:complexType name="Клиент"> <xs:sequence> <xs:element name="Адрес" type="Адрес" ms:is-constant="1" /> <xs:element name="Заказы" type="Заказы" ms:is-constant="1" /> </xs:sequence> <xs:attribute name="Имя" type="xs:string" ms:field="ContactName" /> <xs:attribute name="Должность" type="xs:string" ms:field="ContactTitle" /> <xs:attribute name="Фирма" type="xs:string" ms:field="CompanyName" /> </xs:complexType> <xs:complexType name="Адрес"> <xs:sequence> <xs:element name="Страна" type="xs:string" ms:field="Country" /> <xs:element name="Город" type="xs:string" ms:field="City" /> <xs:element name="Улица_дом" type="xs:string" ms:field="Address" /> <xs:element name="Индекс" type="xs:string" ms:mapped="false" /> </xs:sequence> </xs:complexType> <xs:complexType name="Заказы"> <xs:sequence> <xs:element name="Заказ" type="Заказ" minOccurs="0" maxOccurs="unbounded" ms:relation="Orders" ms:relationship="CustOrds" /> </xs:sequence> </xs:complexType> <xs:complexType name="Заказ"> <xs:sequence> <xs:element name="Дата" type="xs:date" ms:field="OrderDate" /> <xs:element name="Стоимость" type="xs:float" ms:field="Freight" /> </xs:sequence> <xs:attribute name="Номер" type="xs:string" ms:field="OrderID" /> </xs:complexType> <xs:element name="Клиент" type="Клиент" ms:relation="Customers" /> </xs:schema>
Аннотация sql:relation используется для отображения узла на
таблицу. Она не поддерживается в тэгах определения типа, т.е.
только в xs:element и xs:attribute, поэтому нам пришлось ввести
в схему элемент составного типа "Клиент": <xs:element name="Клиент" type="Клиент" ms:relation="Customers" />. Вложенные
элементы соответствуют записям дочерней таблицы, поэтому для
них требуется еще задать ms:relationship. Отношения между таблицами в
терминах родительской и дочерней таблиц (parent / child) и полей, по которым
устанавливается связь (parent-key /
child-key), определяются как атрибуты элемента <ms:relationship> в разделе
определения аннотаций <xs:annotation>, <xs:appinfo>. Затем это
отношение можно использовать, чтобы вложить дочерние записи
внутрь родительского элемента <xs:element name="Заказ" type="Заказ"... ms:relation="Orders" ms:relationship="CustOrds"/>. Если
вложенный элемент не соответствует никакой дочерней таблице, а
несет чисто контейнерную функцию (как, например, Адрес), то он помечается атрибутом ms:is-constant="1":
<xs:element
name="Адрес" type="Адрес" ms:is-constant="1"/>. Аннотация ms:field
привязывает XML-узел к полю таблицы. Она не требуется, когда
название атрибута совпадает с названием поля. Непривязанные
атрибуты также не допускаются. Если мы не планируем брать
значение узла из БД, но в силу каких-либо причин не можем
исключить его из схемы, его нужно пометить аннотацией ms:mapped="false":
<xs:element
name="Индекс" type="xs:string" ms:mapped="false" />. От is-constant она отличается тем, что узел
вообще исключается из результирующего
XML-документа. <?xml version="1.0" encoding="utf-8" ?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ms="urn:schemas-microsoft-com:mapping-schema" id="SQLSchema2"> <xs:annotation> <xs:appinfo> <ms:relationship name="Начальник-Подчиненный" parent="Employees" parent-key="EmployeeID" child="Employees" child-key="ReportsTo" /> </xs:appinfo> </xs:annotation> <xs:element name="Сотрудник" type="Тип_Сотрудник" ms:relation="Employees" ms:key-fields="EmployeeID" ms:limit-field="ReportsTo" /> <xs:complexType name="Тип_Сотрудник"> <xs:sequence> <xs:element name="Сотрудник" type="Тип_Сотрудник" ms:relation="Employees" ms:key-fields="EmployeeID" ms:relationship="Начальник-Подчиненный" ms:max-depth="3" /> </xs:sequence> <xs:attribute name="ID_сотрудника" type="xs:ID" ms:field="EmployeeID" ms:hide="true" /> <xs:attribute name="Имя" type="xs:string" ms:field="FirstName" /> <xs:attribute name="Фамилия" type="xs:string" ms:field="LastName" /> </xs:complexType> </xs:schema>
Он взят из документации к SQLXML 3.0. Таблица Employees
содержит записи по сотрудникам и связана сама с собой, т.е. в
поле ReportsTo для каждого сотрудника указывается EmployeeID
его начальника. static void Annotated_XPathQuery_SQLXML() { ... cmd.CommandText = "Клиент[Адрес/Страна='Spain' or Адрес/Страна='France']"; cmd.SchemaPath = "..\\Schemas\\SQLSchema1.xsd"; cmd.CommandType = SqlXmlCommandType.XPath; cmd.RootTag = "Клиенты"; XmlDocument xml = new XmlDocument(); xml.Load(cmd.ExecuteStream()); ... }
В Скрипте 10, как и в предыдущем примере (Скрипт 9), на SQL Server посылается XPath-запрос, однако теперь данные рассматриваются через призму выбранной аннотированной схемы (указывается в свойстве SqlXmlCommand.SchemaPath) и трактуются в соответствии с задаваемой ею структурой. В данном случае запрос выбирает всех клиентов из Испании и Франции и сделанные ими заказы. Встроенной поддержкой XPath (а также XQuery) SQL Server в настоящее время не располагает, поэтому XPath по дороге превращается в то, что ему более понятно, а именно - в SQL-запрос. Если быть совсем точным, то в запрос типа FOR XML EXPLICIT. Ради любопытства можете открыть Profiler и посмотреть его для Скрипта 10 (здесь я его приводить не буду, потому что он занял бы еще как минимум страницу). Поддерживается подмножество стандартного синтаксиса XPath в части осей, функций и операторов. Отрадно, что каждым SQLXML веб-релизом это подмножество расширяется. | |
Шаблоны (templates) объединяют в себе квантово-волновой (SQL / XML) дуализм SQL Server. Козьма Прутков мог бы смело их уподобить хранимым процедурам, которые допускают как SQL-, так и XPath-запросы. Шаблон - это XML-файл, структура которого показана на рис.6. <?xml version="1.0" encoding="utf-8" ?> <Солянка xmlns:sql="urn:schemas-microsoft-com:xml-sql"> <sql:header> <sql:param name="Колво"></sql:param> <sql:param name="Сумма">1000</sql:param> </sql:header> <sql:query client-side-xml="0"> SELECT TOP 1 * FROM Employees FOR XML AUTO </sql:query> <sql:query client-side-xml="1"> SELECT c.ContactName, COUNT(o.OrderDate) AS 'Кол-во' FROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID GROUP BY c.ContactName HAVING COUNT(o.OrderDate) >= @Колво FOR XML NESTED </sql:query> <sql:xpath-query mapping-schema="..\Schemas\SQLSchema1.xsd"> Клиент[number(Заказы/Заказ/Стоимость)>$Сумма] </sql:xpath-query> <sql:xpath-query mapping-schema="..\Schemas\SQLSchema2.xsd"> Сотрудник </sql:xpath-query> </Солянка>
Он состоит из частей трех основных типов. Каждая часть является опциональной. В <sql:query> перечисляются операторы SQL, в <sql:xpath-query> - запросы XPath, в <sql:header> описываются параметры для запросов (как SQL, так и XPath). Привязка описаний параметров в заголовке к параметрам в запросах осуществляется при помощи атрибута name ("Колво" - @Колво, "Сумма" - $Сумма). В одной секции <sql:query> может находиться несколько SQL-запросов, в отличие от XPath-запросов, каждый из которых должен заключаться в свою секцию <sql:xpath-query>. В данном примере SQL-запросы пришлось разнести по разным секциям, так как первый из них выполняется на сервере, а второй - на клиенте. Атрибут client-side-xml эквивалентен свойству ClientSideXml класса SqlXmlCommand, с которым мы сталкивались в п.6. В <sql:query> могут присутствовать не только select'ы, но и операторы объявления и модификации данных, а также вызовы хранимых процедур, что позволяет помещать в секцию не просто несколько разрозненных операторов, а последовательность, связанную логикой выполнения. Условие - весь select-вывод должен идти в виде XML. Допускаются опции AUTO и EXPLICIT. XPath-запросы выполняются относительно заданной в атрибуте mapping-schema аннотированной схемы. Для каждого XPath-запроса она может быть своя. Отметим, что некоторые (хоть все) из задействованных аннотированных схем могут располагаться непосредственно в файле шаблона, так что предыдущий шаблон можно записать как: <?xml version="1.0" encoding="utf-8" ?> <Солянка xmlns:sql="urn:schemas-microsoft-com:xml-sql"> <xs:chema xmlns:xs="http://www.w3.org/2001/XMLSchema" _ xmlns:ms="urn:schemas-microsoft-com:mapping-schema" id="SQLSchema2" sql:is-mapping-schema="1"> ...Схема с рис.5... </xs:schema> ...<sql:header> и <sql:query>-секции с рис.6... <sql:xpath-query mapping-schema="..\Schemas\SQLSchema1.xsd"> Клиент[number(Заказы/Заказ/Стоимость)>$Сумма] </sql:xpath-query> <sql:xpath-query mapping-schema="#SQLSchema2"> Сотрудник </sql:xpath-query> </Солянка>
Таким образом, результат выполнения шаблона может состоять из фрагментов различных схем, соответствующих разным секциям, объединенных корневым элементом. Остается сохранить его в файле формата UTF-8 (Скрипт 11). static void Execute_TemplateFile_SQLXML() { ... cmd.CommandText = "..\\Templates\\XMLTemplate1.xml"; cmd.CommandType = SqlXmlCommandType.TemplateFile; SqlXmlParameter prm = cmd.CreateParameter(); prm.Name = "@Колво"; prm.Value = 20; XmlDocument xml = new XmlDocument(); xml.Load(cmd.ExecuteStream()); ... }
Вызывающее приложение передает шаблону только один параметр @Колво = 20 (показать клиентов и количество сделанных ими заказов, если оно превышает 20). Для другого запроса будет взято значение параметра по умолчанию из шаблона - <sql:param name="Сумма">1000</sql:param> (показать только тех клиентов, которые сделали хотя бы один заказ на сумму свыше 1000). Если приложение не обеспечивает параметра при вызове шаблона и секция <sql:header> не содержит значение параметра по умолчанию, то для него будет использовано значение по умолчанию, определенное в схеме SQL Server (DEFAULT для поля таблицы или для параметра хранимой процедуры). Если значение по умолчанию в схеме SQL Server также не определено, значение параметра полагается в NULL. Стоит обратить внимание на конвертацию number(Заказы/Заказ/Стоимость) в одном из XPath-запросов в шаблонах рис.6, 7. Дело в том, что несмотря на объявление стоимости как xs:type="xs:float" параметр $Сумма норовит передаваться как nvarchar, что приводит к неверным результатам (напр. '5.00' > '1000'). Еще один, эквивалентный, способ вызова шаблона из SqlXml состоит в использовании свойства CommandStream вместо CommandText. Соответственно должно быть скорректировано свойство CommandType. ... cmd.CommandStream = new FileStream("..\\Templates\\XMLTemplate1.xml", FileMode.Open, FileAccess.Read); cmd.CommandType = SqlXmlCommandType.Template; ... Или даже так: ... cmd.CommandStream = new MemoryStream(); StreamWriter sw = new StreamWriter(cmd.CommandStream, System.Text.Encoding.UTF8); sw.Write("<?xml version='1.0' encoding='utf-8' ?> " + "<Солянка xmlns:sql='urn:schemas-microsoft-com:xml-sql'>" + В свойстве XslPath класса SqlXmlCommand может задаваться
ссылка на XSL-преобразование, которому подвергается
сформированный на основе шаблона XML-файл. Преобразование
можно также оговорить в корневом элементе шаблона (например,
sql:xsl="....xsl">). | |
11 UpdateGrams |
До сих пор под XML-взаимодействием с SQL Server понималось, в основном, чтение данных с сервера в XML-формате с помощью SELECT ... FOR XML или XPath. Возникает вопрос: можно ли их модифицировать в рамках XML-представления. Про возможность модификации косвенно упоминалось пару раз: когда мы говорили про возможности ADO.Net (п.7) и про шаблоны (п.10). Первый способ предусматривает связь с сервером через DataAdapter и работу с DataSet в рамках его XMLной ипостаси. Второй можно реализовать, поместив запросы на обновление в секцию <sql:query> шаблона: <Root xmlns:sql="urn:schemas-microsoft-com:xml-sql"> <sql:header> <sql:param name="Имя"></sql:param> <sql:param name="Фамилия"></sql:param> </sql:header> <sql:query> UPDATE Customers SET ContactName = 'Maria Anders' WHERE CustomerID = 'ALFKI' INSERT Employees (FirstName, LastName) Values (@Имя, @Фамилия) </sql:query> </Root> Его выполнение:
static void Execute_UpdateTemplate_SQLXML() { ... cmd.CommandText = "..\\Templates\\XMLTemplate3.xml"; cmd.CommandType = SqlXmlCommandType.TemplateFile; SqlXmlParameter prm = cmd.CreateParameter(); prm.Name = "@Имя"; prm.Value = "ааа"; prm = cmd.CreateParameter(); prm.Name = "@Фамилия"; prm.Value = "ббб"; cmd.ExecuteNonQuery(); } равнозначно тому, как если бы эти запросы были выполнены
обычным путем. Возникает вопрос: можно ли модифицировать
данные непосредственно на сервере (не кэшируя их
предварительно в DataSet) и работая с ними, как с XML, а не
через реляционные операторы. Такой способ предоставляет
UpdateGrams. <?xml version="1.0" encoding="utf-8" ?> <ROOT xmlns:updg="urn:schemas-microsoft-com:xml-updategram"> <updg:header> <updg:param name="Дата"/> <updg:param name="Стоимость" /> </updg:header> <updg:sync mapping-schema="..\Schemas\SQLSchema1.xsd"> <updg:before> <Клиент Имя="Ana Trujillo" updg:id="x" /> <Клиент Имя="Antonio Moreno" updg:id="y" /> </updg:before> <updg:after> <Клиент updg:id="y" Фирма="Рога&Копыта" Имя="Дон Педро" /> </updg:after> <updg:before> <Клиент CustomerID="ALFKI" /> </updg:before> <updg:after> <Клиент CustomerID="ALFKI"> <Заказы> <Заказ> <Дата> $Дата </Дата> <Стоимость> cast(@Стоимость as money) </Стоимость> </Заказ> </Заказы> </Клиент> </updg:after> </updg:sync> <updg:sync mapping-schema="..\Schemas\SQLSchema2.xsd"> <updg:after> <Сотрудник updg:at-identity="x" Имя="Альбус" Фамилия="Дамблдор" > <Сотрудник Имя="Минерва" Фамилия="МакГонагалл" ReportsTo="x" /> </Сотрудник> </updg:after> </updg:sync> </ROOT> Поскольку я постарался напихать в него по
максимуму показательных вещей, разберем данный updategrams по
частям. Начнем с того, что это XML-файл, структура которого
довольно близка к уже рассмотренным нами шаблонам. В </updg:sync>
может задаваться аннотированная схема, которая отображает
реляционную схему на XSD и благодаря которой мы можем работать
с информацией в БД как с XML. Если схема не указана,
предполагается отображение по умолчанию: каждая запись таблицы
- элемент, поля в ней - атрибуты. По большому счету
updategrams состоит из секций трех типов: <updg:header> -
в ней передаются возможные параметры; <updg:before> и
<updg:after>.
Если запись фигурирует только в <updg:before>,
она удаляется; если только в <updg:after> -
вставляется; если и там, и там - обновляется. Рассмотрим,
например, ситуацию, когда весь updategrams состоит только из
<updg:sync mapping-schema="SQLSchema1.xsd"> <updg:before> <Клиент Имя="Ana Trujillo" /> </updg:before> <updg:after> </updg:after> </updg:sync> В процессе его выполнения на SQL Server
происходит следующее:
SET XACT_ABORT ON
BEGIN TRAN DECLARE @eip INT, @r__ int, @e__ int SET @eip = 0 DELETE Customers WHERE ( ContactName=N'Ana Trujillo' ) ; SELECT @e__ = @@ERROR, @r__ = @@ROWCOUNT IF (@e__ != 0 OR @r__ != 1) SET @eip = 1 IF (@r__ > 1) RAISERROR ( N'SQLOLEDB Error Description: Ambiguous delete, unique identifier required Transaction aborted ', 16, 1) ELSE IF (@r__ < 1) RAISERROR ( N'SQLOLEDB Error Description: Empty delete, no deletable rows found Transaction aborted ', 16, 1) IF (@eip != 0) ROLLBACK ELSE COMMIT SET XACT_ABORT OFF Из этого сразу видно несколько важных
вещей. Первое: каждая секция <updg:sync>
открывает отдельную транзакцию (см. BEGIN TRAN). Второе:
описание модифицируемого элемента (<Клиент Имя="Ana Trujillo"
/>) в <updg:before>
и/или <updg:after>
должно соответствовать одной записи, неважно, идет ли
маппирование по дефолту или через аннотированную схему. Если
записей, отвечающих эквивалентному условию WHERE не находится
(@r__ < 1) или больше одной (@r__ > 1), будет
сгенерирована ошибка (RAISERROR) и транзакция откатится
(ROLLBACK).
Поскольку в каждой секции <updg:before> и <updg:after> может находиться несколько записей, то необходимо как-то сопоставить их друг другу в случае обновления. Например, при парсинге этого шаблона <updg:before> <Клиент Имя="Ana Trujillo" updg:id="x" /> <Клиент Имя="Antonio Moreno" updg:id="y" /> </updg:before> <updg:after> <Клиент updg:id="y" Фирма="Рога&Копыта" Имя="Дон Педро" /> </updg:after> SqlXml должен понимать, что клиента по
имени Ana Trujillo мы хотим просто удалить, а клиента по имени
Antonio Moreno обновляем, поэтому строка <Клиент
updg:id="y" Фирма="Рога&Копыта" Имя="Дон Педро" />
в <updg:after> соответствует именно ему. Это можно
сделать двумя способами. Первый - пометить их одним и тем же
updg:id.
Второй способ - однозначно идентифицировать записи при помощи
первичного ключа таблицы. Для этого нужно а) определить ключ в
аннотированной схеме (вспоминайте аннотацию ms:key-fields) и
б) явно сослаться на него в <updg:before>/<updg:after>
(скажем, <Сотрудник ID_Сотрудника="..." />).
Следующая часть <updg:before> <Клиент CustomerID="ALFKI" /> </updg:before> <updg:after> <Клиент CustomerID="ALFKI"> <Заказы> <Заказ> <Дата> $Дата </Дата> <Стоимость> cast(@Стоимость as money) </Стоимость> </Заказ> </Заказы> </Клиент> </updg:after> производит обновление и вставку
одновременно. В заказы, сделанные клиентом с идентификатором
ALFKI добавляется еще один
заказ. При этом SQL Server сам распознает, что новую запись в
таблице Orders нужно подчинить данному клиенту в таблице
Customers и автоматически устанавливает для нее CustomerID в
"ALFKI".
exec sp_executesql N'... INSERT Orders (OrderDate, Freight, CustomerID) VALUES (@Дата, cast(@Стоимость as money), N''ALFKI'') ... ', N'@Дата nvarchar(19),@Стоимость sql_variant', N'08.04.2002 18:41:34', 100 Это происходит благодаря тому, что поле
CustomerID указано в качестве связующего в <ms:relationship> схемы
SQLSchema1.xsd. Обратите внимание, что несмотря на то, что в
аннотирующей схеме ему явно не соответствует никакой
элемент/атрибут, на него можно ссылаться в updategrams-файле.
Для автоматического подчинения родительскую запись в <updg:before>/<updg:after>
необходимо идентифицировать именно по CustomerID, которое
значится как parent-key в схеме (parent-key="CustomerID"). Определение записи по другим
атрибутам, пусть даже однозначно ее идентифицирующим
(например, <Клиент Имя="Maria
Anders" Фирма="Alfreds Futterkiste"
...>), к такому эффекту не приводит. Следующая часть
updategrams-файла:
<updg:sync mapping-schema="..\Schemas\SQLSchema2.xsd"> <updg:after> <Сотрудник updg:at-identity="x" Имя="Альбус" Фамилия="Дамблдор" > <Сотрудник Имя="Минерва" Фамилия="МакГонагалл" ReportsTo="x" /> </Сотрудник> </updg:after> </updg:sync> открывает другую транзакцию и работает с
другой аннотирующей схемой, которая, как вы помните,
превращает parent-child таблицу в XML-иерархию. Здесь
демонстрируется не автоматическое, а "ручное" подчинение. Мы
вставляем одновременно две записи. Чтобы подчинить вторую
первой, нужно значение поля ReportsTo
для второй установить в первичный ключ первой. Положение
осложняется тем, что первичный ключ - это identity, и его
значение априори неизвестно. Выйти из положения позволяет
аннотация updg:at-identity. Кстати, здесь мы снова
обращаемся к полю (ReportsTo), которое нигде в схеме не
засвечено, а используется только в описании отношения (<ms:relationship>).
Дата и стоимость заказа передаются в виде параметров. Если параметр подставляется standalone, можно использовать XPath-обозначение ($Дата), если же над ним по ходу выполняются какие-то SQL-преобразования - то SQLное (@Стоимость). Я не стал дальше усложнять пример, но хотел бы отметить еще две полезных аннотации. <updg:nullvalue> позволяет оговорить SQLный NULL: <updg:sync mapping-schema="SQLSchema2.xsd" updg:nullvalue="Пусто"> <updg:after> <Сотрудник updg:at-identity="x" Имя="Альбус" Фамилия="Дамблдор" > <Сотрудник Имя="Минерва" Фамилия="МакГонагалл" ReportsTo="Пусто" /> </Сотрудник> </updg:after> </updg:sync> В этом случае второй сотрудник не будет
подчинен первому, т.к. несмотря на то, что в XML эта запись
вложена, при занесении ее в таблицу ей не будет назначено
никакого руководителя (ReportsTo=NULL).
Аннотация ms:inverse применяется не в UpdateGrams, а при описании аннотированной схемы. Мы не рассматривали ее в п.9, потому что она имеет смысл только тогда, когда та используется затем в updategrams. Дело в том, что SQL Server считает, что иерархия "родитель-потомок" в XML соответствует ограничению первичный - внешний для ключей связанных таблиц. Рассмотрим updategrams вида <updg:sync mapping-schema="..\Schemas\SQLSchema2.xsd"> <updg:before> <Клиент CustomerID="ALFKI"> <Заказы> <Заказ /> </Заказы> </Клиент> </updg:before> </updg:sync> Сразу ясно, что этот пример вызовет ошибку,
поскольку данный клиент сделал более одного заказа, а условием
UpdateGrams является однозначная идентификация записи. Но дело
не в этом. На сервере будут выполнены действия в следующем
порядке: DELETE Orders WHERE CustomerID = N'ALFKI'; DELETE
Customers WHERE CustomerID = N'ALFKI'. (Использование
атрибутов, не входящих в определение <ms:relationship>, - напр.,
<Клиент Имя="Maria Anders"> приведет
к полному DELETE Orders). Из этой последовательности видно,
что SqlXml сначала пытается произвести обновления / удаления в
дочерней таблице, а уже потом из родительской, чтобы по
возможности не противоречить referential constraints. В жизни
бывают ситуации, когда схема может задавать вложенность
элементов, противоположную направлению действия ограничения
primary key/foreign key в таблицах. Тогда SqlXml, предполагая,
что вложенный элемент соответствует внешнему ключу, полезет на
самом деле в первичную таблицу, получит от SQL Server по рукам
и откатит транзакцию. Чтобы побороть такую ситуацию, в <ms:relationship> нужно
поставить ms:inverse="true".
Полный список аннотаций, как всегда, можно найти в документации к SQLXML 3.0. Вызов UpdateGrams из приложения происходит аналогично вызову шаблона через файл (см. Скрипт 12) или Stream. При его тестировании в БД Northwind предварительно нужно превратить связь Orders -> [Order Details] из строгой в каскадную. Поскольку [Order Details] не участвует в updategrams, ее FK будет препятствовать обновлению Orders (в отличие от Customer -> Orders, где это учитывается автоматически за счет указания в relationship в аннотированной схеме). ...
cmd.CommandText = "..\\Templates\\UpdateGrams1.xml"; cmd.CommandType = SqlXmlCommandType.TemplateFile; SqlXmlParameter prm = cmd.CreateParameter(); prm.Name = "@Дата"; prm.Value = DateTime.Now.ToString(); prm = cmd.CreateParameter(); prm.Name = "@Стоимость"; prm.Value = 100; cmd.ExecuteNonQuery();
| |
12 XML Bulk Load |
Большие объемы данных неэффективно "заливать" в базу
чередой последовательных insert'ов. Для этого применяются
средства массовой загрузки. Аналогом bcp / BULK INSERT при
работе с XML-файлами выступает XML Bulk Load. Он не грузит
весь документ целиком в память, а прочитывает его по отдельным
узлам, понимая на основе аннотированной схемы, когда запись
заканчивается и ее можно отправить SQL Server'у для вставки.
Использование аннотированных схем позволяет осуществлять
наполнение одновременно нескольких связанных таблиц. XML Bulk
Load не есть утилита с графическим интерфейсом, а всего лишь
СОМ-компонент, устанавливаемый SQLXML веб-релизами. Массовая
закачка XML-документа осуществляется программным путем -
достаточно написать VB-скрипт из нескольких строчек. Я
надеюсь, что эта идея вас не пугает, потому что если вы
дочитали до этого места, значит, вы, скорее, разработчики,
нежели пользователи или администраторы. Для работы из-под .NET
Framework необходимо импортировать библиотеку типов Microsoft
SQLXML Bulkload 3.0 Type Library (...\Program Files\Common
Files\System\Ole DB\xblkld3.dll). <Книги_по_XML> <Книга Название="The Guru's Guide to SQL Server Stored Procedures, XML, and HTML" ISBN="0201700468" Страниц="576"> <Порядковый_номер>1</Порядковый_номер> <Издательство>Wesley Professional</Издательство> <Цена_на_Амазоне>34.99</Цена_на_Амазоне> <Дата_выхода>2001-12-21</Дата_выхода> <Авторы> <Автор Имя="Ken" Фамилия="Henderson" /> <Автор Имя="Ron" Фамилия="Soukup" /> </Авторы> </Книга> <Книга Название="Programming Microsoft SQL Server 2000 With XML (Pro-Developer)" ISBN="0735613699" Страниц="400"> <Порядковый_номер>2</Порядковый_номер> <Издательство>Microsoft Press</Издательство> <Цена_на_Амазоне>41.99</Цена_на_Амазоне> <Дата_выхода>2001-06-01</Дата_выхода> <Авторы> <Автор Имя="Graeme" Фамилия="Malcolm" /> </Авторы> </Книга> ... </Книги_по_XML> Вот аннотированная схема, которую я для него определил (схема должна находиться в отдельном файле, размещение ее в самом XML-документе не допускается). <?xml version="1.0" ?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ms="urn:schemas-microsoft-com:mapping-schema"> <xs:annotation> <xs:appinfo> <ms:relationship name="Книга_Авторы" parent="Book" parent-key="BookID" child="Author" child-key="BookID" /> </xs:appinfo> </xs:annotation> <xs:element name="Книга" ms:relation="Book"> <xs:complexType> <xs:sequence> <xs:element name="Порядковый_номер" ms:field="BookID" ms:datatype="int" /> <xs:element name="Издательство" ms:field="Publishing" ms:datatype="varchar(50)" /> <xs:element name="Цена_на_Амазоне" ms:field="Price" ms:datatype="numeric(6,2)" /> <xs:element name="Дата_выхода" ms:field="IssueDate" ms:datatype="smalldatetime" /> <xs:element name="Авторы" maxOccurs="1" ms:is-constant="1"> <xs:complexType> <xs:sequence> <xs:element name="Автор" minOccurs="0" maxOccurs="unbounded" ms:relation="Author" ms:relationship="Книга_Авторы"> <xs:complexType> <xs:attribute name="Имя" ms:field="FirstName" ms:datatype="nvarchar(50)" /> <xs:attribute name="Фамилия" ms:field="LastName" ms:datatype="nvarchar(50)" /> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="Название" ms:field="Title" ms:datatype="nvarchar(200)" /> <xs:attribute name="ISBN" ms:field="ISBN" ms:datatype="char(10)" /> <xs:attribute name="Страниц" ms:field="NumPages" ms:datatype="smallint" /> </xs:complexType> </xs:element> </xs:schema> И вот скрипт, который осуществляет загрузку: class BulkExample { static void BulkLoad_File_SQLXML() { SQLXMLBULKLOADLib.SQLXMLBulkLoad3Class bl = new SQLXMLBULKLOADLib.SQLXMLBulkLoad3Class(); bl.ConnectionString = "Provider=SQLOLEDB;..."; bl.SchemaGen = true; bl.SGDropTables = true; bl.KeepNulls = true; bl.Transaction = true; bl.ErrorLogFile = "..\\BulkCopy\\XMLDocForBulkLoad.err"; bl.Execute("..\\BulkCopy\\XMLDocForBulkLoad.xsd", "..\\BulkCopy\\XMLDocForBulkLoad.xml"); } [STAThread] static void Main() { BulkLoad_File_SQLXML(); } } Следует обратить внимание на атрибут [STAThread], поскольку
компонент пока работает только в однопоточном режиме. Свойство
SchemaGen, определяет, должны ли таблицы под загрузку
создаваться или вставка идет в уже имеющиеся. Чтобы просто
создать структуры и не переносить при этом сами данные, нужно
еще поставить BulkLoad = false. Имена
таблиц и полей, их типы и прочие метаданные определяются
аннотированной схемой (ms:relation, ms:field, ms:datatype).
Методу Execute можно передавать не только название XML-документа, но и ADODB.Stream: ... StreamReader sr = File.OpenText("..\\BulkCopy\\XMLDocForBulkLoad.xml"); ADODB.StreamClass sc = new ADODB.StreamClass(); sc.Charset = "UTF-8"; sc.Open(System.Type.Missing, ADODB.ConnectModeEnum.adModeUnknown, ADODB.StreamOpenOptionsEnum.adOpenStreamUnspecified, "", ""); sc.WriteText(sr.ReadToEnd(), ADODB.StreamWriteEnum.stWriteChar); sc.Position = 0; bl.Execute("..\\BulkCopy\\XMLDocForBulkLoad.xsd", sc); sr.Close(); sc.Close(); что позволяет загружать в БД динамически сгенерированный внутри кода XML без необходимости его промежуточного сохранения. Для загрузки XML-фрагмента (набора элементов без корневого, не являющегося каноническим well-formed документом) необходимо использовать свойство XMLFragment. | ||||||||||||||||||||||||||||||||||||||||||||||||||
13 Доступ к SQL Server по HTTP |
Насколько вы заметили, в п.1 среди одного из ключевых преимуществ поддержки XML в СУБД называлась возможность доступа по HTTP. Опять же, насколько вы заметили, HTTP у нас до сих пор, в общем-то, не пахло. Настало время исправить этот недостаток. Напрямую по HTTP с SQL Server общаться нельзя. Во всяком случае пока между клиентом и SQL Server-ом приходится иметь IIS. В состав SQLXML 3.0 входит ISAPI-фильтр ...\Program Files\Common Files\System\Ole DB\sqlis3.dll, который принимает на себя обработку методов GET, HEAD, POST, вытаскивает из них тело запроса и отправляет его на SQL Server. SQL Server его обрабатывает, ответ в виде XML передается Web-серверу, а от него по HTTP - клиенту. Таким образом, второй участок пути (IIS - SQL Server) относится к локальному взаимодействию с SQL Server, с которым, я надеюсь, все ясно, т.к. им мы занимались на протяжении 12-ти предыдущих параграфов. Чтобы разобраться с первым участком (клиент - IIS), нам нужно понять две вещи: как правильно упаковать запрос в GET и POST-обращения и как их отправить фильтру. С фильтром все происходит классически: на IISе создается виртуальная директория, к которой он подвязывается. Для ее создания можно воспользоваться графической утилитой Configure IIS Support в SQLXML 3.0 (в отличие от аналогичной утилиты в составе SQL Server она поддерживает все те новшества, которые были представлены в этом релизе), либо сделать это программным путем, написав скрипт, похожий на тот, что представлен ниже. static void Main() { const string SQLSrv = "(local)"; const string DBName = "Northwind"; string SQLVirtDirName = "SQLXML3"; string SQLVirtDirPhysPath = Directory.GetParent(Directory.GetCurrentDirectory()).FullName; //Получаем ссылку на основной объект SQLVDIRLib.SQLVDirControl3 sqlVDirCtl = new SQLVDIRLib.SQLVDirControl3(); //Соединяемся с сервером localhost и дефолтовым web-сайтом sqlVDirCtl.Connect("localhost", "1"); //Получаем коллекцию виртуальных директорий SQLVDIRLib.IVRootEnum2 sqlVDirs = sqlVDirCtl.SQLVDirs; //Если вирт.дир. с таким именем существует, удаляем if (sqlVDirs.get_Exists(SQLVirtDirName)) sqlVDirs.xxxRemoveVirtualDirectory(SQLVirtDirName); //Создаем новую вирт.дир. SQLVDIRLib.ISQLVDir2 sqlVDir = sqlVDirs.AddVirtualDirectory(SQLVirtDirName); //1-я закладка New Virtual Directory Properties sqlVDir.PhysicalPath = SQLVirtDirPhysPath; //2-я закладка New Virtual Directory Properties sqlVDir.SecurityMode = SQLVDIRLib.tagSecurityModes.smINTEGRATED; //3-я закладка New Virtual Directory Properties sqlVDir.ServerName = SQLSrv; sqlVDir.DatabaseName = DBName; //4-я закладка New Virtual Directory Properties sqlVDir.AllowFlags = SQLVDIRLib.tagAllowFlags.afURL_QUERIES | SQLVDIRLib.tagAllowFlags.afTEMPLATES | SQLVDIRLib.tagAllowFlags.afXPATH | SQLVDIRLib.tagAllowFlags.afPOST; //5-я закладка New Virtual Directory Properties //Получить коллекцию виртуальных имен для данной вирт.дир. SQLVDIRLib.IVirtualNames2 sqlVDirVNames = sqlVDir.VirtualNames; //Добавляем новые виртуальные имена //Параметры: название поддир., ее тип, отн.физ.путь (там, где не нужно - пустая строка) sqlVDirVNames.xxxAddVirtualName("dbobj", SQLVDIRLib.tagVirtualTypes.vtDBOBJECT, ""); sqlVDirVNames.xxxAddVirtualName("schem", SQLVDIRLib.tagVirtualTypes.vtSCHEMA, "Schemas"); sqlVDirVNames.xxxAddVirtualName("templ", SQLVDIRLib.tagVirtualTypes.vtTEMPLATE, "Templates"); SQLVDIRLib.IVirtualName sqlWebSvc = sqlVDirVNames.xxxAddVirtualName("websvc", SQLVDIRLib.tagVirtualTypes.vtSOAP, "WebService"); sqlWebSvc.WebService = "SQLSoapSample"; sqlWebSvc.Domain = "localhost"; sqlWebSvc.SoapMethods.AddStoredProcMethod("ЗаказыКлиентаЗаГод", "CustomerOrdersForYear", 1, 1, SQLVDIRLib.tagSoapMethodOutput.smoDATASET); sqlWebSvc.SoapMethods.AddTemplateMethod("ПримерВызоваШаблона", "..\\Templates\\XMLTemplate1.xml", 1); sqlWebSvc.SoapMethods.GenerateConfig(0, 0); sqlWebSvc.SoapMethods.GenerateWSDL(); //Отсоединяемся от сервера sqlVDirCtl.Disconnect(); } Некоторые пояснения к тому, что здесь делалось. Для начала,
чтобы воспользоваться функциональностью объекта SQLVDirControl
из .NET Framework, нужно импортировать библиотеку классов
соответствующей СОМовской dll'и - ...\Program Files\Common
Files\System\Ole DB\sqlvdr3.dll. Процесс работы с этим
объектом практически повторяет шаги, выполняемые из
графического интерфейса визарда по созданию виртуальной
директории SQL Server. Для обслуживания анонимных соединений с
SQL Server по HTTP следует выбрать учетную запись Windows,
авторизованную на доступ к SQL Server, либо SQLный логин, под
которыми анонимусы будут ходить на SQL Server. В локальных
интранет-сценариях лучше выбирать Windows Integrated
Authentication, когда пользователь под своей доменной учетной
записью сквозным образом авторизуется на IIS и далее на SQL
Server (что соответствует его интегрированному или смешанному
режиму безопасности). Basic Authentication (Clear Text) to SQL
Server account, как следует из названия, будет запрашивать у
пользователя при обращении к виртуальной директории его логин
и пароль на SQL Server. Так же, как базовая аутентификация на
IISе, они передаются в открытом виде, поэтому при выборе этого
сценария следует применять HTTPS. Виртуальная директория,
естественно, должна быть привязана к определенному экземпляру
SQL Server (он может стоять на машине, отличной от той, где
установлен IIS) и к определенной базе данных на нем.
Естественно, все механизмы проверки полномочий при обращении к
объектам сервера и совершении на нем каких-либо действий,
остаются в силе, однако еще до того, как дело дойдет до SQL
Server, можно ввести дополнительные ограничения на уровне
виртуальной директории. Опция Allow URL queries (SQL,
template) разрешает непосредственно по HTTP запрашивать SQL
Server при помощи SELECT ... FOR XML или передав текст
шаблона. По умолчанию она запрещена, чтобы кто ни попадя из
Интернет не приставал к SQL Server с произвольными запросами.
Конечно, если у человека нет прав, он и так будет послан, но
зачем напрягать сервер и заставлять его тратить время на
проверку. Если вы все-таки хотите открыть предыдущую опцию
только для запросов на чтение, запретите Allow posted
updategrams - при этом будут отвергаться все ad hoc (т.е. те,
которые приходят от пользователя, как было показано в конце
п.10, а не те, что подготовили вы и сохранили в поддиректории
типа template) шаблоны типа UpdateGrams. Allow XPath - то же,
что первая опция, но для ad hoc XPath-запросов. Allow template
queries - разрешить пользователям вызывать хранящиеся на
сервере шаблоны (а не направлять ему свои). Очень похоже на
ситуацию, когда при классическом доступе пользователям даются
права только на вызовы определенных хранимых процедур, чтобы
даже с теми объектами, на которые у них есть права, они не
творили, что хотели, а действовали в рамках предусмотренных
бизнес-правил. Allow POST - поскольку очень длинный запрос в
GET не пролезет, то можно сделать доступной посылку запросов
методом POST. Maximum size of POST queries (in kilobytes)
позволяет тем не менее ограничить сверху длину
запроса. FOR XML-запрос из Скриптов 3, 4 передается, например, так: http://localhost/SQLXML3?sql=SELECT
c.ContactName, c.ContactTitle, o.OrderDate FROM Customers c
INNER JOIN Orders o ON c.CustomerID = o.CustomerID WHERE
c.ContactName = ? and year(o.OrderDate) = ? FOR XML
AUTO&x=Maria Anders&y=1997&root=root
Здесь SQLXML3 - название виртуальной
директории SQL Server, параметр sql содержит текст FOR
XML-запроса, параметры x и y передают значения параметров
запроса (имена параметров запроса в данном случае
несущественны в отличие от, например, шаблонов), параметр root
- название корневого тэга. В параметре xsl может передаваться
имя локального файла, содержащего XSL-преобразование с тем,
чтобы результаты запроса сразу получались отфильтрованными и
отформатированными. Скрипт 15 демонстрирует отправку этого
запроса методом GET, как если бы вышеприведенная строка была
просто набрана в URL-строке браузера. Web-сервер отвечает
Stream'ом, в котором передается XML-результат запроса от SQL
Server. Этот Stream загружается в XML-документ, с которым
затем работает приложение.
static void Execute_FORXMLQuery_HTTPGET() { HttpWebRequest rqst = (HttpWebRequest) WebRequest.Create("http://localhost/SQLXML3?sql=SELECT c.ContactName, c.ContactTitle, o.OrderDate FROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID WHERE c.ContactName = ? and year(o.OrderDate) = ? FOR XML AUTO&x=Maria Anders&y=1997&root=root"); rqst.Method = "GET"; rqst.Credentials = CredentialCache.DefaultCredentials; HttpWebResponse rspn = (HttpWebResponse) rqst.GetResponse(); Stream s = rspn.GetResponseStream(); XmlDocument xml = new XmlDocument(); xml.Load(s); rspn.Close(); ... } Скрипт 16 делает то же самое методом POST. Строка запроса передается не в URL, а во входном Stream'е, предварительно запрошенном у Web-сервера. Результаты получаются и обрабатываются аналогично предыдущему примеру. static void Execute_FORXMLQuery_HTTPPOST() { string query = "sql=SELECT c.ContactName, c.ContactTitle, o.OrderDate FROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID WHERE c.ContactName = ? and year(o.OrderDate) = ? FOR XML AUTO&x=Maria Anders&y=1997&root=root"; HttpWebRequest rqst = (HttpWebRequest) WebRequest.Create("http://localhost/SQLXML3"); rqst.Method = "POST"; rqst.Credentials = CredentialCache.DefaultCredentials; rqst.ContentLength = query.Length; StreamWriter sw = new StreamWriter(rqst.GetRequestStream()); sw.Write(query); sw.Close(); HttpWebResponse rspn = (HttpWebResponse) rqst.GetResponse(); XmlDocument xml = new XmlDocument(); xml.Load(rspn.GetResponseStream()); rspn.Close(); ... } Форма, представленная в Скрипте 17, функционально эквивалентна 16. В случае формы параметры Web-запроса передаются в полях (связка "имя параметра / значение" соответствует атрибутам name / value). Htm-файл с формой может находиться в любом локальном каталоге, т.к. он служит только средством передачи Web-запроса с клиента на Web-сервер. При ссылках на аннотированные схемы (например, когда из формы выполняется не SQL-запрос, а шаблон) следует иметь в виду, что схемы должны находиться либо в текущем каталоге, либо в каком-нибудь из вложенных в него подкаталогов. Излишне напоминать, что в случае использования русских названий и других расширенных символов файл нужно сохранять не в ANSI, а как UTF-8 или подобном формате. <head> <TITLE>Пример FOR XML через POST из формы</TITLE> </head> <body> Демонстрирует отправку FOR XML-запроса методом POST из формы <form action="http://localhost/sqlxml3" method="POST"> <B>Имя клиента</B> <input type="text" name="name"> <br> <B>Год</B> <input type="text" name="year"> <input type="hidden" name="contenttype" value="text/xml"> <input type="hidden" name="sql" value=" SELECT c.ContactName, c.ContactTitle, o.OrderDate FROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID WHERE c.ContactName like ? + '%' and year(o.OrderDate) = ? FOR XML AUTO "> <input type="hidden" name="Root" value="Корень"> <p><input type="submit" value="Сабмит"> </form> </body> Все остальные типы XML-запросов к SQL Server передаются по HTTP совершенно аналогично Скриптам 15, 16. Различия составляют только правила формирования строки запроса к Web-серверу. Вот пример передачи по HTTP прямого XPath-запроса из п.8: http://localhost/SQLXML3/dbobj/Customers[@ContactName>=$НачБуква]/@ContactName?НачБуква='X'
Dbobj - подкаталог виртуальной директории
типа Dbobject, определенной при создании виртуальной
директории SQLXML3. XPath-запрос входит в URI, параметром
служит параметр XPath-запроса.
Вот пример XPath-запроса через аннотированную схему, из п.9 (Скрипт 10). http://localhost/SQLXML3/schem/SQLSchema1.xsd/Клиент[Адрес/Страна='Spain'%20or%20Адрес/Страна='France']?root=Клиенты
При желании можно передать параметры:
http://localhost/SQLXML3/schem/SQLSchema1.xsd/Клиент[Адрес/Страна=$Country]?Country=Spain&root=Клиенты
Schem - виртуальный подкаталог типа Schema,
предполагается, что файл с аннотированной схемой
SQLSchema1.xsd находится в нем. В качестве еще одного
параметра в строке URL можно передавать ...&xsl=<Путь к
файлу с XSLT-преобразованием>.
Шаблоны должны храниться в подкаталоге типа Template. Обращение к шаблону происходит следующим образом: http://localhost/SQLXML3/templ/XmlTemplate1.xml?Колво=20 По сути, это HTTP-реализация Скрипта 11:
происходит вызов шаблона XmlTemplate1 и передача ему одного из
параметров (для другого используется значение по умолчанию).
Текст шаблона приводился на рис.6. Кроме того, как мы видели в
завершение п.10, можно вызывать не только шаблоны, хранящиеся
на сервере, но (при наличии прав), передать в HTTP-запросе
текст собственного шаблона, который сервер выполнит аналогично
хранимым шаблонам (т.н. ad hoc-шаблон). Это очень похоже на
передачу FOR XML-запроса, только вместо параметра sql
используется параметр template:
http://localhost/SQLXML3?template=<Root
xmlns:sql='urn:schemas-microsoft-com:xml-sql'><sql:query>
SELECT TOP 1 * FROM Employees FOR XML
AUTO</sql:query></Root>
Большие ad-hoc шаблоны проще передавать
методом POST:
static void Execute_FORXMLQuery_HTTPPOST() { string query = "template=<Солянка xmlns:sql='urn:schemas-microsoft-com:xml-sql'> <sql:header><sql:param name='q'></sql:param><sql:param name='s'>1000 </sql:param></sql:header> <sql:query client-side-xml='0'>SELECT TOP 1 * FROM Employees FOR XML AUTO</sql:query> <sql:query client-side-xml='1'>SELECT c.ContactName, COUNT(o.OrderDate) AS 'Кол-во' F ROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID GROUP BY c.ContactName HAVING COUNT(o.OrderDate) >= @q FOR XML NESTED</sql:query> <sql:xpath-query mapping-schema='Schemas\\SQLSchema1.xsd'> Клиент[number(Заказы/Заказ/Стоимость)>$s] </sql:xpath-query> <sql:xpath-query mapping-schema='Schemas\\SQLSchema2.xsd'>Сотрудник</sql:xpath-query> <Солянка>&q=20"; HttpWebRequest rqst = (HttpWebRequest) WebRequest.Create("http://localhost/SQLXML3"); rqst.Method = "POST"; rqst.ContentType = "application/x-www-form-urlencoded"; byte[] postData = System.Text.Encoding.UTF8.GetBytes(query); rqst.ContentLength = postData.Length; rqst.Credentials = CredentialCache.DefaultCredentials; rqst.GetRequestStream().Write(postData, 0, postData.Length); HttpWebResponse rspn = (HttpWebResponse) rqst.GetResponse(); XmlDocument xml = new XmlDocument(); xml.Load(rspn.GetResponseStream()); rspn.Close(); ... } | |
14 SQL Server как Web-сервис |
Web-сервис - это компонент, к которому можно обращаться с
помощью XML. Результаты вызова метода также оборачиваются в
XML. Протокол устройства XML-сообщений, которыми обмениваются
клиент и Web-сервис, называется SOAP. В отличие от RPC и
других бинарных, зависимых от платформы протоколов,
ориентированных на плотное взаимодействие, SOAP
предусматривает работу в слабосвязанных средах (Интернет) и,
будучи основан на XML, позволяет общаться компонентам
независимо от операционной системе или инструмента разработки,
в которых они были созданы. Потребность в удаленном
взаимодействии компонентов через Интернет назрела давно. В
свое время HTML-формы и ASP превратили посетителя Web-узла из
пассивного зрителя, просматривающего заранее подготовленные
странички, в активного участника, формирующего страницу по
информации из базы на основе своих критериев. Однако эта
информация доставлялась потребителю по-прежнему в
HTML-формате, что, может быть, удобно с точки зрения
визуального восприятия, но никуда не годится, если вы хотите,
например, выцепить из полученной странички котировки акций,
превратить их в recordset и передать своему приложению.
Web-сервисы публикуют описания своих интерфейсов в wsdl-файлах
(аналог библиотеки типов в СОМ). Например, методу,
возвращающему котировки, нужно подать на вход интересующую
дату и биржевой символ. Чтобы найти подходящий Web-сервис или
выяснить назначение найденного, можно использовать
универсальный реестр UDDI и протокол DISCO. В Visual
Studio.Net входят средства создания как самих Web-сервисов,
так и клиентов, их использующих. Например, не составляет труда
создать Web-сервис, который будет принимать имя и параметры
хранимой процедуры на SQL Server, выполнять ее и отсылать
результаты клиенту. Словом, типичная трехуровневая
архитектура, только клиентская компонента, компонента
бизнес-логика и сервер теперь не ограничены пределами
локальной сети, а находятся где угодно в Интернете. Начиная с
SQLXML 3.0 в роли Web-сервиса может выступать SQL Server 2000,
так что надобность в промежуточной компоненте теперь отпадает.
Присутствие ПО промежуточного слоя наиболее часто оправдывают
требованиями безопасности, масштабируемости и
программируемости. Последние два, вероятно, наиболее честные,
потому что я с трудом понимаю, как толщина middleware может
влиять на устойчивость сервера к попыткам несанкционированного
проникновения. Если вы дыряво настроили политики безопасности
на сервере, то злоумышленник все равно на него придет
независимо от того, 5 шагов ему перед этим придется сделать
или 10. Вот программируемость - это уже серьезней. Т-SQL до
сих пор остается странной смесью мощи и ограничений.
Отсутствие многих обыденных для современных языков
конструкций, не говоря уже об ООП, приводило к тому, что народ
утешал себя тем, что SQL не для того предназначался, и уходил
писать логику на С++, Visual Basic, Delphi и т.д. В последнее
время круг задач, решаемых SQL Server, очень сильно расширился
за рамки пресловутого SQL, следовательно, возможности языка
программирования на серверной стороне, как бы он ни назывался,
должны им соответствовать. И они будут соответствовать в
следующей версии. Пункт третий - масштабируемость. С целью
повышения масштабируемости и распределения нагрузки в SQL
Server 2000, как известно, были введены распределенные
фрагментированные представления. Несмотря на то, что благодаря
этому механизму SQL Server занимает в настоящий момент первые
места в рейтингах производительности, следует понимать, что в
действительности это лишь первый этап на пути к базе данных,
автоматически распределяемой по многим серверам в сети, когда
каждый узел, получивший запрос, разбивает его на подзапросы к
тем серверам, где в действительности лежат нужные данные (а
если они продублированы, то к наименее загруженным). Таким
образом, понятно, что все три критерия обособления являются
достаточно условными, и не всегда компоненты бизнес-логики
будут выноситься за пределы SQL Server. Следовательно, тот
факт, что клиентское приложение сможет общаться с ним напрямую
как с Web-сервисом, на самом деле очень важен. Вернемся к
Скрипту 14. CREATE PROCEDURE CustomerOrdersForYear @Name nvarchar(20), @Year smallint AS SELECT c.ContactName, c.ContactTitle, o.OrderDate, o.Freight FROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID WHERE c.ContactName like @Name + '%' and year(o.OrderDate) = @Year В качестве второго метода Web-сервиса выбираем шаблон из п.10 (рис.6). Он добавляется вызовом AddTemplateMethod. Параметрами служат внешнее имя метода, местоположение шаблона относительно данного скрипта и способ передачи ошибок (SOAP - да/нет). Результат шаблона можно передавать только как набор XML-элементов. Строка sqlWebSvc.SoapMethods.GenerateWSDL(); производит автоматическую генерацию wsdl-файла, который кладется в физическую директорию, соответствующую виртуальному подкаталогу типа soap. После этого Web-сервис становится доступным для обращений. Как и в классическом DCOM, на машине клиента нужно создать прокси, содержащего сигнатуру методов Web-сервиса. В графической среде разработки Visual Studio это происходит автоматически при добавлении ссылки на wsdl-файл в раздел Web References проекта. Прокси можно также сгенерировать с помощью утилиты командной строки wsdl.exe: wsdl.exe /l:CS /o:SQLSrvWebService_Proxy.cs /n:SQLSrvWebRef http://localhost/SQLXML3/websvc?wsdl В качестве параметров здесь передаются
язык, на котором будет создан прокси, имя файла с прокси, его
пространство имен и URI Web-сервиса с его описанием в виде
wsdl. Обращение клиента к Web-сервису показано в Скрипте 20.
static void SQLSrvWebService_Client() { SQLSrvWebRef.SQLSoapSample proxy = new SQLSrvWebRef.SQLSoapSample(); proxy.Credentials = System.Net.CredentialCache.DefaultCredentials; Console.WriteLine("----------------------------------------------------------------"); Console.WriteLine("Результаты выполнения шаблона (всегда как массив XML-элементов):"); object[] resultXml = proxy.ПримерВызоваШаблона("20", "1000"); foreach (XmlElement x in resultXml) Console.WriteLine(x.OuterXml); Console.WriteLine("\n----------------------------------------------------------------"); Console.WriteLine("Результаты выполнения хранимой процедуры (заказаны как DataSet):"); int retVal; DataSet resultRel = proxy.ЗаказыКлиентаЗаГод("Maria", 1997, out retVal); DataRelation rel = resultRel.Relations[0]; Console.WriteLine("\nТаблицы связаны отношением " + rel.RelationName); Console.Write("Родительская таблица " + rel.ParentTable.TableName + ", родительский ключ - "); foreach (DataColumn c in rel.ParentColumns) Console.Write(c.ColumnName + " "); Console.Write("\nДочерняя таблица " + rel.ChildTable.TableName + ", дочерний ключ - "); foreach (DataColumn c in rel.ChildColumns) Console.Write(c.ColumnName + " "); Console.WriteLine(""); foreach (DataRow rParent in rel.ParentTable.Rows) { Console.WriteLine("\nТаблица " + rel.ParentTable.TableName); foreach (DataColumn cParent in rel.ParentTable.Columns) Console.Write(cParent.Caption + ": " + rParent[cParent] + "; "); Console.Write("\n\tСвязанные записи из таблицы " + rel.ChildTable.TableName); foreach (DataRow rChild in rParent.GetChildRows(rel)) { Console.Write("\n\t"); foreach (DataColumn cChild in rel.ChildTable.Columns) Console.Write(cChild.Caption + ": " + rChild[cChild] + "; "); } Console.WriteLine(""); } } Результат выполнения шаблона получается в виде массива XmlElement, состоящего из единственного элемента, OuterXml которого показан ниже: <Солянка xmlns:sql="urn:schemas-microsoft-com:xml-sql"> <Employees EmployeeID="1" LastName="Davolio" FirstName="Nancy" Title="Sales Re presentative" TitleOfCourtesy="Ms." ... /> <Customers ContactName="Horst Kloss" Кол-во="28" /><Customers ContactName="Jos e Pavarotti" Кол-во="31" /><Customers ContactName="Roland Mendel" Кол-во="30" /> <Клиент Имя="Horst Kloss" Фирма="QUICK-Stop" Должность="Accounting Manager"><А дрес><Страна>Germany</Страна><Город>Cunewalde</Город><Улица_дом>Taucherstra?e 10 </Улица_дом></Адрес><Заказы><Заказ Номер="10273"><Дата>1996-08-05</Дата><Стоимос ть>76.07</Стоимость></Заказ><Заказ Номер="10285"><Дата>1996-08-20</Дата><Стоимос ть>76.83</Стоимость></Заказ>...</Заказы></Клиент> <Сотрудник Фамилия="Fuller" Имя="Andrew"><Сотрудник Фамилия="Davolio" Имя="Nan cy" /><Сотрудник Фамилия="Leverling" Имя="Janet" /><Сотрудник Фамилия="Peacock" Имя="Margaret" /><Сотрудник Фамилия="Buchanan" Имя="Steven"><Сотрудник Фамилия=" Suyama" Имя="Michael" /><Сотрудник Фамилия="King" Имя="Robert" /><Сотрудник Фами лия="Dodsworth" Имя="Anne" /></Сотрудник><Сотрудник Фамилия="Callahan" Имя="Laur a" /></Сотрудник> </Солянка> DataSet как результат второго метода
состоит из двух таблиц: Customers и Orders. Выбор Row
Formatting = Nested на стадии определения метода Web-сервиса
приводит к тому, что условие соединения таблиц в запросе
(JOIN) переходит в свойство Relations объекта DataSet и
позволяет для каждого клиента выбрать сделанные им заказы:
Таблица Customers ContactName: Maria Larsson; ContactTitle: Owner; Customers_Id: 0; Связанные записи из таблицы Orders OrderDate: 03.02.1997 0:00:00; Freight: 17.92; Customers_Id: 0; OrderDate: 28.02.1997 0:00:00; Freight: 16.27; Customers_Id: 0; OrderDate: 12.05.1997 0:00:00; Freight: 188.04; Customers_Id: 0; OrderDate: 06.06.1997 0:00:00; Freight: 242.21; Customers_Id: 0; Таблица Customers ContactName: Maria Anders; ContactTitle: Sales Representative; Customers_Id: 1; Связанные записи из таблицы Orders OrderDate: 25.08.1997 0:00:00; Freight: 29.46; Customers_Id: 1; OrderDate: 03.10.1997 0:00:00; Freight: 61.02; Customers_Id: 1; OrderDate: 13.10.1997 0:00:00; Freight: 23.94; Customers_Id: 1; Таблица Customers ContactName: Maria Larsson; ContactTitle: Owner; Customers_Id: 2; Связанные записи из таблицы Orders OrderDate: 14.10.1997 0:00:00; Freight: 152.3; Customers_Id: 2; OrderDate: 02.12.1997 0:00:00; Freight: 328.74; Customers_Id: 2; OrderDate: 11.12.1997 0:00:00; Freight: 48.2; Customers_Id: 2; The program '[2460] ConsoleApplication1.exe' has exited with code 0 (0x0). | |
15 Заключение | «Назад |
У меня закончилось время, которое я отвел на написание данного материала. В соответствии с нашей договоренностью (п.1) я ставлю точку. Отрадно, что мы успели разобрать практически все основные моменты. Для дальнейшего чтения рекомендуются темы OpenXML, DiffGrams и XQuery. Спасибо за внимание. Конференция "Корпоративные базы данных 2002" | |
|