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

MDX。缓慢变化的值及其调整。ADOMD.Net。C#中的MDX用法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (4投票s)

2009年11月14日

CPOL

12分钟阅读

viewsIcon

40178

downloadIcon

1410

MDX查询,用于获取日期之前的度量的最后一个有效值,然后计算到给定日期为止对该值有贡献的所有值。ADOMD.Net。C#中的MDX用法。

获取给定日期之前的最后一个有效值,然后计算到给定日期为止对该值有贡献的所有值

MDX,C# 2.0,ADOMD.Net,SQL Server Analysis Services 2005

MDX



引言

文章涵盖

  • MDX查询,子查询,集合,维度,轴,元组,成员,属性
  • WITH,MEMBER,IIF,NOT IsEmpty,NonEmpty,Tail,Item(),CurrentMember,Name,PrevMember,StrToMember,IS NULL,SELECT,ON ROWS,ON COLUMNS,FROM,WHERE
  • using Microsoft.AnalysisServices.AdomdClient; Microsoft.AnalysisServices.AdomdClient.AdomdConnection, Microsoft.AnalysisServices.AdomdClient.AdomdCommand, Microsoft.AnalysisServices.AdomdClient.AdomdCommand.ExecuteCellSet(), Microsoft.AnalysisServices.AdomdClient.AdomdParameter, Microsoft.AnalysisServices.AdomdClient.CellSet, Microsoft.AnalysisServices.AdomdClient.Tuple

您可以在相关的部分中查看样本内容以及如何在您的PC上部署样本。

缓慢变化的值及其调整。我所说的“缓慢变化”是什么意思?这是指所有难以进行恒定度量的值,因此它们以某种方式被计算出来。例如,这些值包含大量的(有时甚至巨大的)元素或不同的信息来源,例如,这是盘点结果或企业与其交易对手之间的内部付款验证。这是“现实的快照”。在进行这种“真实”测量之后,决策者仍然需要最新的值。可以基于导致有趣值变化的值来计算它们。但是,由于时间的关系,计算出的值与实际情况不同,例如,由于盗窃或操作失误。因此,信息系统中存在这种“坚实的基础”。

本文涵盖了如何使用SSAS中的MDX检索这类值的最真实水平,并使用ADOMD.Net将其获取到C#中。它不假定任何先前关于OLAP和Microsoft Analysis Services以及ADOMD.Net对象的经验,但是您应该能够从备份中备份数据库,并从Business Intelligence Development Studio(BI DevStudio)将数据库部署到Analysis Service。需要查看查询的工作情况。

我以库存水平为例。

获取库存水平的主要问题是确定当前产品上次盘点发生的时间。然后是获取有效的日期维度成员以将其传递给查询。

MDX查询

概念

基本思想是获取一个立方体,该立方体计算给定时间、给定地点、给定产品的库存量。思路是

  • 获取给定日期和时间之前的最后一次盘点。
  • 添加和减去自该日期时间以来的所有库存变动。

有几个表会影响库存水平。我们将所有这些表用于调整。

尺寸

有三个维度

  • 时间,以任何操作发生的秒数表示
  • 地点,可以是仓库或其他产品存储的任何站点。
  • 产品,这是感兴趣的产品。

我们应该获取特定产品在特定地点、特定时间的库存水平

Measures

有很多度量。现在我们对[Measures].[Last Stocktake Qty]感兴趣,它来自年度或半年度盘点。

获取给定日期和时间之前的最后一次盘点

此任务部分的解决方案是“缓慢变化的值”的一个特殊案例,该案例在《MDX Solutions》一书中第84页有描述。有两种方法可以做到这一点,我们将同时使用它们。一种在第一个版本(递归)中,另一种在第二个版本(过滤有序集的尾部)中,稍后在ADOMD.Net客户端中使用。与书中的版本不同,我们获取最后一次已报告库存水平的时间,实际上它是Time维度成员的唯一名称。我们这样做是因为接下来我们需要累加其他值表中对库存水平有贡献的值,这些值发生在从上次更新到感兴趣的时间点之间的期间。

一个执行此操作的计算成员是

-- last update of Stock level Second
MEMBER [Measures].[Last Update of Stock Level] AS
'IIF (
	NOT IsEmpty ([Measures].[Last Stocktake Qty]),
	[Time].[Second].CurrentMember.Name,
	IIF ( 
		[Time].[Second].PrevMember IS NULL,
		NULL,
		([Measures].[Last Update of Stock Level], [Time].[Second].PrevMember)
	)
)'

此计算成员测试Stocktake Quantity是否为空,但返回第一个非空Stocktake级别的秒数。

NOT IsEmpty ([Measures].[Last Stocktake Qty]),
[Time].[Second].CurrentMember.Name,

如果Stocktake级别在当前秒不存在,我们切换到前一秒。为此,我们显式地将时间确定为[Time].[Second].PrevMember,当我们引用前一秒时,否则我们引用同一单元格。

([Measures].[Last Update of Stock Level], [Time].[Second].PrevMember)

当然,我们必须考虑边界处理 - 前一秒必须存在。

[Time].[Second].PrevMember IS NULL

要完成此块,我们确定感兴趣的库存水平。

-- Last measured stocktake level
MEMBER [Measures].[Last Measured Stock Level] AS
'(
	StrToMember("[Time].[Second].[" 
		+ [Measures].[Last Update of Stock Level]
		+ "]"
		), 
	[Measures].[Last Stocktake Qty]
)'

我们必须使用StrToMember()函数,因为[Measures].[Last Update of Stock Level]是字符串,但元组由成员组成,所以我们提供一个成员。

添加和减去自该日期时间以来的所有库存变动

此时,我们得到了最后一个更新库存水平的秒数,该秒数 precedes(在……之前)我们感兴趣的秒数。为了对构成库存水平的事实进行求和,我们可以使用sum函数。

-- SaleQty
MEMBER [Measures].[SaleQty Later] AS
'SUM(
	StrToMember("[Time].[Second].&[" 
			+ [Measures].[Last Update of Stock Level]
			+ "]")
		: [Time].[Second].CurrentMember, [Measures].[Sale Qty])'

但这操作太昂贵了。最好是减去两个数字:时期末的累积值和最后一次更新日期的累积值。要做到这一点,我们应该为每个对Stocktake级别有贡献的值创建一个计算成员。在BI DevStudio项目中,您选择“Cubes”节点,展开它,双击“Amicus Cub”,然后选择“Calculations”选项卡,在那里您可以为立方体创建计算度量。

CREATE MEMBER [Measures].[SaleQtyToDate] As 'sum(null : [Time].[Second].currentmember, [Measures].[Sale Qty])'

或者使用MDX脚本。

CREATE MEMBER [Measures].[SaleQtyToDate] As 
'Sum(null : [Time].[Second].currentmember, [Measures].[Sale Qty])'

在AS2005中,在表示范围的表达式中,null表示当前级别的第一个成员(显式范围成员确定当前级别)。

然后,“不确定”期间发生的更改可以使用以下计算成员获得

-- SaleQty
MEMBER [Measures].[Diffs SaleQty] AS
'([Time].[Second].CurrentMember, [Measures].[SaleQtyToDate])
	- (   
			StrToMember("[Time].[Second].&[" 
					+ [Measures].[Last Update of Stock Level]
					+ "]"), 
			[Measures].[SaleQtyToDate])'

在第一个元组中使用[Time].[Second].CurrentMember不是必需的。但是,对我而言,这种写作方式更透明。[Time].[Second].CurrentMember是感兴趣的秒数。

我们对每个影响库存水平的值都做类似的操作

  • Receipts (入库)
  • NegativeAdjustment (负调整)
  • PositiveAdjustment (正调整)
  • TransferIn (转入)
  • TransferOut (转出)

汇总

我们的目标是在特定时间获取特定地点特定产品的库存水平。在MDX中,切片器(slicer)就是为此目的。它明确了单元格上下文。这意味着它确定了CurrentMembers。在我们的例子中,我们获取产品STEVE,在1号地点,在‘2008-10-29 08:55:15’秒的库存水平(要获取特定成员的完整标识符,或任何标识符,您可以选择对象浏览器树中感兴趣的对象,然后右键单击,然后选择“复制”)。

-- Slice to product, location, second
WHERE (
	[Product].[Product].&[STEVE], 
	[Location].[Location].&[1], 
	[Time].[Second].&[2008-10-29 08:55:15]
)

现在我们将所需值的各个部分组合起来

-- Real stock level
MEMBER [Measures].[Real Stock Level] AS
'
[Measures].[Last Measured Stock Level]
- [Measures].[Diffs SaleQty]
 + [Measures].[Diffs Receipts]
 - [Measures].[Diffs NegativeAdjustment]
 + [Measures].[Diffs PositiveAdjustment]
 - [Measures].[Diffs TransferOut]
 + [Measures].[Diffs TransferIn]
'

以及全部代码

WITH 
-- last update of Stock level Second
MEMBER [Measures].[Last Update of Stock Level] AS
'IIF (
	NOT IsEmpty ([Measures].[Last Stocktake Qty]),
	[Time].[Second].CurrentMember.Name,
	IIF ( 
		[Time].[Second].PrevMember IS NULL,
		NULL,
		([Measures].[Last Update of Stock Level], [Time].[Second].PrevMember)
	)
)'
-- Diferences in calculated members after last update date
-- SaleQty
MEMBER [Measures].[Diffs SaleQty] AS
'([Time].[Second].CurrentMember, [Measures].[SaleQtyToDate])
	- (   
			StrToMember("[Time].[Second].&[" 
					+ [Measures].[Last Update of Stock Level]
					+ "]"), 
			[Measures].[SaleQtyToDate])'
-- Receipts
MEMBER [Measures].[Diffs Receipts] AS
'([Time].[Second].CurrentMember, [Measures].[ReceiptsToDate])
	- (   
			StrToMember("[Time].[Second].&[" 
					+ [Measures].[Last Update of Stock Level]
					+ "]"), 
			[Measures].[ReceiptsToDate])'
-- NegativeAdjustment
MEMBER [Measures].[Diffs NegativeAdjustment] AS
'([Time].[Second].CurrentMember, [Measures].[NegativeAdjustmentToDate])
	- (   
			StrToMember("[Time].[Second].&[" 
					+ [Measures].[Last Update of Stock Level]
					+ "]"), 
			[Measures].[NegativeAdjustmentToDate])'
-- PositiveAdjustment
MEMBER [Measures].[Diffs PositiveAdjustment] AS
'([Time].[Second].CurrentMember, [Measures].[PositiveAdjustmentToDate])
	- (   
			StrToMember("[Time].[Second].&[" 
					+ [Measures].[Last Update of Stock Level]
					+ "]"), 
			[Measures].[PositiveAdjustmentToDate])'
-- TransferIn
MEMBER [Measures].[Diffs TransferIn] AS
'([Time].[Second].CurrentMember, [Measures].[TransferInToDate])
	- (   
			StrToMember("[Time].[Second].&[" 
					+ [Measures].[Last Update of Stock Level]
					+ "]"), 
			[Measures].[TransferInToDate])'
-- TransferOut
MEMBER [Measures].[Diffs TransferOut] AS
'([Time].[Second].CurrentMember, [Measures].[TransferOutToDate])
	- (   
			StrToMember("[Time].[Second].&[" 
					+ [Measures].[Last Update of Stock Level]
					+ "]"), 
			[Measures].[TransferOutToDate])'
-- Last measured stocktake level
MEMBER [Measures].[Last Measured Stock Level] AS
'(
	StrToMember("[Time].[Second].[" 
		+ [Measures].[Last Update of Stock Level]
		+ "]"
		), 
	[Measures].[Last Stocktake Qty]
)'
-- Real stock level
MEMBER [Measures].[Real Stock Level] AS
'
[Measures].[Last Measured Stock Level]
- [Measures].[Diffs SaleQty]
 + [Measures].[Diffs Receipts]
 - [Measures].[Diffs NegativeAdjustment]
 + [Measures].[Diffs PositiveAdjustment]
 - [Measures].[Diffs TransferOut]
 + [Measures].[Diffs TransferIn]
'
-- Query 
SELECT [Measures].[Measures].[Real Stock Level] ON COLUMNS
FROM [Amicus Dev]
-- Slice to product, location, second
WHERE (
	[Product].[Product].&[STEVE], 
	[Location].[Location].&[1], 
	[Time].[Second].&[2008-10-29 08:55:15]
)

最终版

在继续之前,我们考虑第二种方法,即在当前时间点不存在时获取上一个值(我使用了“点”这个词,因为列、行、页面、章节、节等是轴,因此成员就是点)。

--member for second of last stocktake check
Member  AS
'Tail(
	NonEmpty( 
		[ExistingSeconds],
		([Measures].[Last Stocktake Qty])
	),
	1
).Item(0).Item(0)'

细心的读者会注意到两件事。一件叫做Set。它代表[Time].[Second]级别上的所有成员,换句话说,所有秒。最后是计算成员[Time].[Second].[Last Stocktake Check],它与[Time].[Second]维度相关,并且在主执行中返回成员而不是成员名称。前者允许我们将此成员用于可以使用[Time].[Second]的地方。后者使我们免于使用StrToMember(),从而提高了性能。注意:尽可能使用成员和命名集,因为成员或集合只编译一次,然后您可以在任何地方使用它们。

但是最细心的读者会注意到,上面提供的计算成员始终返回时间维度中的最后一个秒。这是因为切片器(Where子句中的成员)确定了单元格上下文,但并没有缩小计算区域。这对我来说是一个陷阱,因为我来自SQL,其中Where是过滤选项。我们必须使用子立方体(subcube)

( 
	SELECT
	{ NULL : 
		[Time].[Second].&[2008-10-29 08:54:25]		//10
																//Time dimension
	} on 0
	, {[Product].[Product].&[STEVE] } on 1						//Product dimension
	, {[Location].[Location].&[1] } on 2						//Location dimension
	From
	[Amicus Dev]
)

并强制使用“过滤”上下文在集合和成员中使用关键字Existing

--period from begining till interesting date
Set [ExistingSeconds] AS 
'Existing [Time].[Second].Members'

在这一点的阐述中,我们得到了一个在日期之前的最后一次更新的[Time].[Second]维度成员。但这种情况发生了,当我们解决一个问题时,我们创建了另一个问题。这正是这种情况:现在我们丢失了[Time].[Second]维度的CurrentMember。我们可以从ExistingSeconds集合中获取它

--interesting second value
Member [Time].[Second].[InterestingSecond] AS
'[ExistingSeconds].Item([ExistingSeconds].Count - 1).Item(0)'

我们取过滤后的秒数([ExistingSeconds])组成的最后一个元组(.Item([ExistingSeconds].Count - 1))的第一个成员(.Item(0))。您还记得我们用子查询/子立方体过滤了这个维度,以我们感兴趣的秒数吗?如果您以前忘记说了,我要求说一下。

所以我们也解决了这个问题。我们的运气是它比我们之前解决的问题更容易,因为在大多数情况下情况正好相反。

最终版本

With 
--period from begining till interesting date
Set [ExistingSeconds] AS 
'Existing [Time].[Second].Members'
--member for second of last stocktake check
Member [Time].[Second].[Last Stocktake Check] AS
'Tail(
	NonEmpty( 
		[ExistingSeconds],
		([Measures].[Last Stocktake Qty])
	),
	1
).Item(0).Item(0)'
--interesting second value
Member [Time].[Second].[InterestingSecond] AS
'[ExistingSeconds].Item([ExistingSeconds].Count - 1).Item(0)'
--actual stocktake quantity (last checked) 		 
Member [Measures].[Actual Stocktake Qty] AS
'(
	[Time].[Second].[Last Stocktake Check],
	[Measures].[Last Stocktake Qty]
)'
-- Diferences in calculated members after last update date
-- SaleQty
MEMBER [Measures].[Last Sale Qty] AS
'([Time].[Second].[InterestingSecond], [Measures].[SaleQtyToDate])
	- ([Time].[Second].[Last Stocktake Check], [Measures].[SaleQtyToDate])'
-- Receipts
MEMBER [Measures].[Last Receipts] AS
'([Time].[Second].[InterestingSecond], [Measures].[ReceiptsToDate])
	- ([Time].[Second].[Last Stocktake Check], [Measures].[ReceiptsToDate])'
-- NegativeAdjustment
MEMBER [Measures].[Last Negative Adjustment] AS
'([Time].[Second].[InterestingSecond], [Measures].[NegativeAdjustmentToDate])
	- ([Time].[Second].[Last Stocktake Check], [Measures].[NegativeAdjustmentToDate])'
-- PositiveAdjustment
MEMBER [Measures].[Last Positive Adjustment] AS
'([Time].[Second].[InterestingSecond], [Measures].[PositiveAdjustmentToDate])
	- ([Time].[Second].[Last Stocktake Check], [Measures].[PositiveAdjustmentToDate])'
-- TransferIn
MEMBER [Measures].[Last Transfer In] AS
'([Time].[Second].[InterestingSecond], [Measures].[TransferInToDate])
	- ([Time].[Second].[Last Stocktake Check], [Measures].[TransferInToDate])'
-- TransferOut
MEMBER [Measures].[Last Transfer Out] AS
'([Time].[Second].[InterestingSecond], [Measures].[TransferOutToDate])
	- ([Time].[Second].[Last Stocktake Check], [Measures].[TransferOutToDate])'
-- Real stock level
MEMBER [Measures].[Most Real Stocktake Qty] AS
'
[Measures].[Actual Stocktake Qty]
- [Measures].[Last Sale Qty]
 + [Measures].[Last Receipts]
 - [Measures].[Last Negative Adjustment]
 + [Measures].[Last Positive Adjustment]
 - [Measures].[Last Transfer Out]
 + [Measures].[Last Transfer In]
'
Select
{[Measures].[Most Real Stocktake Qty]} on 0
From
( 
	SELECT
	{ NULL : 
		[Time].[Second].&[2008-10-29 08:54:25]		//10
																//Time dimension
	} on 0
	, {[Product].[Product].&[STEVE] } on 1						//Product dimension
	, {[Location].[Location].&[1] } on 2						//Location dimension
	From
	[Amicus Dev]
)

此外,这个版本的查询更简洁。现在我们用C#来编写!

C#/ADOMD.Net应用程序,用于从AS2005检索值

C# application to retrieve value from AS2005 usging MDX query

这是文章的一部分,您将在此找到连接到SQL Server Analysis Services并以我们的C#/.Net代码获取MDX查询结果的代码。

我不知道你怎么样,但我更喜欢以自然的方式确定日期和时间,即从日历中选择日期并以##:##:##的格式输入时间。这是我们面临的第一个挑战,因为只能通过现有的维度成员进行查询。这是很自然的,因为任何关于不存在的信息的请求都是模糊的(null)。我们可以要求用户从存储中存在的现有值列表中选择感兴趣的秒数,但这会很难看。所以没有办法,我们必须找到最佳匹配。在时间的情况下,最佳匹配是给定时间的最近(左侧)秒。

		/// <summary>
		/// Query to get biggest valid member of [Time].[Second] dimension that less than given datetime
		/// </summary>
		private const string queryGetLastSecondButGiven =
			@"With Member [Measures].x AS 'CDate([Time].[Second].CurrentMember.Name)' "
			+ @"Select Tail(Filter(Existing [Time].[Second].Children, [Measures].x <= @Second), 1) on 0, { } on 1 From [Amicus Dev] "
			+ @"Where (StrToMember(@Location), StrToMember(@Product))";

有趣的地点

  • Member [Measures].x AS 'CDate([Time].[Second].CurrentMember.Name)'

    在MDX查询中,VBA函数是可访问的。其中CDate()可以将字符串转换为日期。字符串来自[Time].[Second]维度的当前成员的名称。

  • Tail(Filter(Existing [Time].[Second].Children, [Measures].x <= @Second), 1) on 0

    让我们一步一步地分析这个块

    1. @Second - 参数,在查询执行时变为数值。
    2. Filter(... [Time].[Second].Children, [Measures].x <= @Second) 获取[Time].[Second]维度中所有小于(早于)给定日期时间的所有成员。
    3. Existing [Time].[Second].Children 仅根据当前切片器上下文获取子成员。它强制将查询上下文应用于计算成员。
    4. Tail(..., 1) 从集合中获取最后一个元组。
  • { } on 1 告诉MDX仅获取轴0,行上为null集。这是必要的,因为默认情况下会检索度量。这是对MDX说“够了”的方法。因为任何不必要的动作都会消耗CPU时间。
  • From [Amicus Dev] - 数据源。
  • StrToMember(@Location), StrToMember(@Product) - 分别作为字符串传递的地点和产品成员。
  • Where (...) - 切片器。它确定单元格上下文。

以及执行此时间成员获取任务的C#代码。最好以字符串格式获取秒数,以便以后使用。

		/// <summary>
		/// Gets biggest member of [Time].[Second] dimension but less than given date boundary
		/// </summary>
		/// <param name="upperBoundary">date time upper boundary</param>
		/// <param name="product">product to filter</param>
		/// <param name="location">location to filter</param>
		/// <returns>uniquer name of existant second member or null</returns>
		string GetNearestValidSecondMember(DateTime upperBoundary, string product, string location)
		{
			using (AdomdConnection conn = new AdomdConnection(activeConnectionString))
			{
				CellSet cs;		// cell set //query result
				try
				{
					conn.Open();
					AdomdCommand cmd = conn.CreateCommand();
					cmd.CommandText = queryGetLastSecondButGiven;

					cmd.Parameters.Add(new AdomdParameter("Second", upperBoundary));
					cmd.Parameters.Add(new AdomdParameter("Location", location));
					cmd.Parameters.Add(new AdomdParameter("Product", product));

					cs = cmd.ExecuteCellSet();
				}
				finally
				{
					conn.Close();
				}

				return cs.Axes[0].Set.Tuples[0].Members[0].UniqueName;
			}
			return null;
		}

所有细节都在后面描述。我只想提一下cs.Axes[0].Set.Tuples[0].Members[0].UniqueName这一行。此查询的CellSet始终为空。因此,要检索维度名称,我们必须通过Axes集合来访问,该集合包含轴,然后是Set,Tuples,最后是Member,它由UniqueName(成员标识符)组成。我们必须获取UniqueName,因为我们将它作为参数发送给StrToMember。这将使我们免于不必要的打字。

以类似的方式,我们获取产品和地点成员来填充相应的下拉组合框,以便用户选择(参见源代码中的详细信息,GetDimMembers(...)方法)。

当所有用户数据收集完毕后,就可以获取库存水平了。

查询。它与最终版本相同,除了它被引用,并且显式的维度成员(时间、产品和地点)被替换为参数。

		/// <summary>
		/// Query to get stocktake level of product at location for datetime
		/// </summary>
		private const string queryGetRealStocktakeLevel =
			@"With Set [ExistingSeconds] AS 'Existing [Time].[Second].Members' "
	...
			+ @"Select {[Measures].[Most Real Stocktake Qty]} on 0 From ( SELECT { NULL : "
		+ @"StrToMember(@Second) "   //[Time].[Second].&[2008-10-29 08:54:25]		"    //Time dimension
			+ @"} on 0 , {"
		+ @"StrToMember(@Product) " //[Product].[Product].&[STEVE]"	//Product dimension
			+ @"} on 1, {"
		+ @"StrToMember(@Location) " //[Location].[Location].&[1]"		//Location dimension
			+ @"} on 2 From [Amicus Dev])";

用于从AS2005检索数据的C#代码,为了使代码可运行,我们必须添加对Microsoft.AnalysisServices.AdomdClient程序集的引用,当然。

using Microsoft.AnalysisServices.AdomdClient;
	
	//code from btGet_Click(...) method
	...
			//query
			using (AdomdConnection conn = new AdomdConnection(activeConnectionString))
			{
				try
				{
					conn.Open();
					AdomdCommand cmd = conn.CreateCommand();
					cmd.CommandText = queryGetRealStocktakeLevel;
					
					cmd.Parameters.Add(new AdomdParameter("Second", second));
					cmd.Parameters.Add(new AdomdParameter("Location", location));
					cmd.Parameters.Add(new AdomdParameter("Product", product));

					CellSet cs = cmd.ExecuteCellSet();

					if (cs.Cells.Count > 0)
					{
						Cell cell = cs.Cells[0];
						teStoktakeLevel.EditValue = cell.FormattedValue;
					}					
				}
				catch (System.Exception ex)
				{
					MessageBox.Show(ex.Message);
				}
				finally
				{
					conn.Close();
				}
			}
	...

activeConnectionString的值为“Provider=msolap;Data Source=local;Initial Catalog=AmicusAnalysisServices;Cube=Amicus Dev”

简而言之,我们使用AdomdConnection对象建立与OLAP的连接,打开它(.Open()),然后使用AdomdConnection.CreateCommand()创建一个专门用于OLAP的命令对象(AdomdCommand的实例),然后将查询文本分配给创建的命令,添加参数到命令(cmd.Parameters.Add(new AdomdParameter("...", ...)),执行命令(ExecuteCellSet)。最后,我们获取感兴趣的单元格(.Cells(0))和单元格值(cell.FormattedValue)。我们不遍历单元格集,因为我们知道它应该只包含一个单元格,所以我们只访问这一个单元格。顺便说一句,ExecuteScalar没有实现。

样本部署

databaseSource.bak 文件。这是关系数据库源。创建一个空的“Cubes”数据库,并从给定的文件中进行备份(但是,您可以使用任何数据库名称,如果您想通过更改BI DevStudio AmicusAnalysisServices解决方案中的“Amicus Dev.ds”节点中的连接字符串来给自己找麻烦)。

AmicusAnalysisServices.sln。这是BI解决方案,用于在SQL Server Analysis Services中创建数据库和立方体。要部署立方体,您必须在Visual Studio中打开解决方案,然后从主菜单运行“Build/Deploy AmicusAnalysisServices”。

GetStockTakeLevel.sln。这是演示应用程序,它连接到AS2005并根据选定的参数检索Stocktake水平。

MDX。这些是包含文章中所有MDX查询的文本文件。

我的演示样本使用了DevXpress组件用于GUI。下载版本包括必需的DevXpress.DLLs,以及不包含它们的版本(Light)。如果您没有Xpress组件并且不想下载它,请放心 - 样本核心不依赖于这些DLL。

致谢

感谢阅读。

关注点

在撰写本文时,我感兴趣的是作为开发人员、项目经理或远程团队创始人参与软件/Web项目。主要是使用Microsoft Technologies、DB/ERP/Financial的项目。……但我并不局限于运用我的技能和机会,为人们创造一个更轻松、更舒适的世界。

历史

文章创作于2009年11月14日。

© . All rights reserved.