Следующая ступень эволюции - 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.ехе).
|