将本机iOS应用连接到SQL Server Analysis Services多维数据集

使用Web服务模式,我们可以将本机移动应用程序与几乎任何数据源连接起来。 SSAS最常被用作Microsoft Excel和现成的BI平台的后端数据库,以实现切片和切分的即席查询用例。 但是,我们可以将本地移动应用程序连接到SSAS来提供高性能的分析数据库吗? 绝对! 让我们看看如何构建一个可靠的解决方案,将基于Swift的iOS本机应用程序与内部部署的Analysis Services多维数据集连接起来。

建筑

由于没有适用于iOS(或Android)的直接客户端框架连接到SSAS,因此我们将使用熟悉的模式将JSON Web服务放置在SSAS数据库的前面。 Windows上支持Microsoft提供的SSAS客户端驱动程序,因此我将创建的Web服务将使用.NET,并且C#代码将利用Microsoft在SQL Server功能包下载中提供的标准ADOMD客户端驱动程序。

我将使用Swift和XCode开发客户端应用程序,以本机为目标。 当然,可以通过许多不同的方式来开发客户端-本机iOS,本机Android,Xamarin,Ionic等。

最终的架构如下所示:

iOS应用程序设计

在继续之前,让我们看一下iOS应用程序的设计,这些设计将是开发步骤的结果。 这是我们将要创建的UI –一个显示在iPhone上的非常基本的KPI记分卡,数据由我们的.NET Web服务提供。

iOS应用程序中数据集成的关键是ViewController中的快速代码,该代码在屏幕快照中包含KPI列表。 这是一个缩写版本(错误处理和一些复杂性已删除)。 请注意,与任何其他数据源相比,这没有什么真正的不同-所有复杂性都封装在Web服务中。

// excerpt from DashDetailViewController.swift 
func getWebServiceData() {
// Setup headers var headers : [String : String] = [ "Content-Type": "application/json" ] // Add authorization let loginData: NSData = auth.dataUsingEncoding(NSUTF8StringEncoding)! let base64AuthString = loginData.base64EncodedStringWithOptions([]) headers["Authorization"] = "Basic " + base64AuthString // Request to .NET + SSAS web service endpoint if let url = dashParameters?.urlEndpoint, parms = dashParameters?.parameters { Alamofire.request(.POST, url, headers: headers, parameters: parms, encoding: .JSON) .responseJSON { response in let resultNSString = NSString(data: response.data!, encoding: NSUTF8StringEncoding)! // Tear down the package and iterate over the objects found inside if let package = response.result.value as? [String:AnyObject] { if let arr = package["data"] as? [AnyObject] { for (_, item) in arr.enumerate() { if let viz = item as? [String:AnyObject] { if let vizType = viz["vizType"] as? String { if vizType == "LineChart" { print("Calling addVizTrendLineChart") self.addVizTrendLineChart(viz) } if vizType == "KPIItem" { self.addVizKpi(viz) } } } } } self.collectionView?.reloadData() } } }

编写MDX(或DAX)查询

若要创建显示在iOS应用程序中的数据,我们需要查询Analysis Services多维数据集。 与任何数据库(SQL Server,Oracle,MongoDB,DocumentDB等)一样,SSAS Cubes也具有查询语言。 实际上,它们有两种语言,具体取决于Analysis Services是使用多维模式还是表格模式。 在此示例中,我使用了MDX,但是该技术与DAX一样有效。

这是创建上面显示的数据集的MDX:

 // TerritoryKpiList.mdx WITH MEMBER [Measures].[TerritoryLabel] AS [Sales Territory].[Sales Territory].CurrentMember.UniqueName MEMBER [Measures].[TerritoryKey] AS [Sales Territory].[Sales Territory].CurrentMember.Name MEMBER [Measures].[ChildCount] AS [Sales Territory].[Sales Territory].CurrentMember.Children.Count SELECT { [Measures].[TerritoryLabel], [Measures].[TerritoryKey], [Measures].[ChildCount], KPIValue("Revenue"), KPIGoal("Revenue"), KPIStatus("Revenue"), KPITrend("Revenue") } ON COLUMNS, NON EMPTY { (STRTOMEMBER(@TerritoryKey).Children) } ON ROWS FROM [Adventure Works] WHERE (STRTOMEMBER("[Date].[Fiscal].[Fiscal Year].&[" + @Year + "]")) 

请注意,MDX查询中有两个绑定参数: TerritoryKeyYear 。 iOS应用程序通过上面的Swift代码中称为dashParameters的JSON对象传递这些参数。 Web服务从JSON参数对象检索它们,然后通过AdomdCommand将它们注入查询中。

实施.NET Web服务

将SSAS数据传递到移动应用程序的核心是.NET Web服务,在本例中是使用Visual Studio 2015和C#开发的。 实施.NET Web服务非常简单,我不会逐步介绍它。 但是,让我们看一下实现与SSAS连接的一些重要概念。

就像从Web服务进行任何数据库访问一样,也需要数据库驱动程序。 SSAS支持几种不同的选项:OLEDB,XML / A和ADOMD。 我几乎在每个编程用例中都建议使用ADOMD,对于此Web服务,这就是我所使用的。

添加ADOMD引用的第一步是从Microsoft获得并安装驱动程序软件。 最好的方法是从Microsoft SQL功能包中下载要使用的SQL Server版本。 在撰写本文时,最新的生产版本是SQL Server 2014,功能部件包位于此处。 SQL Server功能包下载。

您需要在生产Web前端以及.NET开发工作站或VM上安装ADOMD。

在开发工作站的Web前端上安装ADOMD之后,添加对它的引用,因此您的引用看起来类似于此:

实现ApiController

关键步骤是实现.NET控制器。 同样,这很简单,与您对任何类型的数据库后端所做的操作并没有真正的不同-我不会逐步进行。 但是,让我们看一下控制器中与从SSAS检索和返回数据有关的部分。

 // excerpt from CorporateSalesController.cs 
namespace AdventureWorksAPI.Controllers {
public class CorporateSalesController : ApiController { [HttpPost] public IHttpActionResult TerritoryKpiList([FromBody]WebServiceParameters jsonData) { string queryName = @"CorporateSales\TerritoryKpiList"; try { AdomdCommand cmd = ControllerHelper.setupMDXQuery(queryName); cmd.Parameters.Add("TerritoryKey", jsonData.territoryKey); cmd.Parameters.Add("Year", jsonData.year.ToString()); using (var reader = cmd.ExecuteReader()) { List items = new List(); while (reader.Read()) { Util.MdxUtils.debugDumpRow(reader); KpiItem item = new KpiItem(); int n = reader.FieldCount - 1; item.key = MdxUtils.readString(reader[n - 6]); item.name = MdxUtils.readString(reader[n - 5]); item.childCount = MdxUtils.readInt(reader[n - 4]); item.value = MdxUtils.readDouble(reader[n - 3]); item.goal = MdxUtils.readDouble(reader[n - 2]); item.status = MdxUtils.readInt(reader[n - 1]); item.trend = MdxUtils.readInt(reader[n - 0]); items.Add(item); } List data = new List(); data.Add(new KpiItemPackage( "TerritoryPerformance", "Territory Performance", items, "$#,###,###", "($#,###,###)", "api/CorporateSales/TerritoryDetailViews")); return Ok(new VisualizationPackageObject(data)); } } catch (Exception ex) { log.Error("*** ERROR: " + this.GetType().Name + "." + System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message); return InternalServerError(ex); } }

该控制器派生自标准.NET ApiController基类,并使用AdomdCommand对SSAS多维数据集执行MDX查询。 注意-ADOMD可以处理MDX或DAX,因此您可以在这种类型的Web服务中使用任一查询语言变体。

请注意,在上述控制器方法中,您实际上并没有看到MDX查询为文本。 由于我不想将MDX查询与C#源代码混合使用,因此我将其放在Web服务器的文件系统上,并使用setupMDXQuery(queryName)方法从磁盘加载它并将其作为AdomdCommand返回。 我发现它更干净,并且它允许原子地对查询字符串进行维护和更新,而无需更改Web服务源代码。

此方法的代码如下:

 // excerpt from ControllerHelper.cs public class ControllerHelper { public static AdomdCommand setupMDXQuery(string queryName) { string connString = Util.MdxUtils.getConnectionString(); string query = Util.MdxUtils.fetchNamedMDXQuery(queryName); // Trap for named query missing from file system if (query == null) { throw new Exception("Could not find named query " + queryName); } AdomdConnection conn = new AdomdConnection(); conn.ConnectionString = connString; conn.Open(); AdomdCommand cmd = new AdomdCommand(query); cmd.Connection = conn; return cmd; } } 

fetchNamedMDXQuery(queryName)只是一个帮助程序例程,用于为Web服务器上的文件系统提供I / O。 该代码如下:

 // excerpt from MdxUtils.cs public static string fetchNamedMDXQuery(string queryName) { string cwd = HttpContext.Current.Server.MapPath("~"); string filePath = cwd + @"\MDX\" + queryName + ".mdx"; return File.ReadAllText(filePath); }