По своей структуре это обычные 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>
Рис.3
Теперь, чтобы по этой схеме представить данные из 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>
Рис.4
Аннотация 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-документа.
Разберем еще несколько аннотаций на примере схемы, которая воссоздает по таблице parent-child дерево
иерархии в виде 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>
Рис.5
Он взят из документации к SQLXML 3.0. Таблица Employees содержит записи по сотрудникам и связана сама с
собой, т.е. в поле ReportsTo для каждого сотрудника указывается EmployeeID его начальника.
ms:max-depth задает максимальную глубину
вложенности рекурсии при раскрытии отношения "родитель-потомок".
В отличие от предыдущей ситуации, где количество уровней в иерархии определялось длиной максимальной
ветки связанных таблиц, глубина дерева в случае parent-child таблицы зависит от ветки, по которой мы идем от
корня, и априори неочевидна. Не обладая в текущей версии специальным оператором построения иерархии по
такому типу связи, SQL Server разрешает ее в последовательность соединенных UNION'ами SELECT'ов, каждый
из которых соответствует уровню иерархии. Поэтому их число (ms:max-depth)
SQL Server должен знать заранее.
Максимальное значение для него волевым образом установлено в 50.
Другая аннотация - ms:limit-field - позволяет
провести ограничение (WHERE) по какому-либо полю еще на уровне
схемы, т.е. до того, как дело дойдет до XPath. Обычно она употребляется в паре с ms:limit-value, которая задает
значение критерия. В данном случае эта аннотация опущена, что означает, что по умолчанию берется значение
NULL. Таким образом, верхним уровнем в данной иерархии будут самые-самые начальники (у которых начальников
нет: ReportsTo = Null).
Почему атрибут ID_сотрудника аннотирован как ms:hide="true"? Он несет чисто служебную информацию и вряд
ли понадобится в XML-результате. Но его не хочется выключать из схемы при помощи
ms:mapped="false", потому
что он действительно привязан к информации в БД, которая понадобится в дальнейшем. Например, он может
фигурировать в критерии XPath-запроса: cmd.CommandText = "Сотрудник[@ID_сотрудника='4']" (Чтобы этот
запрос возвратил сотрудника с EmployeeID = 4, нужно убрать фильтрацию в схеме - ms:limit-field).
Наконец, еще одна аннотация, которая сейчас необязательна, но встретится нам через параграф - это ms:key-fields.
Она задает значения полей, составляющих первичный ключ таблицы.
Полный список аннотаций, естественно, не ограничивается теми, которые встретились в этих двух простых
примерах схем. Он достаточно обширен и позволяет строить довольно нетривиальные соответствия между
XML-схемой и реляционным содержанием. Его можно найти в документации на SQLXML 3.0.
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
В Скрипте 10, как и в предыдущем примере (Скрипт 9), на SQL Server посылается XPath-запрос, однако теперь
данные рассматриваются через призму выбранной аннотированной схемы (указывается в свойстве
SqlXmlCommand.SchemaPath) и трактуются в соответствии с задаваемой ею структурой. В данном случае запрос
выбирает всех клиентов из Испании и Франции и сделанные ими заказы. Встроенной поддержкой XPath
(а также XQuery) SQL Server в настоящее время не располагает, поэтому XPath по дороге превращается в то, что
ему более понятно, а именно - в SQL-запрос. Если быть совсем точным, то в запрос типа FOR XML EXPLICIT.
Ради любопытства можете открыть Profiler и посмотреть его для Скрипта 10 (здесь я его приводить не буду,
потому что он занял бы еще как минимум страницу). Поддерживается подмножество стандартного синтаксиса
XPath в части осей, функций и операторов. Отрадно, что каждым SQLXML веб-релизом это подмножество
расширяется.
|
|