论坛交流
首页办公自动化| 网页制作| 平面设计| 动画制作| 数据库开发| 程序设计| 全部视频教程
应用视频: Windows | Word2007 | Excel2007 | PowerPoint2007 | Dreamweaver 8 | Fireworks 8 | Flash 8 | Photoshop cs | CorelDraw 12
编程视频: C语言视频教程 | HTML | Div+Css布局 | Javascript | Access数据库 | Asp | Sql Server数据库Asp.net  | Flash AS
当前位置 > 文字教程 > Sql Server教程
Tag:注入,存储过程,分页,安全,优化,加密,索引,日志,压缩,base64,函数,内存,PDF,迁移,结构,破解,编译,配置,进程,分词,触发器,socket,安装,sqlserver2000,sqlserver2005,sqlserver2008,视频教程

Microsoft Reporting Services in Action:用自定义代码扩展 Microsoft SQL Server 2000 Reporting Servic…

文章类别:Sql Server | 发表日期:2008-10-5 21:35:44

下载 Code.zip 示例代码以获取本文的示例代码。 www.想自杀chinai tp ow er.comnuoEkxe

用自定义代码扩展 Microsoft SQL Server 2000 Reporting Services 用自定义代码扩展 Microsoft SQL Server 2000 Reporting Services
编写嵌入式代码 编写嵌入式代码
使用外部程序集 使用外部程序集
运转中的自定义代码:实现报表预测 运转中的自定义代码:实现报表预测
迁移 OpenForecast 迁移 OpenForecast
小结 小结

用自定义代码扩展 Microsoft SQL Server 2000 Reporting Services

Microsoft 在 2004 年初发布了 Microsoft SQL Server 2000 Reporting Services (Reporting Services),以便为开发人员提供一个完整的报表平台,无论目标平台或开发语言是什么,它都可以轻松地与所有类型的应用程序集成。Reporting Services 最显著的功能之一是它的可扩展特性,这是包括我在内的许多开发人员所欣赏的。您可以扩展或替换 Reporting Services 的几乎任何方面,包括数据、传递、安全性以及报表呈现功能。例如,扩展您的报表功能的一个方法是将它们与您或其他人编写的自定义 .NET 代码集成在一起。www.想自杀chinai tp ow er.comnuoEkxe

在本文中,我将为您展示如何利用 Reporting Services 独特的可扩展体系结构来增强您的报表功能。首先,我将说明嵌入式和自定义代码选项是如何工作的。其次,我将为您展示您可以如何利用自定义代码来编写带有销售预测功能的高级报表。 www.想自杀chinai tp ow er.comnuoEkxe

我将假定您已经具有关于 Reporting Services 的基础知识,并且知道如何用表达式编写报表。如果您还不熟悉 Reporting Services,请访问其官方站点。本文中讨论的代码示例和示例报表均包含在文章源代码中。示例报表将 AdventureWorks2000 数据库用作其数据源,该数据库可以从 Reporting Services 安装程序中安装。www.想自杀chinai tp ow er.comnuoEkxe

返回页首返回页首

编写嵌入式代码

顾名思义,嵌入式代码保存在报表定义 (RDL) 文件中;它的作用范围在报表层。您只能在 Microsoft Visual Basic .NET 中编写嵌入式代码。一旦代码准备好,您就可以使用全局定义的 Code 成员在报表表达式中调用它。例如,如果您编写了一个名为 GetValue 的嵌入式代码函数,就可以使用下列语法从您的表达式中调用它:www.想自杀chinai tp ow er.comnuoEkxe

=Code.GetValue()

除了共享方法外,您的嵌入式代码可以包含任何与 Visual Basic .NET 兼容的代码。实际上,如果您将嵌入式代码视为项目中的私有类,就差不多了。您可以声明类级别的成员和常数、私有或公共方法等。www.想自杀chinai tp ow er.comnuoEkxe

您可以编写嵌入式代码来创建可重用的实用函数,它们可以从报表的几个表达式中调用。例如,请考虑图 1 中所示的 Territory Sales Crosstab 报表。www.想自杀chinai tp ow er.comnuoEkxe

erscstcode01

图 1. 您可以使用嵌入式代码来实现作用范围在报表层的有用实用函数。www.想自杀chinai tp ow er.comnuoEkxe

 

当数据缺失时(给定的行列组合中没有报表数据),该报表会使用一个名为 GetValue 的嵌入式函数来显示 “N/A”。此外,GetValue 还将缺失数据与 NULL 值区分开来。当基础值为 NULL 时,嵌入式代码会将其转换为零。www.想自杀chinai tp ow er.comnuoEkxe

使用代码编辑器www.想自杀chinai tp ow er.comnuoEkxe

要编写自定义嵌入式代码,您可以使用报表设计器代码编辑器 — 您可以在 Report Properties 对话框的 Code 选项卡上找到它,如图 2 所示。www.想自杀chinai tp ow er.comnuoEkxe

erscstcode02

图 2. 使用用于编写嵌入式代码的代码编辑器。编辑器中所示的 GetValue 函数可确定某个值是缺失还是 NULL。www.想自杀chinai tp ow er.comnuoEkxe

 

诚然,上述函数可以很容易地由一个基于 Iif 的表达式替换。但是,将逻辑封装在嵌入式函数中有两点优势。首先,它将表达式的逻辑集中在一个地方,而不是在报表中的每个字段都使用 Iif 函数。其次,它使报表具有更好的可维护性,这是因为如果您决定对函数进行逻辑更改,将不必跟踪并更改报表中的每个 Iif 函数。www.想自杀chinai tp ow er.comnuoEkxe

报表设计器将嵌入式代码保存在报表定义文件的<Code> 元素下。执行此操作时,报表设计器将对文本进行 URL 编码。如果您出于某些原因决定直接更改 Code 元素,就需要注意这一点。www.想自杀chinai tp ow er.comnuoEkxe

处理缺失值www.想自杀chinai tp ow er.comnuoEkxe

一旦 GetValue 函数可以区分报表中的 NULL 和缺失数据,我们就能以下列表达式作为交叉表报表的 txtSalestxtNoOrders 数据字段的基础:分别为www.想自杀chinai tp ow er.comnuoEkxe

=Iif(CountRows()=0, "N/A", Code.GetValue(Sum(Fields!Sales.Value)))

www.想自杀chinai tp ow er.comnuoEkxe

=Iif(CountRows()=0, "N/A", Code.GetValue(Sum(Fields!NoOrders.Value)))

www.想自杀chinai tp ow er.comnuoEkxe

CountRows 函数是 Reporting Services 所提供的几个原生函数之一,它可以返回指定范围内的行数。如果没有指定范围,它将默认为最里面的范围,这在我们的示例中解析为在数据单元格中定义值的静态组。两个表达式都会先使用 CountRows 来检查缺失数据(没有行),如果未发现缺失数据,将显示 “N/A”。否则,它们将调用 GetValue 嵌入式函数来转换 NULL 值。www.想自杀chinai tp ow er.comnuoEkxe

我推荐您使用嵌入式代码来编写简单的报表专用且类似于实用工具的函数。当您的编程逻辑变得越来越复杂时,请考虑将您的代码移到外部程序集,我们将在下一步讨论这一点。www.想自杀chinai tp ow er.comnuoEkxe

返回页首返回页首

使用外部程序集

以编程方式扩展报表的第二种方法是使用外部 .NET 程序集中的预打包逻辑,这些程序集可以使用任何 .NET 支持的语言编写。这种将报表与外部程序集中的自定义代码集成在一起的能力显著提高了编程的选择余地。例如,通过使用自定义代码,您可以: www.想自杀chinai tp ow er.comnuoEkxe

利用 .NET framework 丰富的功能集 — 或示例,比如说您需要一个集合来存储某一矩阵区域的交叉表数据以执行一些计算。您可以“借用”.NET 随附的任何集合类,如 ArrayArrayListHashtable 等。 www.想自杀chinai tp ow er.comnuoEkxe

将您的报表与您或第三方供应商编写的自定义 .NET 程序集集成在一起。例如,为了向第二部分中的 Sales by Product Category 报表添加预测功能,我使用了开放源码的 OpenForecast 包。 www.想自杀chinai tp ow er.comnuoEkxe

通过使用功能强大的 Visual Studio .NET IDE 而不是原始的代码编辑器,编写代码变得更加简单了。 www.想自杀chinai tp ow er.comnuoEkxe

引用外部程序集www.想自杀chinai tp ow er.comnuoEkxe

要使用位于外部程序集中的类型,您必须先使用 Report Properties 对话框中的 References 选项卡让报表设计器知道它,如图 3 所示。www.想自杀chinai tp ow er.comnuoEkxe

erscstcode03

图 3. 使用“Report Properties”对话框来引用外部程序集。www.想自杀chinai tp ow er.comnuoEkxe

 

假定我的报表需要使用自定义的 AWC.RS.Library 程序集(包含在文章的源代码中),我必须首先使用 References 选项卡引用它。虽然这个选项卡允许您浏览并引用任意文件夹中的程序集,但请注意,当报表被执行时,.NET 公共语言运行库 (CLR) 将根据 CLR 探测规则尝试定位程序集。简单地说,这些规则为您提供了两个部署自定义程序集的选项: www.想自杀chinai tp ow er.comnuoEkxe

将程序集部署为私有程序集。 www.想自杀chinai tp ow er.comnuoEkxe

将程序集部署为 .NET 全局程序集缓存 (GAC) 中的共享程序集。作为一个先决条件,您必须强命名您的程序集。有关如何执行此操作的详细信息,请参阅 .NET 文档。 www.想自杀chinai tp ow er.comnuoEkxe

如果您选择了第一个选项,则需要将程序集同时部署到报表设计器和报表服务器中,这样引用该程序集的报表将在测试期间顺利执行,而且是作为托管报表分别执行。假定您已经接受了默认的安装设置,那么如果要将程序集部署到报表设计器二进制文件夹中,请将程序集复制到 C:\Program Files\Microsoft SQL Server\80\Tools\Report Designer 中。一旦您完成此操作,就可以构建报表并在 Visual Studio .NET 的预览模式中呈现它。www.想自杀chinai tp ow er.comnuoEkxe

作为将报表部署到报表目录的一部分,请确保您将程序集复制到报表服务器二进制文件夹中,其默认位置是 C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer\bin。 www.想自杀chinai tp ow er.comnuoEkxe

请注意,将自定义程序集复制到正确位置只是部署过程的一部分。根据代码的任务,您可能还需要调整代码访问安全策略,以便程序集代码可以顺利执行。如果您需要有关部署自定义程序集的详细信息,请参阅 Reporting Services 文档中的“Using Custom Assemblies with Reports”部分。www.想自杀chinai tp ow er.comnuoEkxe

调用共享方法www.想自杀chinai tp ow er.comnuoEkxe

如果您只需要调用程序集中的共享方法(在 C# 中也称为静态方法),那么就可以这么做了,这是因为共享方法在报表中全局可用。 www.想自杀chinai tp ow er.comnuoEkxe

您可以通过下列语法,用完全限定的类型名称来调用共享方法:www.想自杀chinai tp ow er.comnuoEkxe

<Namespace>.<Type>.<Method>(argument1, argument2, ..., argumentN)

例如,如果我需要从一个表达式或嵌入式代码中调用 RsLibrary 类(AWC.RS.Library 程序集)中的 GetForecastedSet 共享方法,我会使用下列语法:www.想自杀chinai tp ow er.comnuoEkxe

=AWC.Reporting Services.Library.RsLibrary.GetForecastedSet(forecastedSet, forecastedMonths)

其中,AWC.RS.Library 是命名空间,RsLibrary 是类型,GetForecastedSet 是方法,还有 forecastedSetforecastedMonths 是参数。www.想自杀chinai tp ow er.comnuoEkxe

调用实例方法www.想自杀chinai tp ow er.comnuoEkxe

要调用实例方法,您还有一些额外的工作要做。首先,您必须枚举需要在 Classes 网格中实例化的所有实例类(类型)。对于每个类,您都必须指定一个实例名称。在后台,Reporting Services 将创建一个具有该名称的变量,以保存对此类型实例的引用。www.想自杀chinai tp ow er.comnuoEkxe

当您在 Classes 网格中指定类名称时,请确保您输入的是完全限定的类型名称(包含命名空间)。在我的示例中(图 3),命名空间是 AWC.RS.Library,而类名称是 RsLibrary。如果您不确定完全限定类名称是什么,请使用 Visual Studio .NET Object Browser 或其他实用工具(如出色的 Lutz Roeder 的 .NET Reflector)来定位类名称并查找其命名空间。www.想自杀chinai tp ow er.comnuoEkxe

例如,假定我需要调用 AWC.RS.Library 程序集中的一个实例方法,那么现在我必须声明一个实例变量 m_Library,如图 3 所示。在我的示例中,这个变量将保存对 RsLibrary 类的引用。www.想自杀chinai tp ow er.comnuoEkxe

如果您要声明多个指向同一类型的变量,则每个变量都需要引用一个该类型的单独实例。在后台,当报表被处理时,Reporting Services 将实例化和实例变量数量一样多的所引用类型的实例。www.想自杀chinai tp ow er.comnuoEkxe

一旦完成引用设置,您就可以通过所指定的实例类型名称来调用实例方法。就像使用嵌入式代码一样,您可以使用 Code 关键字来调用实例方法。共享方法和实例方法之间的区别是您使用变量名称来调用方法,而不是使用类名称。www.想自杀chinai tp ow er.comnuoEkxe

例如,如果 RsLibrary 类型具有一个实例方法 DummyMethod(),我就能从一个表达式或嵌入式代码中调用它,如下所示:www.想自杀chinai tp ow er.comnuoEkxe

Code.m_Library.DummyMethod()

了解了我们作为开发人员,以编程方式扩展报表功能时所使用的选择之后,下面看一下如何将其付诸实践。在下一部分中,我们将了解如何使用嵌入式代码和外部代码向我们的报表中添加高级功能。www.想自杀chinai tp ow er.comnuoEkxe

返回页首返回页首

运转中的自定义代码:实现报表预测

在本部分中,我将向您展示如何在我们的报表中植入预测功能。下面是我们将要创建的示例报表的设计目标: www.想自杀chinai tp ow er.comnuoEkxe

让用户可以生成任意阶段的销售数据交叉表报表。 www.想自杀chinai tp ow er.comnuoEkxe

让用户可以指定预测列的数量。 www.想自杀chinai tp ow er.comnuoEkxe

使用数据外推法来预测销售数据。 www.想自杀chinai tp ow er.comnuoEkxe

以下是我们的虚拟案例。假设您的用户请求了一个报表,以显示按产品类别分组的 Adventure Works 月度预测销售数据。为了让事情变得更加有趣,我们将允许报表用户指定一个数据范围来筛选销售数据以及预测月份的数量。为了实现上述要求,我们将编写一个交叉表报表 Sales by Product Category,如图 4 所示。www.想自杀chinai tp ow er.comnuoEkxe

erscstcode04

图 4. Sales by Product Category 使用嵌入式代码和外部自定义代码进行预测。www.想自杀chinai tp ow er.comnuoEkxe

 

用户可以输入起止日期来筛选销售数据。此外,用户可以指定报表上将显示多少个月的预测数据。报表以交叉表方式显示数据,在行上显示产品类别,在列上显示时间。报表的数据部分首先显示所请求时间段内的实际销售额,后面以粗体显示预测销售额。 www.想自杀chinai tp ow er.comnuoEkxe

例如,如果用户输入 4/30/2003 作为起始日期,输入 3/31/2004 作为截止日期,然后请求查看三个预测月,那么报表将显示 2004 年 4 月、5 月和 6 月的预测数据(为节省空间,图 4 只显示了一个月的预测数据)。www.想自杀chinai tp ow er.comnuoEkxe

您可能也承认,独自实现预测功能并不是一件简单的任务。但如果已经有了为我们执行此操作的预打包代码,又会怎么样呢?如果这个代码可以运行在 .NET 上,我们的报表就可以将其作为自定义代码来访问。输入 OpenForecast。www.想自杀chinai tp ow er.comnuoEkxe

用 OpenForecast 来预测www.想自杀chinai tp ow er.comnuoEkxe

预测本身就是一门科学。一般来说,预测关心的是用于预言未知的过程。预测专业人员使用数学模型来分析数据、发现趋势并作出有根据的推断,而不是去看水晶球。在我们的示例中,Sales by Product Category 报表将通过数据外推方法来预测未来的销售数据。 www.想自杀chinai tp ow er.comnuoEkxe

用来外推一组数据的众所周知的数学模型有很多,如多项式回归、简单指数平滑法等。但是,实现这些模型并不是一件简单的任务。相反,为了我们的销售预测示例,我们将使用由 Steven Gould 编写的出色的开放源码 OpenForecast 包。OpenForecast 是一个包含基于 Java 的预测模型的通用软件包,这些模型可以应用于任何数据系列。这个软件包不要求您了解任何预测知识,并且支持几个数学预测模型,包括单变量线性回归、多变量线性回归等。要了解有关 OpenForecast 的详细信息,请访问它的主页 http://OpenForecast.sourceforge.net/www.想自杀chinai tp ow er.comnuoEkxe

现在,让我们看一下如何实现预测示例,并通过编写一些嵌入式代码和外部代码与 OpenForecast 集成。www.想自杀chinai tp ow er.comnuoEkxe

实现报表预测功能www.想自杀chinai tp ow er.comnuoEkxe

创建一个具有预测功能的交叉表报表需要以下几个实现步骤。让我们从一个预想方法的高级视图开始,然后向下追溯到实现细节。www.想自杀chinai tp ow er.comnuoEkxe

选择一种实现方法www.想自杀chinai tp ow er.comnuoEkxe

图 5 显示了解决方案的逻辑体系结构视图。www.想自杀chinai tp ow er.comnuoEkxe

erscstcode05

图 5. Sales by Product Category 报表使用嵌入式代码来调用 AwRsLibrary 程序集,接着调用 J# OpenForecast 包。www.想自杀chinai tp ow er.comnuoEkxe

 

我们的报表将使用嵌入式代码来调用一个自定义程序集 (AwRsLibrary) 中的共享方法,并获得预测数据。AwRsLibrary 会将现有的销售数据加载到一个 OpenForecast 数据集中,并从 OpenForecast 获得预测模型。然后,它将调用 OpenForecast 来获取所请求月份的预测值。AwRsLibrary 将预测数据返回给报表,然后再显示它。www.想自杀chinai tp ow er.comnuoEkxe

我们至少有两种实现选择可以将交叉表销售数据传递到 AwRsLibrarywww.想自杀chinai tp ow er.comnuoEkxe

再次从数据库中获取销售数据。要实现这一点,报表可以按行传递选定的产品类别和月销售额。然后,AwRsLibrary 可以进行数据库调用以检索匹配的销售数据。 www.想自杀chinai tp ow er.comnuoEkxe

使用报表内部的嵌入式代码将现有销售数据加载到某种类型的结构中,并将该结构传递到 AwRsLibrarywww.想自杀chinai tp ow er.comnuoEkxe

后一种方法的优点是: www.想自杀chinai tp ow er.comnuoEkxe

自定义代码逻辑是独立的。我们不必再次查询数据库。 www.想自杀chinai tp ow er.comnuoEkxe

使用默认的自定义代码安全策略。我们不必为 AwRsLibrary 程序集提高默认的代码访问安全策略。如果我们选择了第一个选项,就不能略过默认代码访问安全设置,这是因为 Reporting Services 将只授予自定义程序集“执行”的权力,而这对于进行数据库调用是不够的。实际上,对于 OpenForecast,我必须对两个程序集都授予完全信任的权力,这是因为任何 J# 代码都需要完全信任权力来顺利执行。但是,如果我选择 C# 作为编程语言,就不必再执行此操作了。 www.想自杀chinai tp ow er.comnuoEkxe

无需数据同步。我们不必考虑同步两个数据容器、矩阵区域和 AwRsLibrary 数据集。 www.想自杀chinai tp ow er.comnuoEkxe

出于上述原因,我选择了第二种方法。为了实现此方法,我们将使用一个表达式来填充矩阵区域数据值。该表达式将调用我们的嵌入式代码以加载一个数组结构,该数组结构是在嵌入式代码中按行来维护的。一旦给定的行被加载,我们就将该数组传递到 AwRsLibrary 以获得预测数据。 www.想自杀chinai tp ow er.comnuoEkxe

现在,让我们从将 OpenForecast 转换到 .NET 开始来讨论实现细节。www.想自杀chinai tp ow er.comnuoEkxe

返回页首返回页首

迁移 OpenForecast

OpenForecast 是用 Java 编写的,因此我必须克服的第一个障碍就是将其与 .NET 集成在一起。我有两个选择: www.想自杀chinai tp ow er.comnuoEkxe

我可以使用一个第三方的 Java 到 .NET 网关来集成两个平台。由于这种方法的复杂性,我很快就放弃了它。 www.想自杀chinai tp ow er.comnuoEkxe

将 OpenForecast 转换到支持 .NET 的语言之一。Microsoft 为此提供了两个选择。第一,您可以使用 Microsoft Java Language Conversion Assistant 将 Java 语言代码转换到 C#。第二,我可以将 OpenForecast 转换到 J#。这会保留 Java 语法,尽管该代码将在 .NET 公共语言运行库(而不是 Java 虚拟机)的控制下执行。 www.想自杀chinai tp ow er.comnuoEkxe

我决定将 OpenForecast 转换到 J#。这种方法附带的好处是,开放源码开发人员可以只维护一个基于 Java 的 OpenForecast 版本。www.想自杀chinai tp ow er.comnuoEkxe

将 OpenForecast 转换到 J# 比我想象的要简单。我创建了一个新的 J# 库项目,将其命名为 OpenForecast,然后在其中加载了所有 *.java 源文件。我在源代码中包含了 .NET 版本的 OpenForecast,本文随附有该源代码。我只须顾及几个 MultipleLinearRegression 中的编译错误,实际结果是,有几个 Java 哈希表方法在 J# 中不受支持,如 keySet()entries() 以及哈希表克隆。我还包含了一个 WinForm 应用程序 (TestHarness),您可以使用它来测试转换后的 OpenForecast。www.想自杀chinai tp ow er.comnuoEkxe

实现 AwRsLibrary 程序集www.想自杀chinai tp ow er.comnuoEkxe

下一步是创建自定义的 .NET 程序集 AwRsLibrary,它将跨接报表嵌入式代码和 OpenForecast。我将 AwRsLibrary 作为一个 C# 类库项目来实现。在其中,我创建了一个类 RsLibrary,它公开了一个静态(共享)方法 GetForecastedSet。该方法的 AwRsLibrary 代码包含在本文的示例代码中。www.想自杀chinai tp ow er.comnuoEkxe

GetForecastedSet 方法以数据集数组的形式接收给定产品类别的现有销售数据,以及对预测数据请求的月数。接着,集成 OpenForecast 有五个步骤:www.想自杀chinai tp ow er.comnuoEkxe

步骤 1:首先,我们创建一个新的 OpenForecast 数据集,并用来自矩阵行数组的现有数据加载它。 www.想自杀chinai tp ow er.comnuoEkxe

步骤 2:接下来,我们获得一个给定的预测模式。OpenForecast 可让开发人员通过调用 getBestForecast 方法,来根据给定数据系列获得最佳的预测数学模型。该方法将检查数据集并尝试几个预测模型,以便选择最理想的一个。如果返回的模型不是很合适,您可以通过实例化在模型项目文件夹下找到的任何类来显式地请求一个预测模型。 www.想自杀chinai tp ow er.comnuoEkxe

步骤 3:接下来,我们准备另一个数据集来保存预测数据,并使用与预测月数一样多的元素来初始化该数据集。 www.想自杀chinai tp ow er.comnuoEkxe

步骤 4:最后,我们调用 forecast 方法来外推数据并返回预测结果。 www.想自杀chinai tp ow er.comnuoEkxe

步骤 5:剩下的最后一件事是将预测数据加载回数据集数组中,以便我们能够将其传回报表嵌入式代码。www.想自杀chinai tp ow er.comnuoEkxe

在我们完成了 AwRsLibraryOpenForecast 两个 .NET 程序集之后,就需要部署它们。www.想自杀chinai tp ow er.comnuoEkxe

部署自定义程序集www.想自杀chinai tp ow er.comnuoEkxe

我们需要将自定义程序集同时部署到报表设计器和报表服务器的二进制文件夹中。自定义程序集的部署过程由下列步骤组成: www.想自杀chinai tp ow er.comnuoEkxe

将程序集复制到报表设计器和报表服务器的二进制文件夹中。 www.想自杀chinai tp ow er.comnuoEkxe

如果自定义代码需要一个提高的代码访问安全权限集,则需要调整基于代码的安全性。 www.想自杀chinai tp ow er.comnuoEkxe

要让 AwRsLibraryOpenForecast 两个程序集在设计时都可用,我们必须将 AWC.RS.Library.dll 和 OpenForecast.dll 复制到报表设计器文件夹中,其默认位置是 C:\Program Files\Microsoft SQL Server\80\Tools\Report Designer。 www.想自杀chinai tp ow er.comnuoEkxe

同样,要在报表服务器下顺利呈现已部署的报表,我们必须将两个程序集都部署到报表服务器的二进制文件夹中,其默认位置是 C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer\bin。事实上,如果所引用的自定义程序集尚未全部部署好,报表服务器将不会让您从 Visual Studio .NET IDE 中部署报表。 www.想自杀chinai tp ow er.comnuoEkxe

默认的 Reporting Services 代码访问安全策略在默认情况下对所有自定义程序集授予执行权限。但是,J# 程序集需要完全信任的代码访问权限。因为 .NET 公共语言运行库沿调用堆栈向上遍历来验证所有调用方都具有必要的权限集,所以我们需要将两个程序集的代码访问安全策略提升到完全信任级别。这将需要对报表设计器和报表服务器的安全配置文件进行更改。 www.想自杀chinai tp ow er.comnuoEkxe

为了帮助您设置代码访问安全策略,我提供了我的 Config 文件夹中的 rssrvpolicy.config 的副本。在接近文件末尾的地方,您将看到两个 CodeGroup XML 元素,分别指向 AwRsLibraryOpenForecast 文件。您需要将这些元素复制到报表服务器的安全配置文件 (rssrvpolicy.config) 中。 www.想自杀chinai tp ow er.comnuoEkxe

此外,如果您希望在报表设计器的预览窗口中预览(运行)报表,那么还需要将这些更改传播到报表设计器的安全配置文件 (rspreviewpolicy.config) 中。 www.想自杀chinai tp ow er.comnuoEkxe

在自定义程序集部署之后,我们需要在报表中编写一些 Visual Basic .NET 嵌入式代码来调用 AwRsLibrary 程序集,此内容将在下一部分中进行讨论。www.想自杀chinai tp ow er.comnuoEkxe

编写报表嵌入式代码www.想自杀chinai tp ow er.comnuoEkxe

要将报表与 AwRsLibrary 集成,我编写了 GetValue 函数,如清单 2 所示。www.想自杀chinai tp ow er.comnuoEkxe

清单 2. 嵌入式 GetValue 函数调用 AwRsLibrary 程序集www.想自杀chinai tp ow er.comnuoEkxe

Dim forecastedSet() As Double  ' array with sales data

Dim productCategoryID As Integer = -1

Dim bNewSeries As Boolean = False

Public Dim m_ExString = String.Empty  ' holds the error message, if any

Function GetValue(productCategoryID As Integer, orderDate As DateTime, sales As Double, reportParameters as Parameters, txtRange as TextBox) As Double

Dim startDate as DateTime = reportParameters!StartDate.Value

Dim endDate as DateTime = reportParameters!EndDate.Value

Dim forecastedMonths as Integer = reportParameters!ForecastedMonths.Value

If (forecastedSet Is Nothing) Then

ReDim forecastedSet(DateDiff(DateInterval.Month, startDate, endDate) +

forecastedMonths)          #1

End If

If Me.productCategoryID <> productCategoryID Then    #2

Me.productCategoryID = productCategoryID

bNewSeries = True

Array.Clear(forecastedSet, 0, forecastedSet.Length - 1)

End If

Dim i = DateDiff(DateInterval.Month, startDate , orderDate)

' Is this a forecasted value?

If orderDate <= endDate Then

' No, just load the value in the array

forecastedSet(i) = sales

Else

If bNewSeries Then

Try

AWC.RS.Library.RsLibrary.GetForecastedSet(forecastedSet, forecastedMonths)  #3

bNewSeries = False

Catch ex As Exception

m_ExString  = "Exception: " & ex.Message

System.Diagnostics.Trace.WriteLine(ex.ToString())

throw ex

End Try

End If

End If

Return forecastedSet(i)

End Function

因为矩阵区域数据单元格使用了一个引用 GetValue 函数的表达式,所以这个函数可由每个数据单元格调用。表 1 列出了 GetValue 函数所采用的输入参数。www.想自杀chinai tp ow er.comnuoEkxe

表 1. 矩阵区域中的每个数据单元格都将调用 GetValue 嵌入式函数,并传递下列输入参数。
参数 用途

productCategoryIDwww.想自杀chinai tp ow er.comnuoEkxe

与单元格相对应的 rowProductCategory 行分组的 ProductCategoryID 值。www.想自杀chinai tp ow er.comnuoEkxe

orderDatewww.想自杀chinai tp ow er.comnuoEkxe

与单元格相对应的 colMonth 列分组的 OrderDate 值。www.想自杀chinai tp ow er.comnuoEkxe

saleswww.想自杀chinai tp ow er.comnuoEkxe

该单元格的合计销售总数。www.想自杀chinai tp ow er.comnuoEkxe

reportParameterswww.想自杀chinai tp ow er.comnuoEkxe

为了计算数组维度,GetValue 需要报表参数的值。我传递了一个对报表参数集合的引用,而不是使用 Parameters!ParameterName.Value 个别地传递参数。www.想自杀chinai tp ow er.comnuoEkxe

txtRangewww.想自杀chinai tp ow er.comnuoEkxe

保存错误消息的变量,以防在获取预测数据时发生异常。www.想自杀chinai tp ow er.comnuoEkxe

 

要了解 GetValue 如何工作,请注意矩阵区域内的每个数据单元格都是来自 forecastedSet 数组的。如果单元格不需要预测(它的相应日期处于请求日期范围内),则我们只需在数组中加载单元格的值,并将其传回以便在矩阵区域中显示它。为了实现此操作,我们需要初始化该数组以获得一个等于请求月数加预测月数的秩。一旦矩阵区域移动到一个新的行并调用我们的函数后,我们就可以通过调用 AwRsLibrary:GetForecastedSet 方法来预测数据了。www.想自杀chinai tp ow er.comnuoEkxe

实现 Sales by Product Category 交叉表报表www.想自杀chinai tp ow er.comnuoEkxe

编写报表本身最困难的部分就是设置它的数据,以确保我们在矩阵区域中始终通过正确的列数来显示预测的列。默认情况下,矩阵区域将不显示没有数据的列。这会扰乱从数组传送到单元格的正确偏移量的计算。 www.想自杀chinai tp ow er.comnuoEkxe

因此,我们必须确保数据库返回请求数据范围内所有月份的记录。要实现这一点,我们需要在数据库中预处理销售数据。这正是 spGetForecastedData 存储过程的任务。在这个存储过程中,我用请求数据范围内的所有月度周期预填充了一个自定义表,如清单 3 所示。www.想自杀chinai tp ow er.comnuoEkxe

清单 3. spGetForecastedData 存储过程确保返回的行集合具有正确的列数www.想自杀chinai tp ow er.comnuoEkxe

CREATE  PROCEDURE spGetForecastedData (

@StartDate smalldatetime,

@EndDate smalldatetime

)

AS

DECLARE @tempDate smalldatetime

DECLARE @dateSet TABLE   (       #1

ProductCategoryID   tinyint,

OrderDate    smalldatetime

)

SET   @tempDate = @EndDate

WHILE (@StartDate <= @tempDate)      #2

BEGIN

INSERT INTO @dateSet

SELECT ProductCategoryID,  @tempDate

FROM ProductCategory

SET @tempDate = DATEADD(mm, -1, @tempDate)

END

SELECT      DS.ProductCategoryID, PC.Name as ProductCategory, OrderDate AS Date, NULL AS Sales

FROM      @dateSet DS INNER JOIN ProductCategory PC ON DS.ProductCategoryID=PC.ProductCategoryID

UNION ALL            #3

SELECT     PC.ProductCategoryID, PC.Name AS ProductCategory, SOH.OrderDate AS Date, SUM(SOD.UnitPrice * SOD.OrderQty) AS Sales

FROM         ProductSubCategory PSC INNER JOIN

ProductCategory PC ON PSC.ProductCategoryID = PC.ProductCategoryID INNER JOIN

Product P ON PSC.ProductSubCategoryID = P.ProductSubCategoryID INNER JOIN

SalesOrderHeader SOH INNER JOIN

SalesOrderDetail SOD ON SOH.SalesOrderID = SOD.SalesOrderID ON P.ProductID = SOD.ProductID

WHERE     (SOH.OrderDate BETWEEN @StartDate AND @EndDate)

GROUP BY SOH.OrderDate, PC.Name, PC.ProductCategoryID

ORDER BY PC.Name, OrderDate

最后,我用可获取销售数据的实际 Transact-SQL 语句联合了表 @dateSet(其 Sales 列值设置为 NULL)的所有记录。 www.想自杀chinai tp ow er.comnuoEkxe

设置数据集之后,编写报表的其余部分就简单多了。我们对报表的交叉表部分使用了一个矩阵区域。要了解矩阵区域魔法是如何工作并调用嵌入式 GetValue 函数的,您可能要用下列表达式替换 txtSales 文本框的表达式:www.想自杀chinai tp ow er.comnuoEkxe

图 6 显示了在应用该表达式时,Sales by Product Category 的外观。www.想自杀chinai tp ow er.comnuoEkxe

erscstcode06

图 6. 矩阵区域如何聚合数据。www.想自杀chinai tp ow er.comnuoEkxe

 

如您所见,我们可以轻松地获得相应的行和列组值,矩阵区域将使用这些值来计算区域数据单元格中的聚合值。现在,我们有一种方法来标识每个数据单元格。矩阵区域的设置如表 2 所示。www.想自杀chinai tp ow er.comnuoEkxe

表 2. 用预测值填充矩阵区域的窍门是让其数据单元格以表达式为基础。
矩阵区域 名称 表达式

www.想自杀chinai tp ow er.comnuoEkxe

rowProductGroupwww.想自杀chinai tp ow er.comnuoEkxe

=Fields!ProductCategory.Valuewww.想自杀chinai tp ow er.comnuoEkxe

www.想自杀chinai tp ow er.comnuoEkxe

colYearwww.想自杀chinai tp ow er.comnuoEkxe

colMonthwww.想自杀chinai tp ow er.comnuoEkxe

=Fields!Date.Value.Yearwww.想自杀chinai tp ow er.comnuoEkxe

=Fields!Date.Value.Monthwww.想自杀chinai tp ow er.comnuoEkxe

数据www.想自杀chinai tp ow er.comnuoEkxe

txtSaleswww.想自杀chinai tp ow er.comnuoEkxe

=Code.GetValue(Fields!ProductCategoryID.Value, Fields!Date.Value, Sum(Fields!Sales.Value), Parameters, ReportItems!txtRange)www.想自杀chinai tp ow er.comnuoEkxe

 

要实现预测列(以粗体显示)的条件格式,我对 txtSales 文本框的字体属性使用了下列表达式:www.想自杀chinai tp ow er.comnuoEkxe

=Iif(Code.IsForecasted(Fields!Date.Value, Parameters!EndDate.Value), "Bold", "Normal")

该表达式调用了报表嵌入式代码中的 IsForecasted 函数。该函数仅仅将销售月度日期与所请求的截止日期相比较,如果销售日期在截止日期之前,则返回 falsewww.想自杀chinai tp ow er.comnuoEkxe

最后,剩下的最后一件事情就是使用报表的 References 选项卡来引用 AwRsLibrary 程序集,如我们在图 3 中看到的那样。请注意,对于该报表,我们不需要设置实例名称(不需要在 Classes 网格中输入任何内容),这是因为我们不调用任何实例方法。www.想自杀chinai tp ow er.comnuoEkxe

调试自定义代码www.想自杀chinai tp ow er.comnuoEkxe

您可能发现调试自定义代码比较有挑战性。因为这个原因,我想与您分享几个对于调试自定义代码比较有用的技术。www.想自杀chinai tp ow er.comnuoEkxe

对于调试嵌入式代码来说,没有太多的选择。到目前为止,我所发现的唯一方法就是:当报表在报表设计器中呈现时,使用 MsgBox 函数来输出消息和变量值。确保在将报表部署到报表服务器之前删除对 MsgBox 的调用。如果您没有删除,则所有 MsgBox 调用都将导致异常。因为某些原因,在嵌入式代码内使用 System.Diagnostics.Trace (OutputDebugString API) 跟踪消息会导致“被吞没”,并且既不会在 Visual Studio .NET 输出窗口中显示,也不会在使用一个外部跟踪工具时显示。www.想自杀chinai tp ow er.comnuoEkxe

在使用外部程序集时,您至少有两个调试选择: www.想自杀chinai tp ow er.comnuoEkxe

输出跟踪消息。 www.想自杀chinai tp ow er.comnuoEkxe

使用 Visual Studio .NET 调试器来逐句通过自定义代码。 www.想自杀chinai tp ow er.comnuoEkxe

跟踪www.想自杀chinai tp ow er.comnuoEkxe

例如,在 AwRsLibrary.GetForecastedSet 方法中,我正在使用 System.Dianogistics.Trace.WriteLine 输出跟踪消息,以显示观察值和预测值。要想在 Visual Studio .NET 或报表服务器中运行报表时查看这些消息,您可以使用 Mark Russinovich 开发的出色的 DebugView 工具,如图 7 所示。www.想自杀chinai tp ow er.comnuoEkxe

erscstcode07

图 7. 在 DebugView 中输出来自外部程序集的跟踪消息。www.想自杀chinai tp ow er.comnuoEkxe

 

调试自定义代码www.想自杀chinai tp ow er.comnuoEkxe

您还可以通过将 Visual Studio .NET 调试器附加到报表设计器进程,来逐句通过自定义程序集代码,如下所示: www.想自杀chinai tp ow er.comnuoEkxe

在 Visual Studio .NET 的一个新实例中打开您要调试的自定义程序集。像往常一样在您的代码中设置断点。 www.想自杀chinai tp ow er.comnuoEkxe

在您的自定义程序集项目属性中,选择 Configuration Properties->Debugging,然后将 Debug Mode 设置为 Wait to Attach to an External Processwww.想自杀chinai tp ow er.comnuoEkxe

在 Visual Studio .NET 的另一个实例中打开您的商业智能项目。 www.想自杀chinai tp ow er.comnuoEkxe

返回到自定义程序集项目,单击 Debug 菜单,然后单击 Processes...。找到承载商业智能项目的 devevn 进程,并附加到它上面。在 Attach To Process 对话框中,确保 Common Language Runtime 复选框已选中,然后单击 Attach。此时,您的 Processes 对话框应该类似于图 8 中所示的对话框。 www.想自杀chinai tp ow er.comnuoEkxe

ERSCstCode08_thumb

图 8. 要调试自定义程序集,应连接到承载商业智能项目的 Visual Studio 实例。www.想自杀chinai tp ow er.comnuoEkxe

 

在我的示例中,我想在 Sales by Product Category 报表调用 AwRsLibrary 程序集时,调试其中的代码。出于这个原因,我在 AwRsLibrary 项目中连接到 AWReporter devenv 进程。 www.想自杀chinai tp ow er.comnuoEkxe

在商业智能项目中,预览调用自定义程序集的报表。或者,如果您已经在预览报表,则单击 Preview Tab 工具栏上的 Refresh Report 按钮。此时,您的断点应该被 Visual Studio .NET 调试器发现。 www.想自杀chinai tp ow er.comnuoEkxe

您很快就会发现,如果需要更改代码并重新编译自定义程序集,则在尝试将其重新部署到报表设计器文件夹时,会导致下列异常:www.想自杀chinai tp ow er.comnuoEkxe

Cannot copy <assembly name>: It is being used by another person or program.

问题在于 Visual Studio .NET IDE 保留了对自定义程序集的引用。您需要关闭 Visual Studio .NET,然后重新部署新的程序集。要避免此问题,您可以使用 Report Host(预览窗口)来调试自定义程序集代码。要进行此操作,请执行以下步骤: www.想自杀chinai tp ow er.comnuoEkxe

将自定义程序集添加到包含商业智能项目的 Visual Studio .NET 解决方案中。 www.想自杀chinai tp ow er.comnuoEkxe

将商业智能项目的起始项更改为调用自定义代码的报表,如图 9 所示。 www.想自杀chinai tp ow er.comnuoEkxe

ERSCstCode09_thumb

图 9. 使用 Report Host 调试选项来避免锁定程序集。www.想自杀chinai tp ow er.comnuoEkxe

 

按下 F5 在预览窗口中运行报表。当报表调用自定义代码时,您的断点将被捕获。 www.想自杀chinai tp ow er.comnuoEkxe

在使用预览窗口方法时,Visual Studio .NET 不会锁定自定义程序集。这可让您将程序集的生成位置更改到报表设计器文件夹,以便在您重新生成程序集时,它始终包含最新的副本。在预览窗口中运行项目,是报表设计器配置文件 (rspreviewpolicy.config) 中指定的代码访问安全策略设置的一个主题。www.想自杀chinai tp ow er.comnuoEkxe

返回页首返回页首

小结

在本文中,我们了解到如何将报表与我们或其他人编写的自定义代码集成在一起。www.想自杀chinai tp ow er.comnuoEkxe

对于简单的报表专用编程逻辑,可以使用嵌入式 Visual Basic .NET 代码。当代码的复杂性增加或者您希望使用 Visual Basic .NET 以外的编程语言时,可以将您的代码移到外部程序集中。www.想自杀chinai tp ow er.comnuoEkxe

使用自定义代码只是开发人员扩展 Reporting Services 的几种方法之一。要了解有关 Reporting Services 扩展性的详细信息,请参阅 Reporting Services 联机丛书的“Extending Reporting Services”部分。www.想自杀chinai tp ow er.comnuoEkxe

 www.想自杀chinai tp ow er.comnuoEkxe

视频教程列表
文章教程搜索
 
Sql Server推荐教程
Sql Server热门教程
看全部视频教程
购买方式/价格
购买视频教程: 咨询客服
tel:15972130058