[AX]AX2012 SSRS报表使用Report Data Provider Class作为数据集

在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就可以布局数据字段了,更详细的步骤参看http://technet.microsoft.com/en-us/library/gg724119.aspx。注意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窗口打开时才会中断到断点。详见http://msdn.microsoft.com/en-us/library/gg724081.aspx

遇到一个奇怪的问题是如果在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$程序员还没来得及吧,程序员好累!

原文地址:https://www.cnblogs.com/duanshuiliu/p/2648819.html