Следующая ступень эволюции - 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 отсутствует.
static
void
Execute_FORXMLQuery_ADODB()
{
...
ADODB.StreamClass
str = new
ADODB.StreamClass();
str.Open(System.Type.Missing,
ADODB.ConnectModeEnum.adModeUnknown,
ADODB.StreamOpenOptionsEnum.adOpenStreamUnspecified, "",
"");
ADODB.CommandClass
cmd = new
ADODB.CommandClass();
cmd.ActiveConnection
= cnn;
cmd.CommandText
= "SELECT '<Root>' "
+
"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 "
+
"SELECT
'</Root>'";
cmd.Parameters.Append(cmd.CreateParameter("@Name",
ADODB.DataTypeEnum.adVarWChar,
ADODB.ParameterDirectionEnum.adParamInput, 30, null));
cmd.Parameters.Append(cmd.CreateParameter("@Year",
ADODB.DataTypeEnum.adInteger,
ADODB.ParameterDirectionEnum.adParamInput, 4, null));
cmd.Dialect
=
"{C8B522D7-5CF3-11CE-ADE5-00AA0044773D}";
cmd.Properties["Output
Stream"].Value =
str;
cmd.Properties["Output
Encoding"].Value =
"UTF-8";
object RecsAffected = null, Params = new
object[] {"Maria Larsson",
1997};
cmd.Execute(out RecsAffected, ref Params, (int)ADODB.ExecuteOptionEnum.adExecuteStream);
MSXML2.DOMDocument40Class
xmlDoc = new
MSXML2.DOMDocument40Class();
xmlDoc.load(str);
}
Скрипт 3
Скрипт 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
Тип команды |
Значение в ADO |
Константа в OLE DB |
Запрос Transact-SQL |
{C8B522D7-5CF3-11CE-ADE5-00AA0044773D} |
DBGUID_SQL |
Запрос XPath |
{EC2A4293-E898-11D2-B1B7-00C04F680C56} |
DBGUID_XPATH |
Запрос в XML-шаблоне |
{5D531CB2-E6Ed-11D2-B252-00C04F681B71} |
DBGUID_MSSQLXML |
Поведение провайдера по умолчанию |
{C8B521FB-5CF3-11CE-ADE5-00AA0044773D} |
DBGUID_DEFAULT |
Табл.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 в настоящее время поддерживается по соображениям
совместимости, но развиваться в дальнейшем не будет.
Для
предиката FOR XML существует три возможных опции: AUTO, RAW и
EXPLICIT. Действие первой мы уже видели. Она дает SQL Server
указание сформировать простое вложенное дерево, где иерархия
(вложенность) определяется порядком связывания таблиц в
запросе. Каждой таблице из FROM, хотя бы одно поле которой
попадает в вывод, ставится в соответствие элемент, имя
которого равно имени / псевдониму таблицы. Поля таблиц
отвечают атрибутам элементов. Отсюда следует, что все поля в
запросе должны быть поименованы. Недопустимо, например, SELECT
1 FOR XML… Опция RAW формирует плоский неиерархический
документ независимо от отношений между таблицами в запросе. Он
подобен тому, что мы видели на рис.1. Каждая запись результата
соответствует элементу с именем row. Опция EXPLICIT наиболее
гибкая из трех и позволяет получить XML произвольной
структуры, однако recordset, по которому он строится, должен
следовать определенным правилам расположения записей и
именования полей, чтобы однозначно задать желаемую структуру
дерева. Я не буду сейчас подробно расписывать эти правила,
т.к. несмотря на гибкость, способ этот достаточно громоздок и
на практике применяется редко. В основном он используется
самим SQL Server'ом для преобразования реляционной структуры к
аннотированной схеме (см.п.9). Подробно узнать про опцию
EXPLICIT можно в документации на SQL Server (см. XML and
Internet Support -> Using EXPLICIT Mode).
Поддерживаются
параметризованные запросы с FOR XML и процедуры, возвращающие
SELECT … FOR XML. Передача параметров осуществляется
стандартно при помощи коллекции Parameters объекта
ADODB.Command. Поля типа text / ntext возвращаются в виде
текста, поля типа image - в виде их XPath-пути. FOR XML …,
Binary Base64 возвращает их в кодировке Base64 и для опции RAW
это единственный возможный способ вывести значения BLOB-типов.
FOR XML AUTO, ELEMENTS отображает поля не на атрибуты, а на
подэлементы. SELECT TOP 0 ... FOR XML AUTO, XMLData дает схему
XML-результата в формате XDR. Начиная с SQLXML 2.0 включена
поддержка XSD-схем и утилита для конвертации XDR в XSD
(cvtschema.ехе).