在SSRS报表中使用默认的Dynamics AX作为数据源时可以使用多种数据集抓取数据,Report data provider class是其中一种,它用在一些数据在呈现到报表前还需要做一些处理,这些处理在AX中使用X++完成。
如果要在报表中使用一些参数,就需要在AOT中定义一个DataContract类,由它来定义报表所有参数名称及类型:
[DataContractAttribute]public class SrsRDPContractSample{ AccountNum accountNum; CustAccountStatement accountStmt; boolean inclTax;}[DataMemberAttribute("AccountNum")]public AccountNum parmAccountNum(AccountNum _accountNum = accountNum){ accountNum = _accountNum; return accountNum;}[DataMemberAttribute("CustAccountStatement")]public CustAccountStatement parmAccountStmt(CustAccountStatement _accountStmt = accountStmt){ accountStmt = _accountStmt; return accountStmt;}[DataMemberAttribute("InclTax")]public boolean parmInclTax(boolean _inclTax = inclTax){ inclTax = _inclTax; return inclTax;}
SrsRDPContractSample是一个不继承于任何类的类,通过DataContractAttribute特性标注,定义了几个parmXXX方法来返回成员变量,这些方法通过DataMemberAttribute特性来定义报表参数的名称。
[ SRSReportQueryAttribute (querystr(Cust)), SRSReportParameterAttribute(classstr(SrsRDPContractSample))]public class SrsRdpSampleClass extends SRSReportDataProviderBase{ TmpCustTableSample tmpCust;}[SRSReportDataSetAttribute("TmpCust")]public TmpCustTableSample getTmpCustTable(){ select * from tmpCust; return tmpCust;}public void processReport(){ AccountNum accountNumber; CustAccountStatement custAcctStmt; boolean boolInclTax; Query query; QueryRun queryRun; QueryBuildDataSource queryBuildDataSource; QueryBuildRange queryBuildRange; CustTable queryCustTable; SrsRdpContractSample dataContract; // Get the query from the runtime using a dynamic query. // This base class method reads the query specified in the SRSReportQueryAttribute attribute. query = this.parmQuery(); // Get the parameters passed from runtime. // The base class methods read the SRSReportParameterAttribute attribute. dataContract = this.parmDataContract(); accountNumber = dataContract.parmAccountNum(); custAcctStmt = dataContract.parmAccountStmt(); boolInclTax = dataContract.parmInclTax(); // Add parameters to the query. queryBuildDataSource = query.dataSourceTable(tablenum(CustTable)); if(accountNumber) { queryBuildRange = queryBuildDataSource.findRange(fieldnum(CustTable, AccountNum)); if (!queryBuildRange) { queryBuildRange = queryBuildDataSource.addRange(fieldnum(CustTable, AccountNum)); } // If an account number has not been set, then use the parameter value to set it. if(!queryBuildRange.value()) queryBuildRange.value(accountNumber); } if(custAcctStmt) { queryBuildRange = queryBuildDataSource.findRange(fieldnum(CustTable, AccountStatement)); if (!queryBuildRange) { queryBuildRange = queryBuildDataSource.addRange(fieldnum(CustTable, AccountStatement)); } // If an account statement has not been set, then use the parameter value to set it. if(!queryBuildRange.value()) queryBuildRange.value(int2str(custAcctStmt)); } if(boolInclTax) { queryBuildRange = queryBuildDataSource.findRange(fieldnum(CustTable, InclTax)); if (!queryBuildRange) { queryBuildRange = queryBuildDataSource.addRange(fieldnum(CustTable, InclTax)); } // If flag to include tax has not been set, then use the parameter value to set it. if(!queryBuildRange.value()) queryBuildRange.value(int2str(boolInclTax)); } // Run the query with modified ranges. queryRun = new QueryRun(query); ttsbegin; while(queryRun.next()) { tmpCust.clear(); queryCustTable = queryRun.get(tablenum(CustTable)); tmpCust.AccountNum = queryCustTable.AccountNum; tmpCust.CustName = queryCustTable.name(); tmpCust.LogisticsAddressing = queryCustTable.address(); tmpCust.CustGroupId = queryCustTable.CustGroup; tmpCust.Phone = queryCustTable.phone(); tmpCust.CustInvoiceAccount = queryCustTable.InvoiceAccount; tmpCust.CustAccountStatement = queryCustTable.AccountStatement; tmpCust.InclTax = queryCustTable.InclTax; tmpCust.insert(); } ttscommit;}
SrsRdpSampleClass就是我们的Report data provider class,继承于SRSReportDataProviderBase类,注意特性SRSReportQueryAttribute('Cust')标识了所使用的Query对象,特性SRSReportParameterAttribute(classstr(SrsRDPContractSample))则说明了前面定义的报表参数data contract类。SrsRdpSampleClass定义了表TmpCustTableSample类型的变量tmpCust,TmpCustTableSample是一个TmpDB类型的临时表,通过getTmpCustTable方法返回这个临时表变量中的纪录数据,注意getTmpCustTable方法带有特性[SRSReportDataSetAttribute('TmpCust')],标识一个名为TmpCust的数据集。那么这个临时表tmpCust中的纪录数据又是哪里来的呢?这个是在重载函数processReport()函数中添加的,先是从通过this.parmQuery()得到相应的query对象,this.parmDataContract()得到data contract对象,根据参数来添加过滤条件到Query,最后运行Query,得到的结果添加到临时表变量tmpCust。
在VS2010中要使用这个report data provider,需要把data source type设为Report data provider:
在属性Query中点击...按钮可以选择到SrsRdpSampleClass及其fields,结果自动写入到Query属性中的查询字符串。有了dataset就可以布局数据字段了,更详细的步骤参看。注意Dataset的Dynamic Filters属性,设为true允许用户在query中自定义一些过滤的条件。
Report data provider被.net编写的service调用,所以它的调试需要做一些特别的设置。首先要保证AOS服务所用账号被添加到Microsoft Dynamics Ax Debugging Users本地用户组中,同时Microsoft Dynamics AX server cofiguration中启用Enable brakpoints to debug X++ code running on this server和Enable global breakpoints to debug X++ code running in batch jobs,当然用户options中Debug mode 设置为when breakpoint。最后还要记得手工打开运行Microsoft dynamics 2012 debugger,只有debugger窗口打开时才会中断到断点。详见
遇到一个奇怪的问题是如果在Report data provider class中更改了所用的Query,比如把前面的SRSReportQueryAttribute('Cust')改成SRSReportQueryAttribute('CopyOfCust'),CopyOfCust是从Query “Cust”复制过来的,在VS2010中预览报表可以看到结果是来自于这个新的Query,但是如果在AX中已经使用过MenuItem运行过这个报表就会发现总是使用第一次运行时所用的Query,原以为这和用户的User Data有关系,清理了几遍User data也是这样。被这个问题折腾了快半天,过调试代码发现整个过程异常复杂,大致是SrsReportRunService.getReportDataContract(str _reportName)初始化SrsReportDataContract的实例->SrsReportRunService.getRdlParser(str _reportName)->SrsReportRunCache.getRdlParser(str _reportName)->SrsReportRunCache::getValue(SrsReportRunCacheScope _scope, container _key)->classfactory.globalObjectCache().find(SrsReportRunCache::getCacheScopeStr(_scope), _key),最后看到这些信息pack之后被存放到了SysGlobalObjectCache,而不是User data中,所以清理User data没有用。我们知道SysGlobalObjectCache存放的是在所有client session可以共享的缓存数据,用来存放那些频繁读取的数据以提高性能,重启AOS这些cache数据就丢失了,而我们的这个问题实际上是可以手工刷新来解决的:Tools->Caches->Refresh elements,为什么就不能在VS2010更新报表后自动刷新下相关缓存纪录呢?!M$程序员还没来得及吧,程序员好累!