在写代码的时候遇到一个非常奇怪的问题,感觉是AX类Application的setDefaultCompany和ChangeCompany方法冲突了。我要实现的功能很简单,从外部数据中读取数据,然后写到相关表中,因为外部数据表中包含多个公司的数据所以我要用到ChangeCompany,根据情况把数据插入到不同的公司中,如下所示:
Code
while select area
where area.isVirtual == NoYes::No &&
area.id != 'DAT'
{
ChangeCompany(area.id)
{
salesId = this.parmSQLProvider().insert_AXSalesTable(area.id);
this.parmSQLProvider().insert_AXSalesLine(area.id,salesId);
//Post
salesFormLetter = SalesFormLetter::construct(DocumentStatus::Invoice);
salesFormLetter.update(SalesTable::find(salesId));
}
} 其中调用的方法insert_AXSalesLine如下所示:
Code
//Insert SalesLine
void insert_AXSalesLine(DataAreaId _areaId,SalesId _salesId)
{
SalesLine salesLine;
SalesTable salesTable;
InventDim inventDim;
InventSite site;
str sql;
int i;
;
salesTable = null;
salesTable = SalesTable::find(_salesId);
while select SiteId from site
{
sql = this.sqlInsertCSP_SalesLine(site.SiteId);
command = new CCADOCommand();
command.activeConnection(connection);
command.commandText(sql);
recordSet = command.execute();
comrs = recordSet.recordSet();
while (!recordSet.EOF())
{
salesLine = null;
inventDim = null;
fields = recordSet.fields();
salesLine.initFromSalesTable(salesTable);
//ItemId
salesLine.ItemId = fields.itemIdx(0).value();
salesLine.initFromInventTable(InventTable::findOrCreate(salesLine.ItemId));
//Qty
salesLine.SalesQty = fields.itemIdx(1).value();
salesLine.QtyOrdered = fields.itemIdx(1).value();
salesLine.RemainInventPhysical = fields.itemIdx(1).value();
salesLine.RemainSalesPhysical = fields.itemIdx(1).value();
//LineAmount
salesLine.LineAmount = fields.itemIdx(2).value();
//InventDim;
inventDim.InventSiteId = site.SiteId;
salesLine.InventDimId = InventDim::findOrCreate(inventDim).inventDimId;
salesLine.insert();
comrs.moveNext();
}
}
} 由于外部的数据量比较大,所以while循环运行的时间比较长,不知道为什么这段程序偶尔会报错说,未指定销售订单号,并且提示公司切换到**公司,**公司正是当前打开的Client指定公司,然后退出。经过跟踪发现,在调用CCADO*相关类的方法时会触发类Application的setDefaultCompany方法,该方法把公司切换到了Client所指定的公司001,而实际上我的语句运行的语境应该在ChangeCompany方法指定的公司下,它帮我切换到了Client当前指定的公司,当然就会找不到相应的销售订单了,关键是干么要帮我调用Application的setDefaultCompany方法,这明显是不合适的,它这样一调用,把我的当前公司又给切换回去了,语境变了,后面的运行都是错的了。查MSDN也没找到一个具体在什么情况下会调用Application的setDefaultCompany方法的说明,只能自己一一排除了。经过反复测试,按如下说明可复现上述问题:
1.新建一个类,随便取什么名字,比如叫ChangeCompanyProblem之类的,默认情况下该类的RunOn属性为Called from
2.重载该类的new方法,不需要修改任何代码。
3.新建一个main方法,代码如下:
Code
static void main(Args _args)
{
ChangeCompanyProblem problem;
SalesTable salesTable;
SalesLine salesLine;
;
//Change to another Company that different from current one.
ChangeCompany('001')
{
salesTable = null;
salesLine = null;
select firstonly salesTable;
//Just for having enough time to operating in the client;
sleep(10000);
problem = new ChangeCompanyProblem();
select firstonly salesLine;
}
}
这里的sleep方法,只是模拟正常情况下的一些耗时的计算,假设从select firstonly salesTable运行到problem = new ChangeCompanyProblem()需要10S,这个在一般的场景中应该是比较常见的。
4.新建一个MenuItem让其指向该类,默认情况下该MenuItem的RunOn属性为Client
5.在MainMenu中新建菜单项让其指向该MenuItem
6.在AX的标准菜单(实际上如果不通过AX的标准菜单项而是在AOT里直接打开菜单或者菜单项是不会出问题的)中打开该菜单项,在运行的时候点击客户端界面,随便点哪里,比如其他的菜单项之类的。
7.如果当前公司是"002",应该会出现如下提示:
从上面的步骤可以看出,应该是在实例化类ChangeCompanyProblem的时候把当前公司从001切换到了002,在类ChangeCompanyProblem的new方法和类Application的setDefaultCompany里加断点跟踪也可以得出这样的结论。
将类ChangeCompanyProblem的Runon属性变成Server或者不重载类的new方法,再实例化的时候也就不会切换当前公司了,于是得出如下结论:
如果通过AX的标准菜单项调用一个类实现某个功能,在切换了公司的语境下,在该类的某个方法里要实例化其他类,而被实例化的类的new方法被重载了且用户在实例化该类之前在AX界面上有其他操作(比如随便点击界面的某个地方),如果该类被实例化时发生在客户端,将会触发类Application的setDefaultCompany,导致当前公司切换至当前Client所指定的公司,这样该类被实例化后,所有的代码执行的语境将会被更改至Client的当前公司而不是ChangeCompany所指定的公司,会产生与预期不同的结果。貌似很绕口的说。。。
所以需要注意以下两点:
1.如果要在ChangeCompany包括的语句中实例化类,一定要让这些类运行在服务器端;
2.避免在ChangeCompany包括的语句中调用info,print之类的向客户端打印信息的方法,因为这也会导致application的stDefaultCompany的调用。
由于以上只是出于自己的简单测试,没有见过相应的权威的解释,没办法从原理上得到合理的解释,只是通过有限的测试得到的结果,不能保证正确性,还望高手指点。
测试版本 AX2009 SP1(RU3)