65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (19投票s)

2008年8月1日

CPOL

9分钟阅读

viewsIcon

133080

downloadIcon

3400

本文解释了如何使用 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 客户端对象模型非常重要。

我们将在示例中使用的主要三个对象是 AdomdConnectionAdomdCommandCellsetAdomdConnectionAdomdCommand 对象与其在 ADODB.NET 中的对应对象相似。我们将使用 AdomdCommandExecuteCellSet 方法来检索 CellSet

以下是 CellSet 的部分对象模型,仅显示了我们示例中感兴趣的属性。

01_partial_cellset_object_model.JPG

CellSet 包含 MDX 查询执行的结果。由于 MDX 允许在不同的 Axis 上获取维度成员,因此 CellSet 包含 Axis 的集合。我们的示例将用户限制为查询两个 Axis

  • 轴 0 – 列轴
  • 轴 1 – 行轴

Axis 包含 Position 的集合。Position 表示 Axis 上的一个元组,并依次包含一个或多个 MemberCells 集合包含所有 AxisPosition 组合的单元格。

以下是如何从 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"/>

完成后,您可以浏览新创建的网站,它应该看起来像这样

02_first_run_w600.JPG

正如我之前提到的,我将使用 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”按钮,您应该会看到如下所示的输出网格

03_output_grid_w600.jpg

理解代码

让我们深入一点,看看我们 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 窗体有两个文本框,txtConnStrtxtMDX,用于接受连接字符串和 MDX 查询。它有一个按钮 btnGo,单击该按钮时,我们将执行 MDX 并创建网格。事件处理程序 btnGo_Click 绑定到按钮的 OnClick 事件。标签 lblErr 用于显示任何错误。最后,gridPanel 是我们将创建输出网格的面板。

现在,让我们检查 Web 窗体的代码隐藏。由于我们将使用 ADOMD.NET 客户端组件,我们已在 Web 应用程序中添加了对其的引用。这可以通过菜单 – 网站 > 添加引用来完成。

04_add_reference.JPG

已添加 Microsoft.AnalysisServices.AdomdClientusing 指令,以便我们可以访问对象而无需使用完全限定名称。

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_Clicktry…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) 坐标,我们决定要创建网格的哪一部分(或单元格)。

05_four_cell_types.JPG

如果当前行 (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 的冻结窗格功能类似。

06_frozen_grid_w600.jpg

这得益于应用于不同类型单元格的样式。我们通过四个 CSS 属性实现了这一点:topleftpositionz-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日
  • 添加了文章。

© . All rights reserved.