Friday, January 29, 2010

Wie man die Modifier Private, Protected und Abstract umgeht

Auf Wunsch der Leserschaft werden meine Beiträge nun auch in deutscher Sprache veröffentlicht. Dieser Post bezieht sich auf den englischsprachigen Post How to ignore Private, Protected and Abstract modifiers. 

Bisher habe ich mich immer über die Meldungen "Statisches Konstrukt implementieren, um Änderungen zu ermöglichen." und "'New' muss den Status 'protected' aufweisen." gewundert.

Sagen wir, wir möchten eine neue Verarbeitung mit einer RunBaseBatch-Klasse realisieren: Wenn wir nun den Optimalen Verfahren folgen, werden wir folglich die new()-Methode Protected deklarieren. Aber wie soll nun das Framework der Stapelverarbeitung jemals ein Objekt aus dieser Klasse erstellen, sollte der Benutzer die Verarbeitung in den Stapel stellen?

Weil die new()-Methode ja nun mit dem Modifier Protected geschützt ist, kann aus der Klasse kein Objekt mit new() erstellt werden, richtig?
Theoretisch ja, allerdings hat man da die Rechnung ohne die Möglichkeiten der Reflection-Technik in AX gemacht.

Wie im Allgemeinen bekannt, ist es möglich mit dem Kernelobjekt vom Typ DictClass (oder mit dem vielleicht besser bekannten abgeleiteten Typ SysDictClass) dynamisch Objekte zu erstellen und Objektmethoden aufzurufen. Nach ein paar Tests hat sich herausgestellt, dass das DictClass Objekt seinen ganz eigenen Regeln folgt, wenn es um die Möglichkeiten der Codeverarbeitung geht.

Mit einem DictClass-Objekt kann man Objekte aus Klassen erstellen, die dafür gar nicht geeignet sind. So kann man mit der makeObject()-Methode:
  • Objekte aus abstrakten Klassen erzeugen 
  • Objekte erstellen auch wenn die new()-Methode Private oder Protected deklariert ist (die new()-Methode wird dann auch wirklich durchlaufen) 
  • Objekte erstellen, auch wenn die jeweilige Klasse nicht alle abstrakten Methoden überschrieben hat 
Aber die Möglichkeiten mit dem DictClass-Objekt enden nicht einfach damit verbotene Objekte zu erstellen. Es ist auch möglich:
  • Member- und statische Methoden ungeachtet der Modifier protected, private oder abstract aufzurufen (mit callObject() und callStatic()
Als wäre das nun nicht schon genug der Blasphemie: das DictTable-Objekt verfügt ebenfalls über diese Verhaltenseigenschaften. Mit callStatic() und callObject() des DictTable-Objektes kann jede beliebige Tabellenmethode aufgerufen werden.

Fazit: In AX existieren zwar hässliche, aber äusserst effektive Verfahren um Codesicherheit und Kompilerprüfungen zu umgehen. Auch wenn es möglich ist, diese Tricks einzusetzen, rate ich dringend davon ab. Dieser Artikel soll nur zeigen was möglich ist, das heisst aber nicht, dass es auch sinnvoll ist.

(Inhalt bezieht sich auf AX 2009)

TypeId from table field

How to retrieve the typeId by a given FieldId? I didn't found a method doing this in AX, so I wrote it by my self:

int fieldId2TypeId(TableId _tableId, FieldId _fieldId)
{
    DictField dictField = new DictField(_tableId, _fieldId);
    int ret; // 32 bit length type
    ;
    if (dictField.typeId())
    {
        // extended type is given
        ret = dictField.typeId(); // dictField.typeId() is an int 16 range value
    }
    else if (dictField.enumId())
    {
        // enum type is given
        ret = dictField.enumId(); // dictField.enumId() is an int 16 range value
    }
    ret = ret << 16; // move enum or exteneded type information to left side 16 bit area


    ret = dictField.type() | ret; // combine left side 16 bits from typeId information with right side 16 bits from enum or extended type information


    return ret;
}

Wednesday, January 13, 2010

Import and Export Labels for external translations

When working in international and multi language environment, often situation comes where you have a label file (*.ald) and you should extract the labels for transfer to translation office. After, you have to create a new label file with the translated data from the translation office retrieved.

To make that process more handy, I have created an export and import routine to do the import and the export from and to an excel file.

How to install

  • Download and import the XPO file from here.

How to use it

  • Open Menu Microsoft Dynamics AX\Tools\Development tools\Label\Excel
  • Choose Dump (for export) or Load (for import)
  • Or open from AOT\MenuItems\Action the item Dev_LabelFileToExcel or the item Dev_LabelFileFromExcel


Exportation of Labels



You can export into an Excel file from scratch or in an Excel file which was exported and translated before; template and translation labels will be read from source and written into Excel file. But if the template label expression given by Excel file (in case you select one) is not corresponding to the template expression that comes along the source, the translation expression is put just idle to Excel file so the translation office can translate it again (means: the expression has changed since last time export).
First time exportation should always be done with option Recreate file.
The source can either be the current *.ald files given by application you are working on, or from a user defined application directory (or copy of). Keep in mind that you probably have to restart the AOS to ensure the label files are up to date.
If you choose option File System Directory (Server/AOS), your client does not need file access to the application folder, the server will read the files.
The process will finish with a display of the modified/created Excel file. All orange fields has now to be translated.

Importation of labels


Choose the Excel file where the translated table are stored. The structure must correspond to the Excel file which was exported (otherwise the import will fail).
Choose the directory where an *.ald label file should be created. Don't specify an application directory where an running AOS is accessing (otherwise you can get strange display effects in application, data access violations and label file loose). If a label file for the given translation language is already present, it will be replaced.

Hope this tool makes your live with AX easier as did for me.

It works in AX 2009 with Office 2007 (may be it works for AX 4.0 and Office 2003 as well)
This post will not be translated into German, since it's not very hard to understand this tool ...

Friday, January 8, 2010

Add-In Extension: Create default methods for classes

May be you know the Add-In for automated creation of find(..) & exists(..) method for tables provided by Axaptapedia?

Well, I have created a similar Add-In for classes. The goal of that Add-In is the same as the find(..) & exists(..) Add-In, to speed up code implementation.

What's the functionality?
  • It creates required abstract and interface methods
  • If the class is not abstract, a protected new(..) and a static construct(..) constructor will be created
  • If the class is in the RunBase tree, a static main(..) method is created
  • Already created methods will not be touched

How to install:
  • Import this XPO containing a private project with a class and a menu item.
  • Add the menu Item to the menu SysContextMenu (you can decide on what position)
  • Add a the code below to the class method SysContextMenu/verifyItem before line 217 (switch menuItem)
    case menuItemActionStr(DEV_CreateDefClsMeths):
        return ! docNode && _firstType == UtilElementType::Class;
  • In AOT, select a class and open context menu/Add-Ins. You will see now the new entry named Create default class methods. Klick it to process.
Tip: place the modification on a private layer (a layer which is used internally only): it's not really customer related, so you don't have to deliver it to them.

It works in AX 2009. Not tested for AX 4.0

(this article is available in English only)

Wednesday, January 6, 2010

Join-Queries mit temporären Tabellen (und es funktioniert eben doch)

Join-Queries mit temporären Tabellen (und es funktioniert eben doch)

Dieser Beitrag bezieht sich auf den englischsprachigen Artikel Queries with temporary tables (nevertheless it works)

Die Verwendung von temporären Tabellen innerhalb Queries kann ab und an zu Problemen führen; im Speziellen, wenn man die Fehlermeldung "Ein Datensatz in % (%) kann nicht ausgewählt werden. Temporäre Tabellen müssen bei Verknüpfung mit persistenten Tabellen äußere Tabellen sein." erhält. Ein Arbeitskollege hat allerdings eine solche Query zu meinem Erstaunen trotzdem zum Laufen gebracht. Auf der Grundlage seiner Vorarbeit habe ich das Thema mal genauer untersucht.

Die Inhaltsgrundlage dieses Beitrages basiert auf dem Wissen durchgeführten Tests. Die Funktionsweise von Queries mit temporären Tabellen wird hier allein durch Beobachtungen erklärt (auch wenn AX vielleicht anders als angenommen arbeitet).

Der genannte Fehler wird ausgelöst wenn die Datenselektion der jeweiligen Datenquelle nicht auf dem gleichen Tier ausgeführt wird, wie die übergeordnete (parent) Datenquelle. Die erste Datenquelle nimmt allerdings eine Sonderstellung ein. Die Datenquellen werden nämlich auf dem Tier abgearbeitet wo sich der Datenpuffer der Datenquelle befindet. Hat aber der Server-Tier einmal mit der Abarbeitung einer Datenquelle begonnen, kann für die weitere Datenselektion nicht mehr zurück auf den Client-Tier gewechselt werden.

Unter Beachtung einiger Regeln steht für eine Join-Query mit temporären Tabellen aber absolut nichts im Wege. Es gilt zu beachten:
  • Persistente Tabellen werden stets auf dem Server-Tier verarbeitet
  • Temporäre Tabellen werden auf dem Tier verarbeitet, auf den das Handle der Tabelle hinzeigt
    • Temporäre Tabellen, die üblicherweise persistent sind, aber mit der setTmp()-Methode zur Laufzeit temporär gestellt werden, werden auf dem Tier gehalten, wo die setTmp()-Methode angewendet wird

      CustTable custTable
      ;
      custTable.setTmp(); // now the handle will fixed on the current tier

    • Temporäre Tabellen welche bereits auch als solche im AOT definiert sind, werden dort gehalten wo sich auch die Deklaration befindet

      TmpSysQuery
      tmpSysQuery; // the handle is already fixed on the current tier
      ;

  • Die erste Datenquelle in der Query kann ihren Datenpuffer wahlweise vom Client- oder Server-Tier beziehen
  • Alle weiteren, und somit eingebetteten Datenquellen müssen ihren Datenpuffer entweder auf dem gleichen Tier wie ihre übergeordnete (parent) Datenquelle oder aber auf dem Server-Tier halten

Folgende Konstellationen sind demnach möglich oder eben nicht (einige Beispiele):
(Siehe englischer Artikel)

Fazit: Die Meldung "Ein Datensatz in % (%) kann nicht ausgewählt werden. Temporäre Tabellen müssen bei Verknüpfung mit persistenten Tabellen äußere Tabellen sein." würde wohl besser heissen: "Temporäre Datenquelle % bezieht Daten nicht aus dem gleichen Tier wie die übergeordnete Datenquelle % noch vom Server-Tier selbst.".

Hier noch ein paar allgemeine Anmerkungen zur Verwendung der setTmp()-Methode für persistente Tabellen:
  • Die setTmp()-Methode muss immer in derselben Methode wie die Deklaration der Tabelle erfolgen
  • Es darf noch keine Manipulation mit dem Tabellenpuffer erfolgt sein (andernfalls hat die setTmp()-Methode keinen Einfluss mehr auf den Puffer!)
  • Für Datenmanipulationen sollten die Skip-Methoden wie skipDataMethods()/skipDeleteMethods()/skipEvents()/skipDataBaseLog() verwendet werden und mit den Befehlen doUpdate(), doInsert() und doDelete() gearbeitet werden um ungewollte Änderungen an der persistenten Tabelle in der Datenbank zu verhindern


(Inhalt bezieht sich auf das QueryRun Objekt der Versionen AX 3.0 3tier, AX 4.0 und AX 5.0)

Join-Queries with temporary tables (nevertheless, it works)

Usage of temporary tables often ends in trouble, especially when the error message “Cannot select a record in % (%). Temporary tables must be the inner tables when joined to permanent tables.“ rises up.

But a workmate of mine has solved the issue of that message with a special client/server constellation. Basing his solution I did some further research.

This article is based on knowledge elaborated by tests. All explanation of query functionality is related to its results (even AX perhaps does not work as described).

All problems begin, when the data selection of a data source is made in a different tier than its parent data source. Only the first data source has free tier choice. Data sources will be fetched on the tier where its data buffer is located. Once the server tier has become as data handler, it is not possible to re-access client data.

But if you take care to some rules, a query with temporary tables will run fine. Keep in mind, that:

  • Permanent tables are fetched on server tier
  • Temporary tables are fetched on there, where its data buffer is located:
    • Temporary tables, which are usually permanent, but made temporary with a setTmp() call, gets its tier where you make the setTmp()-call.

      C
      ustTable
      custTable
      ;
      custTable.setTmp(); // now the handle will fixed on the current tier

    • Temporary tables, which are already declared in AOT as temporary, gets its tier there, where its declaration is located

      TmpSysQuery
      tmpSysQuery; // the handle is already fixed on the current tier
      ;

  • First data source’s data buffer can either be located on the client tier or on the server tier as well
  • Every other, and therefore embedded data source must have its data buffer on the same tier as the parent data source or on the server tier

Look these examples which are possible or even not (not all possibilities listed …):













Summary: The message “Cannot select a record in % (%). Temporary tables must be the inner tables when joined to permanent tables.“ would better be named as “The temporary table's data buffer % is neither on the same tier as its parent % nor on the server tier”.

Some common remarks when using permanent tables as temporary tables:
  • The setTmp() method should done in the same method as where the table’s declaration is located
  • Between declaration and setTmp()-call no buffer manipulation is allowed (otherwise setTmp() will not work!)
  • When doing data manipulations use the skip-methods like skipDataMethods()/skipDeleteMethods()/skipEvents()/skipDatabaseLog() and the commands doUpdate(), doInsert() and doDelete() to prevent unwanted manipulation on the permanent data base table
(Content is related to the QueryRun Object from Version AX 3.0 3tier, AX 4.0 und AX 5.0)