XML в MS SQL Server 2000 и технологиях доступа к данным - Аннотированные схемы

ОГЛАВЛЕНИЕ

Аннотированные схемы

По своей структуре это обычные 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 веб-релизом это подмножество расширяется.