Как создать расширение для обработки данных для служб отчета SQL Server 2000 Reporting Services, в котором в качестве источника данных используется ADO.NET DataSet.
Загрузить программный код для этой статьи на Visual Basic
Загрузить программный код для этой статьи на C#
Оригинал статьи (EN)
Службы отчетов Reporting Services обеспечивают доступ к источникам данных SQL Server, Oracle, ODBC и OLE DB в качестве стандартных функций. Во многих случаях все, что требуется для отчета — это подключиться к базе данных и сделать запрос, после чего все данные для отчета будут получены. Но что делать, если в качестве источника данных требуется использовать DataSet? Например, если уже имеется промежуточный программный слой, на котором происходит обработка данных и результат хранится в виде DataSet. Или просто вы хотите перед публикацией в отчете обработать данные таким образом , который проще реализовать на Microsoft Visual Basic или C#, чем на SQL, и при этом набор DataSet мог бы быть логичным результатом такой обработки. К счастью, это вполне можно реализовать. Это даже сравнительно просто сделать, если вы выясните, какие расширения интерфейсов обработки данных действительно должны быть реализованы, чтобы обернуть DataSet так, чтобы с ним можно было работать из Reporting Services.
В этой статье мы рассмотрим создание и разработку простого расширения для обработки данных, которое можно использовать для передачи данных DataSet в отчет Reporting Services.
Службы отчетов Reporting Services дают возможность настраивать источники данных, которые доступны благодаря расширениям для обработки данных (data processing extensions). Расширение для обработки данных — это сборка, в которой реализован набор интерфейсов из пространства имен Microsoft.ReportingServices.DataProcessing. Существует шесть интерфейсов, которые должны быть реализованы в любом расширении для обработки данных.
Интерфейс | Описание |
---|---|
IDbConnection | Уникальное соединение с источником данных. В системе |
IDbTransaction | Локальная транзакция. |
IDbCommand | Запрос или команда, которые используются после подключении к источнику данных. |
IDataParameter | Параметр или пара «имя/значение», которые передаются в команду или запрос. |
IDataParameterCollection | Коллекция всех параметров, относящихся к команде или запросу. |
IDataReader | Метод чтения потока данных из источника данных, только в одном направлении и только для чтения. |
Существует еще несколько интерфейсов, которые обеспечивают дополнительную функциональность для подключений, транзакций и т. д., но эти функции не требуются в расширении для обработки данных DataSet. Подробнее о дополнительных интерфейсах можно почитать в разделе Preparing to Implement a Data Processing Extension (EN) в документации по Reporting Services (на английском языке).
Посмотрев на интерфейсы, перечисленные выше, можно убедиться, что создание расширения для обработки данных довольно похоже на создание поставщика данных в Microsoft .NET Framework. Часть интерфейсов имеет такие же названия и обеспечивают подобные (но не идентичные) функциональные возможности, поэтому, если вам доводилось раньше работать с интерфейсами System.Data, обязательно следует учитывать эти отличия.
В целях доступа к DataSet для половины этих интерфейсов необходима самая минимальная реализация, а остальные используются с самыми общими настройками. Давайте посмотрим, что нужно сделать, чтобы заставить это расширение работать.
Ниже рассмотрен пример приложения, в котором данные считываются из двух или более
Откройте новый проект библиотеки классов в Microsoft Visual Studio и добавьте ссылку на файл Microsoft.ReportingServices.Interfaces.dll. Он содержит пространство имен Microsoft.ReportingServices.DataProcessing, на которое потребуется указать ссылку, чтобы получить доступ к расширениям для обработки данных.
Первым пунктом в нашей повестке дня являются классы, в которых реализуются интерфейсы IDbTransaction, IDataParameter и IDataParameterCollection. Эти классы включены только потому, что они требуются в любом расширении для обработки данных. Поскольку в нашем примере мы не подключаемся к базе данных, не изменяем данные и не используем SQL, эти классы не используются ни для чего. Их можно реализовать в формальном виде, как это сделано в коде примера, дополняющего эту статью.
В ваших приложениях может потребоваться более полная реализация этих интерфейсов. Например, в случае, если для получения данных в DataSet потребуется запросить их в реляционной базе данных.
Так как эти обязательные классы в нашем примере фактически не используются, давайте рассмотрим реализации IDbConnection, IDbCommand и IDataReader, которые и выполняют реальную работу в этом расширении.
Поскольку мы не подключаемся к базе данных, класс подключения используется для двух целей.
using System; using System.Data; using System.Configuration; using System.Xml; using Microsoft.ReportingServices.DataProcessing; namespace Microsoft.Samples.ReportingServices.DataSetExtension { public class DSXConnection : Microsoft.ReportingServices.DataProcessing.IDbConnection { private string _connString; // IDbConnection.ConnectionTimeout defaults to 15 seconds. private int _connTimeout = 15; private ConnectionState _state = ConnectionState.Closed; private string _locName = "DataSet Data Extension"; internal string _xmlSchema; // Default constructor. public DSXConnection() { } // Connection string constructor overload. public DSXConnection(string connString) { _connString = connString; } public string ConnectionString { get { return _connString; } set { _connString = value; } } public int ConnectionTimeout { get { return _connTimeout; } } public ConnectionState State { get { return _state; } } // Not used. public Microsoft.ReportingServices.DataProcessing.IDbTransaction BeginTransaction() { return (null); } // Not used. public void Open() { _state = ConnectionState.Open; return; } // Not used. public void Close() { _state = ConnectionState.Closed; return; } // Implemented. public Microsoft.ReportingServices.DataProcessing.IDbCommand CreateCommand() { // Create a Command object and pass in the // Connection object to provide config info. return new DSXCommand(this); } public string LocalizedName { get { return _locName; } } // Implemented. Inherited from // IExtension through IDbConnection. public void SetConfiguration(string configuration) { // Get the XML schema file // from the config file settings. XmlDocument schemaDoc = new XmlDocument(); schemaDoc.LoadXml(configuration); if (schemaDoc.DocumentElement.Name == "XSDConfiguration") { foreach (XmlNode schemaChild in schemaDoc.DocumentElement.ChildNodes) { if(schemaChild.Name == "XSDFile") { _xmlSchema = schemaChild.InnerText; } else { throw new Exception ("Cannot find XSD configuration element."); } } } else { throw new Exception ("Error returning data from the configuration file."); } } public void Dispose() { } } }
Затем следует реализация IDbCommand. Как и в случае с классом Connection, для нашей задачи необходимы только некоторые члены класса Command. В данном случае это перегруженный конструктор объекта Connection, свойство CommandText, а также перегрузка CommandBehavior в методе ExecuteReader.
Перегруженный конструктор обеспечивает ссылку на объект Connection. Все, что нам требуется, — это доступ к сведениям
В свойстве CommandText записывается строка, разделенная запятыми, в которой указаны
При вызове ExecuteReader создаются DataReader и DataSet в качестве его источника данных. Ссылка на DataReader возвращается в вызвыющий объект, то есть в данном случае в наш отчет. Обратите внимание, что необходимо реализовать перегрузку CommandBehavior с поддержкой значения типа SchemaOnly. Как в проектировщике отчетов, так и на сервере отчетов используется эта перегрузка, а не версия без параметров, что позволяет получить сведения о полях в дополнение к основным данным.
using System; using System.Data; using System.ComponentModel; using Microsoft.ReportingServices.DataProcessing; namespace Microsoft.Samples.ReportingServices.DataSetExtension { public class DSXCommand : Microsoft.ReportingServices.DataProcessing.IDbCommand { private string _cmdText; private DSXConnection _connection; // IDbCommand.CommandTimeout defaults to 30 seconds. private int _cmdTimeout = 30; private Microsoft.ReportingServices.DataProcessing.CommandType _cmdType; private DSXParameterCollection _parameters = new DSXParameterCollection(); // Default constructor. public DSXCommand() { } // Command text constructor overload. public DSXCommand(string cmdText) { _cmdText = cmdText; } // Connection object constructor overload. public DSXCommand(DSXConnection connection) { _connection = connection; } public string CommandText { get { return _cmdText; } set { _cmdText = value; } } public int CommandTimeout { get {return _cmdTimeout;} set {_cmdTimeout = value;} } public Microsoft.ReportingServices.DataProcessing.CommandType CommandType { get { return _cmdType; } set { _cmdType = value; } } public Microsoft.ReportingServices.DataProcessing.IDataParameterCollection Parameters { get { return _parameters; } } public Microsoft.ReportingServices.DataProcessing.IDbTransaction Transaction { get { return (null); } set { throw new NotSupportedException(); } } // Not used. public void Cancel() { throw new NotSupportedException(); } // Not used. public Microsoft.ReportingServices.DataProcessing.IDataParameter CreateParameter() { return (null); } // Implemented. public Microsoft.ReportingServices.DataProcessing.IDataReader ExecuteReader (Microsoft.ReportingServices.DataProcessing.CommandBehavior behavior) { try { // Create the DataReader. DSXDataReader testReader = new DSXDataReader(_cmdText); // Call the custom method that // populates the DataSet. testReader.CreateDataSet(_connection._xmlSchema); // Return the DataReader. return testReader; } catch(Exception e) { throw new Exception(e.Message); } } public void Dispose() { } } }
Наконец, существует класс DataReader, реализующий IDataReader. Этот класс наиболее полно конкретизирован, так как в проектировщике отчетов используется большинство его членов, чтобы получить данные для отображения. Также имеются две специальные функции CreateDataSet и ParseCmdText, которые обеспечивают дополнительные возможности. ParseCmdText разбирает входную строку с исходными
using System; using System.Data; using System.Xml; using System.Collections; using Microsoft.ReportingServices.DataProcessing; namespace Microsoft.Samples.ReportingServices.DataSetExtension { public class DSXDataReader : Microsoft.ReportingServices.DataProcessing.IDataReader { private string _cmdText; private int _currentRow = 0; private int _fieldCount = 0; private string _fieldName; private int _fieldOrdinal; private Type _fieldType; private object _fieldValue; private DataSet _ds = null; // Default constructor internal DSXDataReader() { } //Command text constructor overload. internal DSXDataReader(string cmdText) { _cmdText = cmdText; } // Implemented. Will be called // by the Report Server to // return DataSet data. public bool Read() { _currentRow++; if (_currentRow >= _ds.Tables[0].Rows.Count) { return (false); } else { return (true); } } public int FieldCount { get { _fieldCount = _ds.Tables[0].Columns.Count; return _fieldCount; } } public string GetName(int i) { _fieldName = _ds.Tables[0].Columns[i].ColumnName; return _fieldName; } public Type GetFieldType(int i) { _fieldType = _ds.Tables[0].Columns[i].DataType; return _fieldType; } public Object GetValue(int i) { _fieldValue = _ds.Tables[0].Rows[this._currentRow][i]; return _fieldValue; } public int GetOrdinal(string name) { _fieldOrdinal = _ds.Tables[0].Columns[name].Ordinal; return _fieldOrdinal; } // Input parameter should be the path // to the .xsd file that was retrieved // from the Connection.SetConfiguration call. internal void CreateDataSet(string schemaFile) { // Open an XML doc to hold the data. XmlDocument xmlDoc = new XmlDocument(); // Create the DataSet. DataSet ds = new DataSet("Customers"); // Create the schema for the DataSet. ds.ReadXmlSchema(schemaFile); // Parse the command text string for the files. string[] parameters = this.ParseCmdText(); // Get the XML data and // merge it into the DataSet. try { for(int i=0;i<parameters.GetLength(0);i++) { DataSet tempDs = new DataSet(); tempDs.ReadXml(parameters[i]); ds.Merge(tempDs); } } catch (Exception e) { throw new Exception(e.Message); } // Set the DataSet variable used in // the rest of the DataReader members // to the one just produced. _ds = ds; // Set the current row to -1 // to prepare for reading. _currentRow = -1; } private string[] ParseCmdText() { // Check format of command text. if (_cmdText.IndexOf(",") != -1) { string[] dsParams = _cmdText.Split(new Char[]{','}); // In production code, you'd // want more error handling here // confirming that the string values // are appropriate XML file names, etc. return dsParams; } else throw new ArgumentException ("The CommandText value is not in the appropriate format."); } public void Dispose() { } } }
Возможно, что у вас уже имеется компонент промежуточного слоя, в котором выполняется определенная обработка и создается набор DataSet, и именно его необходимо использовать вместо того, чтобы создавать новый набор DataSet в коде расширения. Это довольно легко: нужно добавить ссылку на сборку и вызвать метод, который возвращает DataSet, как показано в примере ниже:
using System; using System.Data; using System.Xml; using System.Collections; using Microsoft.ReportingServices.DataProcessing; using ThisCompany.ThisAssembly; namespace ThisCompany.ThisNamespace.DataSetExtension { public class ExtensionDataReader : Microsoft.ReportingServices.DataProcessing.IDataReader { private DataSet _ds = null; // More variables... internal ExtensionDataReader(thisParameter) { // Get the DataSet from the middle-tier assembly. this._ds = new ThisCompany.ThisAssembly. Customer.IntegrateDataSources (thisParameter); // Set the current row. currentRow = -1; } ...
В качестве резюме всего этого процесса можно сказать, что сервер отчетов должен выполнить следующие операции при запуске этого отчета:
— | вызвать конструктор по умолчанию DSXConnection; | ||||||
— | вызвать метод DSXConnection.CreateCommand, в котором используется перегруженный конструктор DSXCommand Connection для создания объекта Command; | ||||||
— | вызвать DSXCommand.ExecuteReader(CommandBehavior), который, в свою очередь:
|
Вот и все. Честно.
Теперь вы можете создать решение, и мы переходим развертыванию.
Скопируйте RSCustomData.DLL в папку сервера отчетов (по умолчанию C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer\bin) и папку проектировщика отчетов (по умолчанию C:\Program Files\Microsoft SQL Server\80\Tools\Report Designer). Затем откройте файл c настройками сервера отчетов, RSReportServer.config, который по умолчанию находится в папке C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer. В узел <Data> добавьте новый дочерний узел <Extension>. В нем нужно прописать все параметры, которые используются при работе расширения. Для элемента <Extension> требуются два атрибута: атрибут Name, уникальное имя вашего расширения, и атрибут Type, который содержит полное имя класса подключения, а также имя сборки (без расширения .dll), указанные через запятую. Это все, что требуется, узел <Configuration> дополнять не обязательно, если он не используется, хотя в нашем примере мы это делаем. Новый узел должен выглядеть следующим образом:
<Data> <Extension Name="DataSet" Type="Microsoft.Samples.ReportingServices.DataSetExtension.DSXConnection,RSCustomData"> <Configuration> <XSDConfiguration> <XSDFile> C:\customer.xsd </XSDFile> </XSDConfiguration> </Configuration> </Extension> </Data>
Если вы работаете с примером на Visual Basic, не забудьте изменить пространство имен на Microsoft.Samples.ReportingServices.DataSetExtensionVB.
Сохраните и закройте файл настройки сервера отчетов и откройте файл проектировщика отчетов RSReportDesigner.config из папкиC:\Program Files\Microsoft SQL Server\80\Tools\Report Designer. Добавьте аналогичный узел <Extension> в раздел <Data> этого файла. Кроме того, потребуется добавить немного отличающийся элемент <Extension> в узел <Designer>. Этот элемент <Extension> также содержит атрибуты Name и Type. Атрибут Name должен содержать такое же уникальное имя расширения, которое указано в элементе <Extension> в узле <Data>. В атрибуте Type через запятую указаны полное имя класса проектировщика запросов и имя содержащей его сборки (без расширения .dll).
<Extension Name="DataSet" Type="Microsoft.ReportDesigner.Design.GenericQueryDesigner, Microsoft.ReportingServices.Designer" />
Благодаря универсальному интерфейсу в проектировщике отчетов при работе с расширением используются простые панели запросов и результатов, а не полнофункциональные визуальные мастера запросов, которые больше подходят для реляционных баз данных.
Теперь нам нужно добавить элементы в файлы политики сервера и проектировщика отчетов, явно указав разрешения на доступ для кода расширения. Для успешной работы расширения для обработки данных необходимы разрешения FullTrust.
Откройте файл политики сервера отчетов rssrvpolicy.config, который по умолчанию находится в папке C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer, а затем создайте новый узел <CodeGroup>:
<CodeGroup class="UnionCodeGroup" version="1" PermissionSetName="FullTrust" Name="DataSetExtensionGroup" Description="This code group grants data extensions full trust."> <IMembershipCondition class="UrlMembershipCondition" version="1" Url="C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services \ReportServer\bin\RSCustomData.dll" /> </CodeGroup>
Затем добавьте идентичный узел <CodeGroup> в файл политики проектировщика отчетов rspreviewpolicy.config, расположенный по умолчанию в папке C:\Program Files\Microsoft SQL Server\80\Tools\Report Designer.
Эти элементы предоставляют расширению DataSet права FullTrust, основываясь на его
Теперь, когда мы создали и развернули расширение с DataSet, очень просто использовать его в отчете. Откройте Visual Studio и начните новый проект с отчетом. Щелкните правой кнопкой мыши на папке Shared Data Sources и выберите пункт Add New Data Source. В раскрывающемся списке Type должна появиться строка с расширением для обработки данных DataSet.
Рис. 1. Папка Shared Data Source
Выберите наше расширение. Затем на панели Credentials выберите вариант No Credentials. Учетные данные не требуются, так как к базе данных подключаться не нужно. Сохраните этот источник данных.
Щелкните правой кнопкой мыши на папке Reports и выберите пункт Add New Report. В окне мастера отчетов выполните нужные действия. На экране Select the Data Source примите настройки по умолчанию для только что созданного источника данных расширения DataSet.
Рис. 2. Экран «Select the Data Source»
На экране Design the Query введите через запятую строку, содержащую полные пути ко всем исходным
Рис. 3. Экран «Design the Query»
Завершите создание отчета, указав необходимые вам параметры. В этом примере были приняты параметры по умолчанию. При предварительном просмотре наш пример отчета должен выглядеть как на рисунке (я немного изменила компоновку поля
Рис. 4. Пример отчета Reporting Services
Чтобы установить отчет на сервере, укажите имя сервера (обычно http://MachineName/ReportServer) в свойствах проекта, а затем, чтобы установить решение, выберите Debug, Start (отчет также появится в браузере).
Итак, после доработки
Hitchhiker's Guide to SQL Server 2000 Reporting Services (EN)