There is a really weird bug in AX 2009 with CLR enumerations. If the code has "if" statement that validates value of a variable of CLR enum type everything is fine. However, the same "switch" statement fails with kernel exception.
Example:
MyNamespace.MyEnum enum;
enum = MyNamespace.MyEnum::Value1;
if (enum == MyNamespace.MyEnum::Value1)
{
info("Everything is fine");
}
switch (enum)
{
case MyNamespace.MyEnum::Value1 :
info("And not so good here");
break;
}
Result:
Error executing code: Wrong argument types for comparison.
Showing posts with label bug. Show all posts
Showing posts with label bug. Show all posts
Monday, April 26, 2010
Thursday, April 22, 2010
Magical FetchMode property
What does the FetchMode property on a query do? I will appreciate if someone will be able to give me very precise answer.
I know only implication of this parameter. It matters when exist/notexist joined datasources on the query are forming a tree - not a chain. If the tree of datasources is quite complex then your query has a very good chance not to work if FetchMode is not set to One2One.
To my knowledge it is related somehow to the way the kernel stores query datasources.
So, my rule of thumb is the following: if a query datasources form tree-like structure, FetchMode must be set to One2One everywhere exist/notexist join is used.
Example:
Query query;
QueryBuildDataSource qbdsInventTable;
QueryBuildDataSource qbdsInventItemGroup;
QueryBuildDataSource qbdsInventModelGroup;
;
query = new Query();
qbdsInventTable = query.addDataSource(tablenum(InventTable));
qbdsInventItemGroup = qbdsInventTable.addDataSource(tablenum(InventItemGroup));
qbdsInventItemGroup.relations(true);
qbdsInventItemGroup.joinMode(JoinMode::ExistsJoin);
qbdsInventItemGroup.fetchMode(QueryFetchMode::One2One);
qbdsInventModelGroup = qbdsInventTable.addDataSource(tablenum(InventModelGroup));
qbdsInventModelGroup.relations(true);
qbdsInventModelGroup.joinMode(JoinMode::NoExistsJoin);
qbdsInventModelGroup.fetchMode(QueryFetchMode::One2One);
I know only implication of this parameter. It matters when exist/notexist joined datasources on the query are forming a tree - not a chain. If the tree of datasources is quite complex then your query has a very good chance not to work if FetchMode is not set to One2One.
To my knowledge it is related somehow to the way the kernel stores query datasources.
So, my rule of thumb is the following: if a query datasources form tree-like structure, FetchMode must be set to One2One everywhere exist/notexist join is used.
Example:
Query query;
QueryBuildDataSource qbdsInventTable;
QueryBuildDataSource qbdsInventItemGroup;
QueryBuildDataSource qbdsInventModelGroup;
;
query = new Query();
qbdsInventTable = query.addDataSource(tablenum(InventTable));
qbdsInventItemGroup = qbdsInventTable.addDataSource(tablenum(InventItemGroup));
qbdsInventItemGroup.relations(true);
qbdsInventItemGroup.joinMode(JoinMode::ExistsJoin);
qbdsInventItemGroup.fetchMode(QueryFetchMode::One2One);
qbdsInventModelGroup = qbdsInventTable.addDataSource(tablenum(InventModelGroup));
qbdsInventModelGroup.relations(true);
qbdsInventModelGroup.joinMode(JoinMode::NoExistsJoin);
qbdsInventModelGroup.fetchMode(QueryFetchMode::One2One);
Friday, April 9, 2010
FormRef and Go to the main table form
It is known fact that "go to the main table form" functionality uses FormRef property on the table to determine which form should be opened (a menu item can be specified there pointing the required form). The interesting fact is that even if FormRef is not specified on the table AX will still try to open some form. Actually AX will look for display menu item which has the name equals to the table name.
Frustrating thing is that if such menu item does not exist the warning will be shown on clicking "go to the main table form":
No object specified on menu item %TableName%.
Frustrating thing is that if such menu item does not exist the warning will be shown on clicking "go to the main table form":
No object specified on menu item %TableName%.
Monday, April 5, 2010
Implicit casting from super-class to sub-class
X++ compiler allows casting from the super-class object to the sub-class. For example, UserConnection class extends Connection class. And you are allowed to write the following code:
UserConnection con = new Connection();
The only consequence of doing such thing is a run-time error thrown if a method that exists only on sub-class will be called for the instantiated object.
In the modern managed languages such implicit casting is not allowed and generates compilation error. So, it is best practice not to use it in AX too.
UserConnection con = new Connection();
The only consequence of doing such thing is a run-time error thrown if a method that exists only on sub-class will be called for the instantiated object.
In the modern managed languages such implicit casting is not allowed and generates compilation error. So, it is best practice not to use it in AX too.
Friday, April 2, 2010
Breakpoint in clicked method
There is quite well known bug in AX that breakpoint placed in the clicked() method of a button will not be triggered. It is quite unpleasant one since when one faces it first time he gets totally confused.
However, there are easy workarounds for this bug. First one – put breakpoint into the method that is called from clicked (in most cases it is possible). But if not, keyword breakpoint can be used – it will be triggered in clicked() method.
However, there are easy workarounds for this bug. First one – put breakpoint into the method that is called from clicked (in most cases it is possible). But if not, keyword breakpoint can be used – it will be triggered in clicked() method.
Wednesday, March 10, 2010
Define overwrites
It is a little bit weird how X++ compiler handles the case when macros with the same name are defined in the class declaration of a class and in the one of its methods. The expected behavior for me is to either not allow to do that or to have a macro from class declaration to be applicable everywhere except for the method where it is redefined. But in AX the value defined in the method will overwrite the value defined in the class declaration and will be the same in all methods.
Example:
class TestDefine
{
#define.A(‘A’)
}
public void method1()
{
;
info(#A);
}
public void method2()
{
#define.A(‘B’)
;
info(#A);
}
If method1 or method2 will be called the ‘B’ will be received in the infolog and not ‘A’.
The next question will be what if define is overwritten in several methods. The answer is that the result is undetermined. It depends on X++ compiler internal flow and cannot be predicted. So, just avoid such constructions and even better avoid defines at all.
Example:
class TestDefine
{
#define.A(‘A’)
}
public void method1()
{
;
info(#A);
}
public void method2()
{
#define.A(‘B’)
;
info(#A);
}
If method1 or method2 will be called the ‘B’ will be received in the infolog and not ‘A’.
The next question will be what if define is overwritten in several methods. The answer is that the result is undetermined. It depends on X++ compiler internal flow and cannot be predicted. So, just avoid such constructions and even better avoid defines at all.
Monday, February 22, 2010
X++ real vs numeric(32,16)
It is an interesting fact that X++ real type is not fully compatible with the table field real type. The problem is that in the database numeric(32,16) data type is used to store fields of the real type. Numeric(32,16) data type stores numbers with no more than 32 digits total and no more than 16 digits after the decimal point, so value 1033 cannot be stored with this data type. X++ real type is floating-point type and can store values in the range from -10127 to 10127.
The following code execution will fail:
public static void testReal()
{
    InventTrans inventTrans;
    real realValue = 1.0e33;
    inventTrans.Qty = realValue;
    inventTrans.doInsert();
}
With the following exception:
The following code execution will fail:
public static void testReal()
{
    InventTrans inventTrans;
    real realValue = 1.0e33;
    inventTrans.Qty = realValue;
    inventTrans.doInsert();
}
With the following exception:
Tuesday, February 16, 2010
Priority of Boolean operations
In X++ Boolean && and || operations have the same priority. That is not the case in other languages where && has higher priority than ||. So adding additional parenthesis to make sure that code works as expected is a good idea.
Example: (true || false && false) will be evaluated to false in X++, but in C# it will be evaluated to true.
Example: (true || false && false) will be evaluated to false in X++, but in C# it will be evaluated to true.
Thursday, February 11, 2010
Delete actions and multiple relations
If one table has several relations to another table delete action will not work properly. In such case delete action will be triggered for only one of the relations. So, in the case of multiple relations one should write his own cascading or restricting logic in the delete method.
An example can be found in Unit and UnitConvert tables. UnitConvert table has two relations to the Unit table – from unit and to unit. Table Unit has cascading delete action for the UnitConvert table. However, if a unit will be deleted, only unit conversions with from unit equal to the deleted one will be deleted. Conversions with to unit equal to the deleted one will survive.
Example of correct implementation can be found in InventTestEmplResponsible and EmplTable. EmplTable has delete method overridden to perform manual delete in the InventTestEmplResponsible table.
An example can be found in Unit and UnitConvert tables. UnitConvert table has two relations to the Unit table – from unit and to unit. Table Unit has cascading delete action for the UnitConvert table. However, if a unit will be deleted, only unit conversions with from unit equal to the deleted one will be deleted. Conversions with to unit equal to the deleted one will survive.
Example of correct implementation can be found in InventTestEmplResponsible and EmplTable. EmplTable has delete method overridden to perform manual delete in the InventTestEmplResponsible table.
Tuesday, February 9, 2010
Intrinsic functions in queries
The following code won't compile in AX.
select extCodeTable
where extCodeTable.ExtCodeTableId == tablenum(CompanyInfo)
join extCodeValueTable
where extCodeValueTable.ExtCodeId == extCodeTable.ExtCodeId;
The reason for that is the call to tablenum(CompanyInfo) - compiler doesn't not allow it inside queries. The simplest workaround is to define a variable, assign it with tablenum(CompanyInfo) value and use it in the query.
select extCodeTable
where extCodeTable.ExtCodeTableId == tablenum(CompanyInfo)
join extCodeValueTable
where extCodeValueTable.ExtCodeId == extCodeTable.ExtCodeId;
The reason for that is the call to tablenum(CompanyInfo) - compiler doesn't not allow it inside queries. The simplest workaround is to define a variable, assign it with tablenum(CompanyInfo) value and use it in the query.
Wednesday, January 27, 2010
Insert_recordset and EntireCache
It is known fact that set-based operations, like insert_recordset, update_recodset and delete_from are falling into row-by-row operations under certain circumstances. For example, if table has insert (update or delete) methods overridden or table has delete actions or aosValidate methods are overridden. To avoid such fallback (of course, if it is acceptable that mentioned logic won't be executed) several methods should be called to disable each of the mentioned cases, for example:
inventItemGroup.skipDataMethods(true);
inventItemGroup.skipDeleteActions(true);
inventItemGroup.skipAosValidation(true);
However, if a table’s CacheLookup property is EntireTable and insert method is overridden, insert_recordset operation (and only it) will fall into row-by-row operations despite the mentioned calls. To avoid that cache should be disable too, like:
inventItemGroup.disableCache(true);
inventItemGroup.skipDataMethods(true);
inventItemGroup.skipDeleteActions(true);
inventItemGroup.skipAosValidation(true);
However, if a table’s CacheLookup property is EntireTable and insert method is overridden, insert_recordset operation (and only it) will fall into row-by-row operations despite the mentioned calls. To avoid that cache should be disable too, like:
inventItemGroup.disableCache(true);
Monday, January 18, 2010
ChangeCompany and table buffers
There is a bug with changeCompany keyword which prevents usage of the same initialized table buffer in different companies. For example, in the following code:
InventItemGroup inventItemGroup;
changeCompany ('A')
{
inventItemGroup.ItemGroupId = 'IA';
inventItemGroup.insert();
}
changeCompany ('B')
{
inventItemGroup = null;
inventItemGroup.ItemGroupId = 'IB';
inventItemGroup.insert();
}
Without inventItemGroup = null; line the second insert will throw an error:
Cannot create a record in Item groups (InventItemGroup).
Insert operations are not allowed across companies. Please use changecompany keyword to change the current company before inserting the record.
Because buffer still has dataAreaId value equals to 'A' and neither changeCompany nor insert does the substitution automatically.
So you need to either avoid using the same buffer in different companies or do not forget to reset it via assigning null value.
InventItemGroup inventItemGroup;
changeCompany ('A')
{
inventItemGroup.ItemGroupId = 'IA';
inventItemGroup.insert();
}
changeCompany ('B')
{
inventItemGroup = null;
inventItemGroup.ItemGroupId = 'IB';
inventItemGroup.insert();
}
Without inventItemGroup = null; line the second insert will throw an error:
Cannot create a record in Item groups (InventItemGroup).
Insert operations are not allowed across companies. Please use changecompany keyword to change the current company before inserting the record.
Because buffer still has dataAreaId value equals to 'A' and neither changeCompany nor insert does the substitution automatically.
So you need to either avoid using the same buffer in different companies or do not forget to reset it via assigning null value.
Subscribe to:
Posts (Atom)