Friday, August 6, 2010

Update_recordset und Arrays von gejointen Tabellen

Die Datenbankmodifikationsansweisung update_recordset bietet hübsche Möglichkeiten zum schnellen Ändern von Daten.

Leider hat sich hier im Kernel ein kleiner Fehler eingeschlichen, wenn man als Wertzuweisung ein Array-Element einer gejointen Tabelle verwendet. Folgendes update_recordset wird teilweise ignoriert, konkret ab der Zuweisung des Array-Element custTableRead.Dimension[2] wird diese sowie jede weitere Feldwertzuweisung ausgelassen (also auch die Zuweisung von NameAlias mit dem Wert "any Alias" wird schlichtweg nicht durchgeführt):

    CustTable    custTableUpdate;
    CustTable    custTableRead;
    ;

    ttsbegin;

    update_recordset custTableUpdate setting Street = custTableRead.Street, Name = custTableRead.Dimension[2], NameAlias = "any Alias"
        where custTableUpdate.AccountNum == "00000001"
        join custTableRead
            where custTableRead.AccountNum == "00000002";

    info(strfmt("%1", custTableUpdate.RowCount()));

    select firstonly custTableUpdate where custTableUpdate.AccountNum == "00000001";

    info(custTableUpdate.Street); // works fine so far
    info(custTableUpdate.Name); // you didn't get what you expect
    info(custTableUpdate.NameAlias); // you didn't get what you expect

    ttscommit;





Allerdings funktioniert alles prima, wenn kein Array-Feld im Spiel ist:

update_recordset custTableUpdate setting Name = custTableRead.Name, NameAlias = "any Alias"
        where custTableUpdate.AccountNum == "00000001"
        join custTableRead
            where custTableRead.AccountNum == "00000002";


Es funktioniert auch wenn das Array-Feld von der gleichen Tabelle stammt wie die zu aktualisierende Tabelle.

update_recordset custTableUpdate setting Name = custTableUpdate.Dimension[2], Street = custTableRead.Street
        where custTableUpdate.AccountNum == "00000001"
        join custTableRead
            where custTableRead.AccountNum == "00000002";

Damit eine Zuweisung von custTableRead.Dimension[2] in custTableUpdate.Name korrekt funktioniert muss man leider den altmodischen Weg gehen: Die Datensätze erst auswählen, dann die Zuweisung vornehmen und danach aktualisieren. Dies kostet zwar mehr Zeit, funktioniert dafür aber korrekt (was meiner Meinung nach auch mehr Priorität geniesst :).

ttsbegin;

    select firstonly Dimension from custTableRead
        where custTableRead.AccountNum == "00000002"
        join forupdate custTableUpdate
            where custTableUpdate.AccountNum == "00000001";
        
    custTableUpdate.Name = custTableRead.Dimension[2];
    custTableUpdate.update();
        
    ttscommit;

Dieses Verhalten bezieht sich nur auf AX 2009 und (falls nicht korrigiert) spätere Versionen. Die Möglichkeit zur Verwendung von Joins in update_recordset steht nämlich erst ab AX 2009 zur Verfügung.

Saturday, July 31, 2010

Mandatory-Eigenschaft auf Formdatasource-Feldern mit deaktiviertem Konfigurationsschlüssel

Es gibt einen Designfehler mit der Mandatory-Eigenschaft des Formdatasource-Feldes. Wenn man es auf true setzt, wird auch auf den Feldinhalt geprüft, selbst wenn das Feld mit per Konfiguration deaktiviert ist.

Als Beispiel: Die Tabelle TableA hat zwei Felder: FieldA und FieldB. FieldB hat als Konfigurationsschlüssel-Eigenschaft den Wert ConfigKeyA. Man erstellt nun ein Formular mit einer Datasource der Tabelle TableA und setzt die Mandatory-Eigenschaft von FieldB auf true. Dann deaktiviert man den Konfigurationsschlüssel ConfigKeyA im Menü Administration/Einstellungen/System/Konfiguration. Zuletzt startet man den Client neu, öffnet das erstellte Formular und versucht einen neuen Datensatz zu speichern. Der Speichervorgang wird fehlschlagen mit der Meldung, dass FieldB ausgefüllt werden muss.

Als Vorsicht beim Manipulieren der Mandatory-Eigenschaft auf  Formdatasource-Feldern.

Dies betrifft AX 2009, aber gut möglich dass das Fehlverhalten auch in früheren Versionen auftritt.

Mandatory falg on form data source field with disabled configuration key

There's a design bug with the form data source field property called mandatory. If you set it to true, the condition if the field is filled in will be checked even the field is deactivated by configuration.

As an example: Your table TableA has two fields: FieldA and FieldB. FieldB has configuration key property value ConfigKeyA. You create a form with a data source from table TableA. You set the data source field FieldB's property mandatory to true. Now you go to administration/settings/system/configuration and deactivate configuration key ConfigKeyA. At least restart client, open the created form and try to save a new record. It will fail with the message that field FieldB has to be filled in.

So be careful if manipulating the mandatory property on form data source fields.

This applies to AX 2009, but I have not tested if it applies to former Versions too.

Debugging von Tablemap-Code im Enterprise Portal

In AX 2009 (oder auch schon in AX 4.0, kann mich gerade nicht erinnern) ist es nicht möglich Ereigniscode von Steuerelementen auf Formularen zu debuggen.

Eine ähnliche Verhaltensweise besteht beim X++-Debuggen im Enterprise Portal von von Tablemap-Methoden. Der Debugger reagiert nicht auch wenn ein Breakpoint vorhanden ist. Die einzige Möglichkeit eine Reaktion des Debugger zu erzwingen ist die Verwendung der Anweisung 'breakpoint', die natürlich nach dem Debuggen wieder entfernt werden sollte.

Vielleicht betrifft dieser Effekt das Debuggen von X++-Tablemap-Code ausgeführt durch den .NET-Business-Connector ganz allgemein, und nicht nur das Enterprise Portal.

Wednesday, July 28, 2010

Debugging tablemap code trough Enterprise Portal

As a known fact (or bug), it's not possible to catch breakpoints in control events on forms since AX 2009 (or even AX 4.0 can't remember exactly).

A similar behaviour exists, if you try to debug X++ code of table maps methods in Enterprise Portal. The debugger won't react even if you have set a breakpoint. The only one method to force the degugger is to use the command 'breakpoint' in your code, which of course should be removed when finishing debugging.

May be this affects X++ table map code debugging executed by .NET Business Connector in general, not especially the Enterprise Portal.

Monday, July 19, 2010

Select Group By und Join Order By

Vorsicht ist geboten bei Select-Statements, welche Group By und Order By mischen. Zwar funktioniert die Abfrage auf der Datenbank korrekt, aber mit der Einbusse von Daten. Daten für Order By-Tabellen sind dann nämlich nicht verfügbar.

Unten stehendes Beispiel bringt die gewünschten Fälligkeitsdaten der Tabelle CustTransOpen, aber die Belege der Tabelle CustTrans fehlen:

CustTable custTable;
CustTrans custTrans;
CustTransOpen custTransOpen;
;

while select CustGroup from custtable group by CustGroup // group by
    join Voucher from custTrans // order by
         where custTrans.AccountNum == custtable.AccountNum
         join DueDate from custTransOpen group by DueDate // group by
              where custTransOpen.RefRecId == custTrans.RecId
{
    info(custTable.CustGroup); // works
    info(custTrans.Voucher); // works not
    info(Date2StrUsr(custTransOpen.DueDate)); // works
}

Wenn man der Tabelle CustTrans ein 'group by' hinzufügt, erhält man dann auch Daten:

CustTable custTable;
CustTrans custTrans;
CustTransOpen custTransOpen;
;

while select CustGroup from custtable group by CustGroup // group by
    join Voucher, RecId from custTrans group by Voucher, RecId // group by
         where custTrans.AccountNum == custtable.AccountNum
         join DueDate from custTransOpen group by DueDate // group by
              where custTransOpen.RefRecId == custTrans.RecId
{
    info(custTable.CustGroup); // works
    info(custTrans.Voucher); // works now
    info(Date2StrUsr(custTransOpen.DueDate)); works
}

Dieselbe Problematik besteht auch mit Datenabfragen die mit dem QueryRun-Objekt verarbeitet werden.

Es bleibt offen, ob die Ursache für dieses Problem bei AX oder an der SQL-Engine liegt; feststeht, dass man mit dem Mischen von Group By und Order By Bugs erzeugt, die man leider nicht so schnell herausfindet.

Thursday, July 15, 2010

Bitmanipulation with integer64

Take care on bit shifting if the result should be a 64 bit integer. Fragments like the follow code example does not take the obviously desired effect:
int64 i64 = 1 << 32; // move the very right positive bit just 32 ranks to the left
The result on that will be 0. The problem comes up with the return value from the operation 1 << 32, it's an 32 bit integer, even the result will be copied in a 64 bit integer (the bit shfiting operation is executed encapsulated before copy to variable i64). The base operand sets the type of result, in our code it's a constant number (1). And because 1 is a 32 bit integer, the results represents a 32 bit integer too. But why it ends with zero result? Its the reaction for the overflow when trying to access a bit area which is not available on a 32 bit integer (a 32 bit integer has just its 32 bits - not more, not less). Other programming languages reacts in the same situation with exceptions type overflow (f.ex. VB6) or moves the bits in circle (f.ex. C#), but AX quits that problem situation just with a type specified null value.

To ensure the code works fine, do it that way:
int64 base = 1;
int64 result = base << 32; // move the very right positive bit just 32 ranks to the left