Microsoft Analysis Services 2005:使用 ADOMD.NET 和 MDX 显示网格






4.76/5 (19投票s)
本文解释了如何使用 ADOMD.NET 和 MDX 从 Microsoft Analysis Services 2005 (OLAP) 多维数据集获取数据,并以网格形式显示。
引言
本文面向所有以某种方式使用过 ASP.NET Web 应用程序、Microsoft SQL Server Analysis Services 和 MDX,并希望为其 Web 应用程序添加分析功能的人员。
数据仓库的概念已不再是新鲜事物。随着当今世界业务规模、大小和复杂性的增长,对业务分析的需求几乎变得不可避免。这导致了一系列被称为“商业智能”(BI) 的工具和技术的发展。Microsoft SQL Server Analysis Services 是 Microsoft 的一款此类产品,有助于构建业务分析应用程序。查询语言 MDX 是一种从 Microsoft SQL Server Analysis Services 多维数据集获取多维数据的强大方法。
然而,这里仍然存在的挑战是如何向业务用户呈现这些分析数据,以便帮助他们做出决策。当今市场上有大量可用的报告工具和产品可以帮助您实现这一目标。Microsoft SQL Server Reporting Services 就是此类工具的一个示例。
本文描述了另一种向用户呈现分析数据的方法。它解释了如何使用 Microsoft ADOMD.NET 客户端组件执行 MDX 查询,以从多维数据集中获取多维数据,并以网格形式呈现给用户。
尽管此处显示的示例仅限于分析数据的呈现,但根据业务需求,该功能可以扩展到做更多的事情,例如向下钻取、向上钻取、排序、筛选等。您甚至可以使用数据创建报告和图表。
必备组件
要使用本文中的示例,需要具备 ASP.NET、Microsoft SQL Server Analysis Services 和 MDX 的基本知识。
运行本文中的源代码需要以下软件
- .NET Framework 2.0
- Microsoft Visual Studio .NET 2005
- Microsoft IIS 6 或更高版本
- Microsoft SQL Server Analysis Services 2005
我已使用 Microsoft 的 AdventureWorks 示例数据库来执行示例 MDX 查询。您可以使用自己的数据库来运行示例。如果您希望使用 AdventureWorks 示例数据库,此链接将指导您如何安装它。
背景
由于我们将使用 ADOMD.NET 客户端组件从 Microsoft SQL Server Analysis Services 多维数据集中获取数据,因此了解 ADOMD.NET 客户端对象模型非常重要。
我们将在示例中使用的主要三个对象是 AdomdConnection
、AdomdCommand
和 Cellset
。AdomdConnection
和 AdomdCommand
对象与其在 ADODB.NET 中的对应对象相似。我们将使用 AdomdCommand
的 ExecuteCellSet
方法来检索 CellSet
。
以下是 CellSet
的部分对象模型,仅显示了我们示例中感兴趣的属性。
CellSet
包含 MDX 查询执行的结果。由于 MDX 允许在不同的 Axis
上获取维度成员,因此 CellSet
包含 Axis
的集合。我们的示例将用户限制为查询两个 Axis
- 轴 0 – 列轴
- 轴 1 – 行轴
Axis
包含 Position
的集合。Position
表示 Axis
上的一个元组,并依次包含一个或多个 Member
。Cells
集合包含所有 Axis
上 Position
组合的单元格。
以下是如何从 CellSet
访问成员详细信息
CellSet.Axes[n].Positions[n1].Members[n2].PropertyName;
这里
n
是轴的索引。列轴为 0,行轴为 1。n1
是位置的索引。对于列轴,这将是列的索引;对于行轴,这将是行的索引。n2
是成员的索引。一个位置(元组)可能包含多个成员。
以下是如何从 CellSet
访问单元格数据
CellSet[n, n1, n2,…nn].PropertyName
这里 n、n1、n2…nn 是轴坐标,取决于 CellSet
中轴的数量。
您可以从 MSDN 找到有关 ADOMD.NET 客户端对象模型的更多信息。
为了以网格形式显示输出,我们将使用 ASP.NET Table
服务器对象。
设置并运行示例代码
您可以简单地将此处提供的示例 Web 应用程序代码解压缩到本地驱动器上的任何文件夹,然后在 IIS 中创建一个虚拟目录,指向包含该代码的文件夹。您现在可以从 Visual Studio .NET 2005 IDE 打开该网站。
由于我们将使用 Windows 身份验证连接到 Microsoft Analysis Services 2005,因此您需要修改 web.config 文件以模拟具有 Analysis Services 访问权限的用户。
<identity impersonate ="true" userName="user" password="password"/>
完成后,您可以浏览新创建的网站,它应该看起来像这样
正如我之前提到的,我将使用 AdventureWorks 数据库来执行 MDX 查询。您可以在运行页面时或在代码隐藏的 Page_Load
方法中根据需要修改连接字符串和默认 MDX 查询。
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//setting default connection string value
txtConnStr.Text = "Your Connection String";
txtMDX.Text = " Your default MDX query";
}
//clearing any error message
lblErr.Text = "";
}
单击“Go”按钮,您应该会看到如下所示的输出网格
理解代码
让我们深入一点,看看我们 Web 应用程序的源代码。首先,我们将查看 Web 窗体的标记。
<form id="form1" runat="server">
<table width="800" border="0">
<tr>
<td>Connection String:</td>
<td>
<asp:TextBox ID="txtConnStr" runat="server" Width="668px">
</asp:TextBox>
</td>
</tr>
<tr>
<td>MDX</td>
<td><asp:TextBox ID="txtMDX" runat="server" Height="100px"
Rows="10" Width="650px" TextMode="MultiLine"> </asp:TextBox></td>
</tr>
<tr>
<td></td>
<td>
<asp:Button ID="btnGo" runat="server" Text=" Go " OnClick="btnGo_Click" />
</td>
</tr>
<tr>
<td colspan="2">
<asp:Label ID="lblErr" runat="server" ForeColor="Red">
</asp:Label>
</td>
</tr>
</table>
<asp:Panel ID="gridPanel" runat="server" CssClass="gridPanel">
</asp:Panel>
</form>
Web 窗体有两个文本框,txtConnStr
和 txtMDX
,用于接受连接字符串和 MDX 查询。它有一个按钮 btnGo
,单击该按钮时,我们将执行 MDX 并创建网格。事件处理程序 btnGo_Click
绑定到按钮的 OnClick
事件。标签 lblErr
用于显示任何错误。最后,gridPanel
是我们将创建输出网格的面板。
现在,让我们检查 Web 窗体的代码隐藏。由于我们将使用 ADOMD.NET 客户端组件,我们已在 Web 应用程序中添加了对其的引用。这可以通过菜单 – 网站 > 添加引用来完成。
已添加 Microsoft.AnalysisServices.AdomdClient
的 using
指令,以便我们可以访问对象而无需使用完全限定名称。
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
//Following is included to use ADOMD library
using Microsoft.AnalysisServices.AdomdClient;
现在,让我们看看 btnGo_Click
事件处理程序的代码,当用户单击按钮时会调用它。
protected void btnGo_Click(object sender, EventArgs e)
{
try
{
CellSet cst = GetCellset();
BuildGrid(cst);
}
catch (System.Exception ex)
{
lblErr.Text = ex.Message;
}
}
为了便于理解,我创建了两个方法。GetCellSet
执行 MDX 查询并返回一个 CellSet
对象,而 BuildGrid
接受一个 CellSet
并创建网格。事件处理程序 btnGo_Click
在 try…catch
块中调用这两个方法。如果发生任何错误,它会在标签 lblErr
中显示。
以下是我们示例中使用的 MDX 查询。请注意,我们在列轴上有一个州-省份,在行轴上有一个 2003 年所有月份与两个度量值(Internet 销售额和 Internet 订单数量)的交叉连接。
select
[Customer].[Customer Geography].[State-Province].Members on columns,
Descendants([Date].[Calendar].[Calendar Year].&[2003],[Date].[Calendar].[Month])*
{[Measures].[Internet Sales Amount],[Measures].[Internet Order Quantity]} on rows
from [adventure works]
GetCellSet
方法执行 MDX 查询并返回一个断开连接的 CellSet
对象。它从文本框中读取连接字符串和 MDX 查询,并使用 AdomdConnection
对象建立与 Microsoft Analysis Services 的连接。然后,它使用 AdomdCommand
对象的 ExecuteCellSet
方法执行 MDX。在返回 CellSet
之前,连接已关闭。
private CellSet GetCellset()
{
//Lets store the connection string and MDX query to local variables
string strConn = txtConnStr.Text;
string strMDX = txtMDX.Text;
//create and open adomd connection with connection string
AdomdConnection conn = new AdomdConnection(strConn);
conn.Open();
//create adomd command using connection and MDX query
AdomdCommand cmd = new AdomdCommand(strMDX, conn);
//The ExecuteCellSet method of adomd command will
//execute the MDX query and return CellSet object
CellSet cst = cmd.ExecuteCellSet();
//close connection
conn.Close();
//return cellset
return cst;
}
BuildGrid
方法接受一个 CellSet
(参数名 cst
),并在我们添加到 Web 窗体中的 gridPanel
面板中创建输出网格。
它检查 CellSet
中轴的数量并将其限制为两个。此外,它还检查任何轴上是否没有返回位置(元组),并抛出错误。
private void BuildGrid(CellSet cst)
{
//check if any axes were returned else throw error.
int axes_count = cst.Axes.Count;
if (axes_count == 0)
throw new Exception("No data returned for the selection");
//if axes count is not 2
if (axes_count != 2)
throw new Exception("The sample code support only queries with two axes");
//if no position on either row or column throw error
if (!(cst.Axes[0].Positions.Count > 0) && !(cst.Axes[1].Positions.Count > 0))
throw new Exception("No data returned for the selection");
它计算每个轴上的维度(或者我应该说是层次结构)的数量。在我们正在运行的 MDX 的情况下,列为 1(州-省份),行为 2(月份和度量值)。
//Number of dimensions on the column
col_dim_count = cst.Axes[0].Positions[0].Members.Count;
//Number of dimensions on the row
if (cst.Axes[1].Positions[0].Members.Count > 0)
row_dim_count = cst.Axes[1].Positions[0].Members.Count;
输出网格所需的总行数将是列上的维度数加上行上的位置数。这是因为我们希望显示列轴中每个维度的列标题。对于列,这将是相反的。
//Total rows and columns
//number of rows + rows for column headers
row_count = cst.Axes[1].Positions.Count + col_dim_count;
//number of columns + columns for row headers
col_count = cst.Axes[0].Positions.Count + row_dim_count;
既然我们知道了网格的行数和列数,让我们创建它。首先,我们清除面板 gridPanel
下的任何内容,然后创建一个新的 Table
控件并将其添加到 gridPanel
中。
//lets clear any controls under the grid panel
gridPanel.Controls.Clear();
//Add new server side table control to gridPanel
Table tblGrid = new Table();
tblGrid.CellSpacing = 0;
tblGrid.Width = col_count * 100;
gridPanel.Controls.Add(tblGrid);
接下来,我们创建嵌套循环以添加行和列(更确切地说,是每行的单元格)。为了显示标题和数据,我们将使用 Label
控件。
//We will use label control to add text to the table cell
Label lbl;
for (cur_row = 0; cur_row < row_count; cur_row++)
{
//add new row to table
TableRow tr = new TableRow();
tblGrid.Rows.Add(tr);
for (cur_col = 0; cur_col < col_count; cur_col++)
{
//create new cell and instance of label
TableCell td = new TableCell();
lbl = new Label();
根据当前行 (cur_row
) 和当前列 (cur_col
) 坐标,我们决定要创建网格的哪一部分(或单元格)。
如果当前行 (cur_row
) 小于 (<) 列维度计数 (col_dim_count
),则表示我们正在创建包含列标题的行。
在写入列标题行时,如果当前列 (cur_col
) 小于 (<) 行维度计数 (row_dim_count
),则表示我们正在创建网格左上角的空单元格。在这种情况下,我们创建一个带有空格的标签控件。否则,如果当前列 (cur_col
) **不**小于 (<) 行维度计数 (row_dim_count
),则表示我们正在创建一个列标题单元格。在这种情况下,我们创建一个带有列成员标题的 Label
控件。
//check if we are writing to a ROW having column header
if (cur_row < col_dim_count)
{
//check if we are writing to a cell having row header
if (cur_col < row_dim_count)
{
//this should be empty cell -- it's on top left of the grid.
lbl.Text = " ";
td.CssClass = "titleAllLockedCell";
//this locks the cell so it doesn't scroll upwards nor leftwards
}
else
{
//this is a column header cell -- use member caption for header
lbl.Text =
cst.Axes[0].Positions[cur_col - row_dim_count].Members[cur_row].Caption;
td.CssClass = "titleTopLockedCell";
// this lockeders the cell so it doesn't scroll upwards
}
}
同样,当当前行 (cur_row
) 大于 (>) 列维度计数 (col_dim_count
) 时,则表示我们正在创建包含数据的行。
在写入数据行时,如果当前列 (cur_col
) 小于 (<) 行维度计数 (row_dim_count
),则表示我们正在创建网格的行标题单元格。在这种情况下,我们创建一个带有行成员标题的 Label
控件。否则,如果当前列 (cur_col
) **不**小于 (<) 行维度计数 (row_dim_count
),则表示我们正在创建一个值单元格。在这种情况下,我们创建一个带有数据的 Label
控件。
我们关闭数据行单元格的换行,这样它就不会换行并看起来很奇怪。
else
{
//We are here.. so we are writing a row having data (not column headers)
//check if we are writing to a cell having row header
if (cur_col < row_dim_count)
{
//this is a row header cell -- use member caption for header
lbl.Text =
cst.Axes[1].Positions[cur_row - col_dim_count].Members[cur_col].Caption;
td.CssClass = "titleLeftLockedCell";
// this lockeders the cell so it doesn't scroll leftwards
}
else
{
//this is data cell.. so we write the Formatted value of the cell.
lbl.Text = cst[cur_col - row_dim_count,
cur_row - col_dim_count].FormattedValue + " ";
td.CssClass = "valueCell";
//this right aligns the values in the column
}
//turn the wrapping off for row header and data cells.
td.Wrap = false;
}
最后,我们将 Label
控件添加到表格单元格中,并将单元格添加到行中。
//add cell to the row.
td.Controls.Add(lbl);
tr.Cells.Add(td);
}
}
}
冻结窗格的工作原理
您一定已经注意到,我们创建的网格具有冻结的行和列标题,这与 Excel 的冻结窗格功能类似。
这得益于应用于不同类型单元格的样式。我们通过四个 CSS 属性实现了这一点:top
、left
、position
和 z-index
。您可以查看下面的样式。为了便于比较,我已删除了其他 CSS 属性。
列标题单元格使用 titleTopLockedCell
样式。请注意,此处未指定“left
”。
.titleTopLockedCell
{
top: expression(parentNode.parentNode.parentNode.parentNode.scrollTop);
position:relative;
z-index: 10;
}
行标题单元格使用 titleLeftLockedCell
样式。请注意,此处未指定“top
”。
.titleLeftLockedCell
{
left: expression(parentNode.parentNode.parentNode.parentNode.scrollLeft);
position:relative;
z-index: 10;
}
空单元格(网格左上角)使用 titleAllLockedCell
样式。请注意,此处同时指定了“left
”和“top
”。
.titleAllLockedCell
{
top: expression(parentNode.parentNode.parentNode.parentNode.scrollTop);
left: expression(parentNode.parentNode.parentNode.parentNode.scrollLeft);
position:relative;
z-index: 20;
}
结论
有多种选项可用于呈现业务分析数据。ADOMD.NET 客户端组件有助于轻松从 Microsoft Analysis Services 检索数据,并且可以以任何形式呈现,例如报告、UI、图表等。本文只是解释如何利用 ADOMD.NET 和 MDX 的强大功能创建自己的 UI 的一步,相信我,可能性是无限的。
如果您喜欢或不喜欢这篇文章,或者您对文章有任何反馈,请随时给我发电子邮件。我很乐意听取您的宝贵意见。
历史
- 2008年8月1日
添加了文章。