最近,为了完成公司的业务要求,除了使用ReportService开发普通的rdl报表,和使用VS2010自带的报表开发工具开发rdlc报表外,还需要使用OWC+AnalysisService来满足一些客户的特殊需求,特别是需要使用OWC的PivotTable和ChartSpace开发,于是我在最近查阅了很多的网上的资料,整理出了一套基于:OWC+AnalysisService+Asp.net4.0构建OLAP应用解决方案,希望Share在这里与有这方面兴趣的程序猿们共享。(其中还有不足之处,希望多多指教)。
要想正确的使用该解决方案,需要处理好以下几方面。
一、AnalysisServices的配置
首先先说一下我是用的开发工具:Asp.net的开发使用的是VS2010,数据库使用的是SqlServer2008 R2,AnalysisServices使用的是SqlServer2008 R2自带的BI开发工具:SQL Server Bussiness Intelligence Development Studio。
1.配置步骤如下:打开SQL Server Bussiness Intelligence Development Studio:
2.选择文件->新建->新建项目:选择Analysis Services项目:
这时就会在解决方案资源管理器里边出现以下文件:
3.然后依次:新建数据源,新建视图,新建多维数据集……(此处不再详细介绍)关于Analysis Services的相关使用知识,请参阅MSDN:Analysis Services 向导。
好了,现在就把Analysis Services 建好了 :
接下来,右键点击AnalysisServicesTestProject,点击部署即可,部署成功后,就可以做接下来的步骤了。
二、VS2010中开发Asp.net代码
打开VS2010,新建我们的OLAP的Web部分了,在VS2010下新建如下的解决方案:AnalysisServices,解决方案下边依次有Application,Common,Service等几个项目文件,当然,这里不一定必要这样子,你可以根据自己的实际需要创建项目,我这里只是一个简单的Demo。
其中Model层主要创建的是一些实体类,ServiceImp层主要实现的数据的访问,这里边又分为数据访问层Persistence和业务逻辑层Provider:
其中Config文件夹里边是一个连接字符串的实体类。Analysis Services的连接字符串如下:
public static readonly string AnalysisServicesConn = "Datasource=localhost;Initial Catalog=AnalysisServicesTestProject";//注意是多维数据集
好了,现在就可以进行Analysis Services的数据访问了,在这里我要讲述一下AnalysisServices的一个专用的数据访问动态链接库ADOMD.NET,参考(
ADOMD.NET 客户端编程)你可以添加引用到你的VS项目。
引用名称:Microsoft.AnalysisServices.AdomdClient。
应用文件位置:C:\Program Files (x86)\Microsoft.NET\ADOMD.NET\100\Microsoft.AnalysisServices.AdomdClient.dll。
现在就可以编写你的数据访问程序了,为了做一个示例,下面我简单的写了一个,写的很粗糙,仅供参考:
/// <summary>
/// 获取AdminUser信息
/// </summary>
/// <returns></returns>
public static CellSet GetAdminUserCellSetMsg()
{
string sql = @"select [BI Admin User].[User Name].children on columns,{[BI Admin User].[User Real Name].children} on rows from [BI Report Center]";
AdomdConnection conn = new AdomdConnection(AnalysisServicesConfig.AnalysisServicesHttpConn2);
try
{
conn.Open();
AdomdCommand comm = new AdomdCommand(sql, conn);
CellSet cs = comm.ExecuteCellSet();
return cs;
}
catch (Exception e)
{
throw e;
}
finally
{
conn.Close();
}
}
其中的黑体字部分就是传说中的MDX查询语句,它跟T-SQL语句有类似之处,但是有很大的不同,关于MDX查询语句请参考: MDX 语句参考
三、前端PivotTable的开发介绍:
PivotTable是一个客户端数据钻取控件。你需要在你的前端页面里边写上这样的一段代码:
<img src="http://hi.youkuaiyun.com/attachment/201108/15/0_1313392793D5jz.gif" alt="" />

客户端代码实现:
<object id="PivotTable1" classid="CLSID:0002E55A-0000-0000-C000-000000000046"></object>
<script language="javascript" type="text/javascript">
window.onload = LoadPage;
var SEP = "/,/";
var SEP2 = "/,,/";
//初始化报告参数
//TODO:这里为了做成通用的报告,变量名称需要变更
//需要添加到行区域的字段名称集合,以Common.SEP分割
var strRowFieldNames = "<%=Server.UrlDecode(Request.QueryString["RowFieldNames"])%>";
//需要添加到列区域的字段名称集合,以Common.SEP分割
var strColumnFieldNames = "<%=Server.UrlDecode(Request.QueryString["ColumnFieldNames"])%>";
//过滤字段名称集合,以Common.SEP分割
var strFilterFieldNames = "<%=Server.UrlDecode(Request.QueryString["FilterFieldNames"])%>";
//需要进行分段汇总的字段名称
//格式为:字段名/,,/分组类型/,,/起始值/,,/间隔值/,/第二个分组
//其中/,,/为Common.SEP2, /,/为Common.SEP
var strGroupFieldNamesAndValues = "<%=Server.UrlDecode(Request.QueryString["GroupFieldNamesAndValues"])%>";
//图表类型
var intChartType = parseInt("<%=Server.UrlDecode(Request.QueryString["ChartType"])%>");
//声明变量,用来存储页面上的OWC Object
//透视表对象及透视表的常量对象
var pvt, pvtconstants;
//图表对象及图标的常量对象
var cht, chtconstants;
function LoadPage()
{
//初始化透视表
InitPivotTable();
}
/**
* 初始化透视表
*/
function InitPivotTable()
{
//声明RecordSet对象
var adors = new ActiveXObject("ADODB.Recordset");
//声明XMLDocument对象
//TODO:msxml2.domdocument有可能是msxml3.domdocument或msxml4.domdocument,有待证明
var xmldoc = new ActiveXObject("msxml2.domdocument");
//服务器端返回的XML字符串,用来构造RecordSet
var str="<%=xml%>";
//XMLDocument对象加载XML字符串
xmldoc.loadXML(str);
//RecordSet设定数据源为上面的XMLDocument对象,并打开
adors.Open(xmldoc);
pvt = document.PivotTable1;
pvtconstants = pvt.Constants;
//设定透视表的数据源为上面的RecordSet对象
pvt.DataSource = adors;
//设定透视表的一些属性,具体请参考OWC For VBA
pvt.AutoFit = true;
pvt.DisplayToolbar = true;
pvt.DisplayExpandIndicator = true;
pvt.DisplayAlerts = true;
pvt.DisplayBranding = true;
pvt.DisplayDesignTimeUI = true;
pvt.DisplayFieldList = true;
pvt.DisplayOfficeLogo = false;
pvt.DisplayPropertyToolbox = false;
pvt.DisplayScreenTips = true;
pvt.ActiveView.TitleBar.Visible = true;
pvt.ActiveView.TitleBar.Caption = "<%=title%>";
pvt.ActiveView.TitleBar.Font.Name = "arial";
pvt.ActiveView.TitleBar.Font.Size = 10;
pvt.ActiveView.TotalBackColor = "CornSilk";
pvt.ActiveView.TotalFont.Name = "arial";
pvt.ActiveView.TotalFont.Size = 8;
pvt.ActiveView.FieldLabelFont.Name = "arial";
pvt.ActiveView.FieldLabelFont.Size = 8;
pvt.ActiveView.HeaderFont.Name = "arial";
pvt.ActiveView.HeaderFont.Size = 8;
pvt.ActiveView.PropertyCaptionFont.Name = "arial";
pvt.ActiveView.PropertyCaptionFont.Size = 8;
pvt.ActiveView.PropertyValueFont.Name = "arial";
pvt.ActiveView.PropertyValueFont.Size = 8;
pvt.ActiveView.ExpandMembers = pvtconstants.plExpandNever;
pvt.ActiveView.ExpandDetails = pvtconstants.plExpandNever;
//设定每一个字段的字体,字号,颜色等
for(var i = 0; i <= pvt.ActiveView.FieldSets.Count - 1; i++)
{
pvt.ActiveView.FieldSets(i).Fields(0).DetailFont.Name = "arial";
pvt.ActiveView.FieldSets(i).Fields(0).DetailFont.Size = 8;
pvt.ActiveView.FieldSets(i).Fields(0).GroupedFont.Name = "arial";
pvt.ActiveView.FieldSets(i).Fields(0).GroupedFont.Name = "arial";
pvt.ActiveView.FieldSets(i).Fields(0).GroupedFont.Size = 8;
pvt.ActiveView.FieldSets(i).Fields(0).SubtotalLabelFont.Name = "arial";
pvt.ActiveView.FieldSets(i).Fields(0).SubtotalLabelFont.Size = 8;
pvt.ActiveView.FieldSets(i).Fields(0).SubtotalFont.Name = "arial";
pvt.ActiveView.FieldSets(i).Fields(0).SubtotalFont.Size = 8;
pvt.ActiveView.FieldSets(i).Fields(0).SubtotalBackColor = "LightSteelBlue";
}
//将所有字段都添加到”过滤”区域
for(var i = 0; i < pvt.ActiveView.FieldSets.Count; i++)
{
pvt.ActiveView.FilterAxis.InsertFieldSet(pvt.ActiveView.FieldSets(i));
}
/*
* 添加一个”合计”字段
* AddTotal方法原型: AddTotal(Name, Field, Function)
* Name合计字段的名字
* Field需要进行合计的列对象
* Function合计方法
*/
var ctotal = pvt.ActiveView.AddTotal("汇总", pvt.ActiveView.FieldSets(0).Fields(0), pvtconstants.plFunctionCount);
/*
* 将”合计”字段添加到”数据区域”
* InsertTotal方法原型: InsertTotal(Total, Before)
* Total是一个PivotTotal类型的对象,也就是上面声明的对象
* Bebore是指定要在其之前插入总计的总计索引。如果不指定该参数,则总计插入到集合的末尾。
*/
pvt.ActiveView.DataAxis.InsertTotal(ctotal, 0);
//设定列区域
SetColumnFields();
//设定明细区域
SetDataFields();
//设定分组
SetGroupFields();
/*
* 隐藏指定对象的明细单元格。
* 如果指定对象为 PivotData 对象,将隐藏所有明细单元格。
* 如果指定对象为 PivotRowMember 对象,将隐藏该行的所有明细单元格。
* 如果指定对象为 PivotColumnMember 对象,将隐藏该列的所有明细单元格。
*/
pvt.ActiveData.HideDetails();
}
//设定行区域
function SetRowFields()
{
if(strRowFieldNames == "")
{
return;
}
var OrgCollection = strRowFieldNames.split(SEP);
//循环要显示在行上的字段
for(var i = 0; i < OrgCollection.length; i++)
{
pvt.ActiveView.FieldSets(OrgCollection[i]).Fields(0).IsIncluded = true;
pvt.ActiveView.RowAxis.InsertFieldSet(pvt.ActiveView.FieldSets(OrgCollection[i]));
}
}
//设定列区域
function SetColumnFields()
{
if(strColumnFieldNames == "")
{
return;
}
var ColumnFieldsCollection = strColumnFieldNames.split(SEP);
for(var i = 0; i < ColumnFieldsCollection.length; i++)
{
pvt.ActiveView.FieldSets(ColumnFieldsCollection[i]).Fields(0).IsIncluded = true;
pvt.ActiveView.ColumnAxis.InsertFieldSet(pvt.ActiveView.FieldSets(ColumnFieldsCollection[i]));
}
}
//设定数据明细区域
function SetDataFields()
{
var ColumnFieldsCollection = strColumnFieldNames + SEP + strRowFieldNames + SEP + strFilterFieldNames + SEP;
for(var i = 0; i < pvt.ActiveView.FieldSets.Count; i++)
{
if(ColumnFieldsCollection.indexOf(pvt.ActiveView.FieldSets(i).Name + SEP) == -1)
{
pvt.ActiveView.DataAxis.InsertFieldSet(pvt.ActiveView.FieldSets(i));
}
}
}
//设定字段分组
function SetGroupFields()
{
//得到分组字段定义的数组,数组的每个元素是字段分组的一个完整定义
var GroupCollection = strGroupFieldNamesAndValues.split(SEP);
for(var i = 0; i < GroupCollection.length; i++)
{
if(GroupCollection[i] == "")
{
break;
}
//得到其中的一个字段分组定义
var OneGroupDefination = GroupCollection[i].split(SEP2);
if(OneGroupDefination.length != 4)
{
break;
}
//字段名称
var FieldName = OneGroupDefination[0];
//分组类型
var GroupMethod = parseInt(OneGroupDefination[1]);
//基数
var BaseAmount = parseFloat(OneGroupDefination[2]);
//间隔值
var IntervalAmount = parseFloat(OneGroupDefination[3]);
if(IntervalAmount == 0)
{
pvt.ActiveView.FieldSets(FieldName).Fields(0).GroupOn = pvtconstants.plGroupOnEachValue;
}
else
{
pvt.ActiveView.FieldSets(FieldName).Fields(0).GroupOn = GroupMethod;
pvt.ActiveView.FieldSets(FieldName).Fields(0).GroupInterval = IntervalAmount;
pvt.ActiveView.FieldSets(FieldName).Fields(0).GroupStart = BaseAmount;
}
}
}
</script>
Asp.net后台代码:首先需要引用OWC11
引用:Microsoft Office Web Components 11.0
DLL文件(引用文件)位置:C:\Windows\assembly\GAC\Microsoft.Office.Interop.Owc11\11.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.Owc11.dll
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Beisen.BIReportCenter.Model;
using System.Data;
using Beisen.AnalysisServices.Utility;
using System.IO;
using System.Xml;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Office.Interop;
using AnalysisServices.Model.CustomEntity;
namespace AnalysisServices.UI.Admin
{
public partial class PivotTableExample : System.Web.UI.Page
{
private string _xml;
public string xml
{
get{return _xml;}
set{_xml = value;}
}
private string _title;
public string title
{
get { return _title; }
set { _title = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
SetXmlValueByAnalysisServices();
}
private void SetXmlValueByAnalysisServices()
{
CellSet cs = Beisen.AnalysisServices.ServiceImp.Provider.BIAdminUserProvider.Instance.GetAdminUserCellSetMsg();
DataTable dt = CellSetToDataTable.ToDataTable(cs);
dt.TableName = "AdminUser";
this._xml = ConvertToXML.ConvertDataTableToXML(dt);
this._title = "数据报表";
}
}
}
其中还需要注意的是,我们从AnalysisServices 里边查询出来的数据,只是一个CellSet,不是我们传统的数据表DataTable,我们需呀进行CellSet到数据表之间的转换:
关于CellSet转DataTable的改进方案
下边该说说 ConvertDataTableToXML方法了:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Text.RegularExpressions;
namespace AnalysisServices.Utility
{
public class ConvertToXML
{
/// <summary>
/// 将DataTable转换成RecordXML函数
/// </summary>
/// <param name=“dt”>要转换的DataTable</param>
/// <returns>转换好的XML字符串</returns>
public static string ConvertDataTableToXML(DataTable dt)
{
/*DataTable支持的数据类型
system.int64
system.byte[]
system.boolean
system.string
system.datetime
system.decimal
system.double
system.int32
system.single
system.int16
system.object
system.byte
system.guid
*/
string TempValue;
for (int i = 0; i < dt.Columns.Count; i++)
{
TempValue = dt.Columns[i].ColumnName;
Regex rex = new Regex("\\W");
TempValue = rex.Replace(TempValue, "");
rex = new Regex("^\\d");
if (rex.Replace(TempValue, "").Length != TempValue.Length)
{
TempValue = "N" + TempValue;
}
dt.Columns[i].ColumnName = TempValue;
}
StringBuilder sb = new StringBuilder();
sb.Append("<xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882' ");
sb.Append("xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882' ");
sb.Append("xmlns:rs='urn:schemas-microsoft-com:rowset' ");
sb.Append("xmlns:z='#RowsetSchema'> ");
sb.Append("<s:Schema id='RowsetSchema'> ");
sb.Append("<s:ElementType name='row' content='eltOnly' rs:CommandTimeout='30'> ");
for (int i = 0; i < dt.Columns.Count; i++)
{
string DataType;
switch (dt.Columns[i].DataType.ToString().ToLower())
{
case "system.boolean":
DataType = "string";
break;
case "system.byte":
case "system.int16":
case "system.int32":
case "system.int64":
DataType = "int";
break;
case "system.decimal":
case "system.double":
case "system.single":
DataType = "float";
break;
case "system.string":
DataType = "string";
break;
case "system.datetime":
DataType = "datetime";
break;
default:
throw new ArgumentException();
}
sb.Append("<s:AttributeType name='" + dt.Columns[i].ColumnName + "' rs:number='" + (i + 1).ToString() + "' rs:writeunknown='true'> ");
sb.Append("<s:datatype dt:type='" + DataType + "' dt:maxLength='100' rs:maybenull='true' /> ");
sb.Append("</s:AttributeType> ");
}
sb.Append("<s:extends type='rs:rowbase' /> ");
sb.Append("</s:ElementType> ");
sb.Append("</s:Schema> ");
sb.Append("<rs:data> ");
for (int i = 0; i < dt.Rows.Count; i++)
{
sb.Append("<z:row ");
for (int j = 0; j < dt.Columns.Count; j++)
{
TempValue = dt.Rows[i][j].ToString().Replace("<", "").Replace(">", "").Replace("'", "").Replace("\"", "");
if (dt.Columns[j].DataType.ToString().ToLower() == "system.datetime" && TempValue == string.Empty)
{
//TempValue = INVALIDDATE.ToString(DATEMASK);
TempValue = "";
}
sb.Append(dt.Columns[j].ColumnName + "='" + TempValue + "' ");
}
sb.Append("/> ");
}
sb.Append("</rs:data> ");
sb.Append("</xml> ");
return sb.ToString();
}
}
}

讲到这里,想必你已经对OWC+AnalysisService+Asp.net4.0构建OLAP应用有一定的了解了吧。
由于时间关系,我介绍的比较粗糙,很多东西还没有说的很清楚,请谅解。如果你有指教或者疑问,请留言交流!