组织使用 Microsoft ® Excel ® 进行以下活动:执行复杂的计算,使用图表、数据透视表等查看信息,以及执行许多其他自定义任务。但在过去,如果您要实施计算引擎,则需要获得开发人员提供的服务,这些开发人员将使用业务分析人士提供的算法来设计代码。现在,使用 Office SharePoint ® Server 2007 中的 Excel 服务技术,业务分析人员自己就可实现所需的计算引擎,从而与以前相比,降低了实现的成本并使维护计算算法更轻松。此外,通过 Excel 服务,Excel 工作簿中的自定义算法可在 Web 服务器上运行,从而使用户能够远程访问它们。正如您所料,这意味着更多用户可从更多位置利用软件。
Excel 服务体系结构
让我们来看一下 Excel 服务体系结构如何实现这种灵活性。Excel 服务包含三层 — Web 前端、应用程序服务器和数据库(见图 1)。SharePoint 内容数据库构成了数据库层。要实现服务器端的 Excel 行为,请将工作簿放置在受信任的 SharePoint 位置或网络文件共享上。某些功能(如其他安全功能)仅可通过 SharePoint 获得。
图 1 Excel Web 服务体系结构
应用程序服务器包含 Excel Calculation Services,后者负责加载给定的工作簿和执行任何所需的计算。工作簿实例可连接到外部数据源。
Web 前端负责通过 SharePoint Web 部件以 HTML 形式显示工作簿的相关部分。Web 前端还负责展示允许远程访问 Excel Calculation Services 的 Web 服务端点。
Excel Services 体系结构的一个重要方面在于它与 SharePoint 2007 相集成。如前所述,要实现某些服务器端行为,需要将工作簿存储在 SharePoint 内容数据库内。这样,就可以利用 SharePoint 内容管理功能,如版本控制、签入/签出以及 Excel 工作簿环境中的安全角色和权限。
类似地,Excel Calculation Services 基于 SharePoint Shared Service Provider (SSP) 模型。SSP 是一种将 SharePoint 功能打包为服务的机制,使跨不同站点的管理和使用更加轻松。因此,可以跨多个 SharePoint 站点重新使用 Excel Calculation Service 实例,以及通过 SharePoint 管理站点对其进行管理。
您还应注意 Excel Services 对 Excel 具有一些限制。Excel Services 不支持基于宏和非托管代码的加载项,如 Visual Basic for Applications (VBA)。反而,Excel Services 支持托管服务器端用户定义函数 (UDF),该接口允许从服务器端工作簿调用自定义计算。
在下文中,我们将查看一个 UDF 代码示例。通过构建一个托管 UDF 来封装非托管代码,可克服与非托管代码加载项相关的限制。对 VBA 和宏的使用进行限制是很困难的,但实质上这或许是一件好事,因为它可防止服务器端计算逻辑变得难以处理。
Excel Services API
现在让我们看看用于与服务器端工作簿交互的基于 Web 服务的 Excel Services API,使用图 2 中的代码段作为以下讨论的基础。请注意,为了清晰起见,已经取消了代码。
首先,我们需要确保客户端可访问工作簿。通过 Excel Service API(Microsoft.Office.Excel.Server.WebServices 的一个组成部分),可访问保存在服务器中某一位置的任何工作簿。通过发布功能,Excel 2007 客户端使工作簿的发布变得更加轻松。使用发布机制的好处就是,您可通过 Excel Services API 控制工作簿的可访问部分(表、视图、数据透视表等)。API 中的主要类是 ExcelService 类,如图 2 中所示。此类代表工作簿的一个内存中的服务器端实例。为了实现多个用户与一个工作簿的同时交互,已执行了基于会话的访问模型。每个用户使用 ExcelService 类的 OpenWorkbook 方法打开一个单独的与工作簿的会话。该 OpenWorkbook 方法返回一个与打开的会话相关的唯一会话 ID。当调用任何后继方法与打开的工作簿交互时,需要提供此会话标识符。要在工作簿中设置一个命名区域,您可使用 RangeCoordinates 类定义命名区域的界限。SetRange 采用了 RangeCoordinates 以及包含将要作为参数传入的值的相应数组。SetRange 方法的一种变体是 SetRangeA1 方法,它使用 Excel 区域规范“A1”而不是 SetRange 使用的区域坐标。一旦指定了所有要求的区域值,就可调用 CalculateWorkbook 强制工作簿计算公式。通过调用一个 CancelRequest 方法,可以取消最近的 CalculateWorkbook 方法。您可使用 GetRange 方法获取根据打开工作簿的一个区域计算出的值。一旦检索出全部计算的值,就可使用 CloseWorkbook 方法关闭工作簿会话。
Excel Services 可通过添加 UDF 进行扩展,可将这些 UDF 作为与内置 Excel 函数相似的单元格公式进行访问。要创建一个 UDF,您需要创建一个 Microsoft® .NET Framework 程序集,该程序集包含至少一个用 UdfClassAttribute 标记的类以及至少一个用 UdfMethodAttribute 标记的方法。请参考以下代码段。在此,我们将 ConvertToUpper 定义为 UDF 方法。在适当地注册 UDF 后,ConvertToUpper 函数可包含在 Excel Services 工作簿实例内:
using Microsoft.Office.Excel.Server.Udf; [UdfClass] public class Util { [UdfMethod] public string ConvertToUpper(string name) { return name.ToUpper(); } }
在这里,我们只介绍了 Excel Services API 的一小部分内容。有关更多详细信息,请参考 MSDN® 文档。
使用 Excel Services 的自定义解决方案
开发自定义解决方案的主要目的就是允许业务分析人员直接将计算(如财务模型)编写为 Excel 公式。目前为止,业务分析人员主要依靠以文本形式的伪代码说明算法。然后,开发人员将伪代码转换为代码。使用 Excel Services,我们能够克服在 Excel 中创建公式的过程固有的一些限制,从而开发人员不必将伪代码转换为真实代码。
允许非开发人员编写计算逻辑的主要挑战,就是利用强制实现稳定结构的能力获得编写工作的灵活性与简单性之间的平衡。要提供该结构,我们需要一种定义输入和输出“接口”的方法,该接口表示计算算法的数据约定。业务分析人员将只能处理作为流入和流出计算实例数据的数据约定一部分的命名区域。
明显的选择是在 Excel 中使用命名的单元格或区域构造定义数据约定。命名区域不仅要与在 Excel 中编写计算所要求的粒度级别一致,还是作为 Excel Services API 方法(如 SetRange 和 GetRange)基础的基本数据结构。但是,使用命名区域的挑战就是:没有用来定义该接口的标准格式或语言,如 XML 架构定义 (XSD) 或 Web 服务描述语言 (WSDL)。更糟糕的是,命名区域(因此还有 Excel Service API 方法)的类型本质上是不安全的。例如,无法强制对给定命名区域进行数据类型检查。最后,没有内置方法用来对计算(Excel 内部)和 Excel Services 客户端程序强制执行约定。
为了克服这些限制,我们开发了一个包含两部分的自定义解决方案。第一部分是一个 Excel 预编译器,旨在基于定义的接口生成命名区域。第二部分是一个通用 Excel Web 服务客户端,用于在遵循该接口时调用工作簿内的计算。
Excel 预编译器
XML 架构定义的所有语义丰富而简单,似乎是定义接口的理想选择。我们决定使用 XML 架构构造来定义输入和输出约定。接下来,我们需要一种方法来将 XML 架构转换为命名区域。我们首先考虑使用随 Excel 2003 引入的 XMLMap 功能。XMLMap 允许将 Excel 中的单元格映射到导入的 XML 架构的元素。遗憾的是,XMLMap 功能对于 Excel Services 不可用。所以,另一种选择是在 Excel 工作簿内部创建命名区域。我们开发了一种预编译器组件,它可以基于架构使用所需的命名区域生成模板工作簿。生成的模板工作簿有三个表,分别用于输入、输出和计算。输入表包含与计算的输入对应的命名区域。类似地,输出表包含与计算的输出对应的命名区域,计算表用于存放计算的值(请参见图 3)。
图 3 工作簿的输入表、计算表和输出表 (单击该图像获得较大视图)
如前所述,编程构造(如循环)对于 Excel Services 不可用。预编译器通过将输入 XML 字段转换为无需复杂的编程构造即可访问的格式弥补了这种限制。例如,可以将 XSD 元素集合转换为命名区域的维度。图 4 描述了一个 XSD 代码段,该代码段是计算引擎的输入数据约定的一部分。
元素 TypeA 和 TypeB 是计算输入的一部分。请注意定义命名区域维度的自定义属性 RangeHeight 和 RangeWidth。预编译器使用此信息生成命名区域维度。预编译器还可以取消各索引字段的引用,将其分为各个单独的列 — 其中每一列代表一个索引值。
预编译器值得注意的一个方面是它能够在重新生成输入和输出表的同时保留现有的计算。如图 5 所示,计算算法的开发是一个迭代过程。业务分析人员和开发人员协同工作以定义最初的数据约定。在工作簿的开发过程中,可能需要修改输入和输出约定,并且必须在重新生成工作簿后这些更改才生效。预编译器通过在重新生成工作簿的同时保留计算表来支持此类迭代开发。
图 5 使用预编译器生成工作簿
图 3 中所示的输入表具有预生成的命名区域。计算表具有引用在输入表中定义的命名区域的计算。接着,输出表引用来自计算表中的计算。
Excel Web 服务客户端
Excel Web 服务客户端的主要作用是使用 Excel Services API 调用工作簿内部的计算。在执行此操作时,它解释基于 XSD 的输入约定,将架构元素映射至适当的命名区域。计算完成后,客户端立即将输出命名区域映射回基于 XSD 的输出约定。图 6 描述了 Excel Web 服务客户端的角色。类型化的 DataSet(基于输入架构约定)作为输入传入。DataSet 中包含的数据将被映射到输入命名区域。计算完成后,会立即将输出命名区域用于填充输出 DataSet。Excel Web 服务客户端负责应用为预编译器定义的规则。还可能插入自定义数据转换以改变 XSD 和命名区域之间的上述映射。在上文中,我们讨论过可供工作簿作者使用的编程结构缺乏,需要对此进行补偿。自定义数据转换允许更改映射,以便业务分析人员创作计算逻辑更加轻松。
图 6 调用自定义计算引擎
深入探讨代码
我们构建的解决方案包含四个项目。PreCompiler 项目容纳了所有代码,用于根据输入和输出架构生成带有所需命名区域的工作簿。它在生成命名区域时考虑上述自定义属性,如 RangeHeight。接着,PreCompiler 项目依赖 SpreadsheetML(一种基于 XML 的语言,用于表示电子表格内部的信息)生成工作簿。SpreadsheetML 项目包含用于封装 SpreadsheetML 组件(如工作簿、工作表等)的简单类。
客户端项目,顾名思义,是 Excel Web 服务客户端代码。它设置输入命名区域的值,强制工作簿重新计算并检索输出命名区域的值。您会想起,我们曾经讨论过需要转换数据以使业务分析人员开发计算更加轻松。为了允许为每个计算引擎自定义数据转换,我们通过定义一个 IDataTransformer 接口对数据转换逻辑进行了外部化,如下所示:
public interface IDataTransformer { object getRangeData(string RangeName); object[] getRangeData(string RangeName, int width); object[,] getRangeData(string RangeName, int width, int height); string getInputSchema(); string getOutputSchema(); string getInputSchemaPrefix(); string getOutputSchemaPrefix(); }
包含 IDataTransformer 接口实现的程序集名称作为客户端程序的输入传入。接着,客户端程序对实现 IDataTransformer 接口的类回调适当的方法。通过这种方式,客户端程序获得用于填充命名区域的值。IDataTransformer 方法内部的实现逻辑负责将输入 DataSet 内部的数据转换为适当的命名区域值。例如,在填充适当的命名区域前,可能需要对 DataTable 中的一些行进行筛选。或者,DataTable 内部的行在传递到工作簿前可能需要进行排序。使用 IDataTransformer 接口,所有此类数据转换需求都能够得到满足。
此处要讨论的另一个重要类是 ExcelServiceFacade。此类对调用方隐藏 Excel Service API 详细信息。此类的另一个重要功能是将各个 SetRange 调用合并到一个聚合的 SetRange 调用中。由于每次调用 SetRange 都会导致往返服务器的操作,这对于减少网络延迟至关重要。通过公布一个最终转换为聚合 SetRange 调用中的本地 SetRange 调用,ExcelServiceFacade 可以大幅度缩短响应时间。图 7 描述了相关的 ExcelServiceFacade 代码。内部缓冲区由 ExcelServiceFacade 类维护,每次使用“本地”SetRange 调用时都会附加该类。填充所有输入命名区域后,内部缓冲区将作为单个调用的一部分发送到服务器。完成计算后,检索输出命名值时将使用相似机制。我们只一次性检索输出表中的所有值,而不是逐个检索输出命名区域中的值。
性能和可伸缩性
我们发现工作簿中计算的实际执行非常快。在对包含重要计算集工作簿(大概涉及一百个命名区域)进行的一次非科学测试中,响应时间低于一秒;其中大部分时间花在往返 Excel 服务器的操作上。随着计算复杂性和并发执行数的不断增加,系统上的负载也不断增加,因此可以通过利用 Excel Services 提供的各种拓扑选项缩放解决方案。不同的拓扑选项使您可以选择放置每个逻辑 Excel Services 层(表示层、应用层和数据库层)的位置。对于小型安装(主要用于测试),可以在单个服务器上部署所有这三层。对于中型安装,表示层和应用层可以安装在单个服务器上,数据库层安装在单独的服务器上。对于大型安装,您可以分别在单独的服务器上安装这三层中的每层。此外,您可以通过添加使用网络负载平衡程序的更多服务器来扩展表示层。也可以使用 SSP 框架支持的负载平衡架构扩展包含 Excel Calculation Services 的应用层。图 8 描述了大型安装,其中每层都安装在单独的服务器上。此外,表示层和应用层是使用负载平衡架构扩展的。
图 8 Excel Web Services 大型安装
对于要求大量计算的工作负载,还可以将 Excel Services 与计算群集服务器结合以无缝地向计算节点分发工作,如图 9 所示。
图 9 Excel Services 高性能安装
您可以看到,使用 Excel Services 实现计算引擎组件的自定义解决方案可以极大地提高效率。这使您可以让用户从任何位置访问自定义工作簿函数,无需开发人员实现逻辑,并使您可以根据需要扩展解决方案。试一试吧。我们相信您会对它提供的更高灵活性和效率感到满意。有关更多信息,请参见“资源”侧栏。
- 服务技术概述
- 确定资源要求以支持 Excel Services
- 在 Excel 2003 中创建 XML 映射
NEW: Explore the sample code online! - or - 代码下载位置: ExcelServices2007_08.exe (226KB) |