Введение в SQL Server Analysis Services для разработчика. CommandText и CommandStream.Источник: blogstechnet
Выполнение произвольного XMLA средствами ADOMD.NET является заманчивым, но, увы, недостижимым решением несмотря на то, что в Интернете можно наткнуться на людей, утверждающих, что им удалось его достичь теми или иными средствами. Кое-где мимоходом упоминается (не буду показывать пальцем на MSDNовский блог одного уважаемого товарища), что все, что для этого нужно - это засунуть текст XMLA-запроса не в CommandText, а в CommandStream объекта AdomdCommand и выполнить ExecuteXmlReader(). К сожалению, это фикция. Документация на ADOMD обложила разработчиков достаточно плотно, не оставив в том числе лазейки в виде CommandStream. В русской MSDN Library на эту тему, кстати, на чистом русском языке говорится: "The AdomdCommand assumes that the System.IO.Stream contains an XML for Analysis compliant command (that is, a command that can be framed by the <Command> tag within an XML for Analysis request)". Выделено мной. Иными словами, что CommandStream, как и CommandText, изо всего текста XMLA-запроса воспринимает только начинку элемента Command и сам за сценой заворачивает ее в полагающиеся родительские элементы. Попытка засунуть в него полноценный XMLA, например, с параметрами в виде элемента <Parameters> - см. Скрипт 1 предыдущего поста - приводит к той же ошибке, что и в случае CommandText - см. Скрипт 4 позапредыдущего поста.
using System.Data; using System.IO; using System.Xml; using System.Diagnostics; using Microsoft.AnalysisServices.AdomdClient; using System.Text;
class Program { static void Main(string[] args) { AdomdConnection cnn = new AdomdConnection(@"Data Source=http://192.168.0.136/msolap/msmdpump.dll; User ID=192.168.0.136\\Administrator;Password=Abra_Chupakabra"); cnn.Open(); string xmla = @"<Execute xmlns='urn:schemas-microsoft-com:xml-analysis'> <Command> <Statement> select [Measures].members on 0, Filter(Customer.[Customer Geography].Country.members, Customer.[Customer Geography].CurrentMember.Name = @CountryName) on 1 from [Adventure Works] </Statement> </Command> <Properties> <PropertyList> <Catalog>Adventure Works DW 2008R2</Catalog> </PropertyList> </Properties> <Parameters> <Parameter> <Name>CountryName</Name> <Value>'United Kingdom'</Value> </Parameter> </Parameters> </Execute>";
AdomdCommand cmd = new AdomdCommand(); cmd.Connection = cnn; cmd.CommandStream = new MemoryStream(Encoding.UTF8.GetBytes(xmla));
XmlReader res = cmd.ExecuteXmlReader(); res.MoveToContent(); res.Read(); Debug.WriteLine(res.ReadOuterXml());
res.Close(); cnn.Close(); } }
Скрипт 1
Рис.1 Чтобы этот код заработал, запрос в CommandStream надо переписать подобно Скрипту 2 из предыдущего поста, взяв только начинку элемента Command и засунув элементы Properties и Parameters в соответствующие свойства объекта AdomdCommand. Тэг <Statement> также можно опускать, как мы помним. Если внутри CommandText (или, соответственно, CommandStream) нет никаких тэгов, ADO MD по умолчанию оборачивает содержимое тэгом <Statement>. static void Main(string[] args) { AdomdConnection cnn = new AdomdConnection("Data Source=http://192.168.0.136/msolap/msmdpump.dll;" + "User ID=192.168.0.136\\Administrator;Password=Abra_Chupakabra"); cnn.Open();
string xmla = @"<Statement> select [Measures].members on 0, Filter(Customer.[Customer Geography].Country.members, Customer.[Customer Geography].CurrentMember.Name = @CountryName) on 1 from [Adventure Works] </Statement>";
AdomdCommand cmd = new AdomdCommand(); cmd.Connection = cnn; cmd.CommandStream = new MemoryStream(Encoding.UTF8.GetBytes(xmla));
cmd.Properties.Add("Catalog", "Adventure Works DW 2008R2"); cmd.Parameters.Add(new AdomdParameter("CountryName", "United Kingdom"));
XmlReader res = cmd.ExecuteXmlReader();
res.MoveToContent(); res.Read(); string s = res.ReadOuterXml();
res.Close(); cnn.Close(); }
Скрипт 2
Рис.2
Метод ExecuteXmlReader возвращает результат в виде XML, как если бы мы выполняли XMLA-запрос из SSMS.
Передача текста команды в свойстве CommandText или CommandStream никак не влияет на формат передачи результата. Если результатом команды является селлсет, его можно получить как в виде XML при помощи метода ExecuteXmlReader, как мы только что видели в Скрипте 2, так и в виде объекта CellSet при помощи метода ExecuteCellSet:
static void Main(string[] args) { AdomdConnection cnn = new AdomdConnection("Data Source=http://192.168.0.136/msolap/msmdpump.dll;" + "User ID=192.168.0.136\\Administrator; Password=Abra_Chupakabra"); cnn.Open();
string xmla = @"<Statement> select [Measures].members on 0, Filter(Customer.[Customer Geography].Country.members, Customer.[Customer Geography].CurrentMember.Name = @CountryName) on 1 from [Adventure Works] </Statement>";
AdomdCommand cmd = new AdomdCommand(); cmd.Connection = cnn; cmd.CommandStream = new MemoryStream(Encoding.UTF8.GetBytes(xmla));
cmd.Properties.Add("Catalog", "Adventure Works DW 2008R2"); cmd.Parameters.Add(new AdomdParameter("CountryName", "United Kingdom"));
CellSet res = cmd.ExecuteCellSet();
for (int i = 0; i < res.Axes[0].Positions.Count; i++) { Debug.WriteLine(""); for (int j = 0; j < res.Axes[1].Positions.Count; j++) Debug.Write(res.Cells[i, j].FormattedValue); }
cnn.Close(); }
Скрипт 3
Я скомбинировал в этом примере первую половину из Скрипта 2, а вторую, которая CellSet, - из Скрипта 2 предыдущего поста. Натурально, получается то же самое, что и на рис.3 предыдущего поста.
Если команда не является запросом, возвращающим кусок кубика, очевидно, что бестолку просить ее при этом возвратить селлсет - будет ошибка. Например, если в предыдущий скрипт вместо селекта подставить XMLA-команду процессинга
<Batch xmlns="http://schemas.microsoft.com/analysisservices/2003/engine"> <Parallel> <Process xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ddl2="http://schemas.microsoft.com/analysisservices/2003/engine/2" xmlns:ddl2_2="http://schemas.microsoft.com/analysisservices/2003/engine/2/2" xmlns:ddl100_100="http://schemas.microsoft.com/analysisservices/2008/engine/100/100"> <Object> <DatabaseID>Adventure Works DW 2008 R2</DatabaseID> <DimensionID>Dim Product</DimensionID> </Object> <Type>ProcessUpdate</Type> </Process> </Parallel> </Batch>
Скрипт 4
при попытке возвратить результат случится ошибка: Pис.3
В то же время если Скрипт 4 в качестве текста команды подставить в Скрипт 2, то при помощи метода ExecuteXmlReader этот запрос нормально выполняется, возвращая результат <root xmlns="urn:schemas-microsoft-com:xml-analysis:empty" />: Рис.4 Это то, что появляется в SSMS в панели Results. Есть еще интересный вопрос, как поймать то, что появляется в панели Messages, но я пока не буду на него отвлекаться, т.к. делается это нетривиально и совершенно не так, как в SQL Server.
Возникает вопрос: зачем параллельно к CommandText'у потребовалось иметь еще свойство CommandStream, если идейно у них одинаковые ограничения? По-видимому, CommandStream предполагается использовать, если содержимое <Command> достаточно велико и хранится в файле. В особенности этим отличаются DDL-команды. Зайдите в SSMS -> Object Explorer, кликните правой кнопкой по кубу Adventure Works и скажите Script Cube As -> Create.
Рис.5
Не повторяйте это на нагруженной машине. Процесс генерации скрипта целиком по кубу достаточно задумчивый. На его выходе рождается здоровый файл XML. Понятно, что если его нужно накатить из пользовательского приложения где-нибудь на другой машине, чтобы воспроизвести по структуре кубик Adventure Works, лучше выполнять его через CommandStream.
static void Main(string[] args) { AdomdConnection cnn = new AdomdConnection("Data Source=http://192.168.0.136/msolap/msmdpump.dll;" + "User ID=192.168.0.136\\Administrator; Password=Abra_Chupakabra"); cnn.Open();
AdomdCommand cmd = new AdomdCommand(); cmd.Connection = cnn; cmd.CommandStream = new FileStream(@"c:\Temp\AdventureWorks.xmla", FileMode.Open, FileAccess.Read); XmlReader res = cmd.ExecuteXmlReader();
res.MoveToContent(); res.Read(); Debug.WriteLine(res.ReadOuterXml());
res.Close(); cnn.Close(); } Скрипт 5
Продолжение следует. |