...feel the spirit of Microsoft Dynamics AX RSS 2.0
 Tuesday, October 16, 2007

Nicht nur in der Microsoft Dynamics AX Entwicklungsumgebung hat man die Möglichkeit mittels Drag & Drop Veränderungen vorzunehmen. Es ist recht einfach diese Funktion auch in der Applikation verfügbar zu machen. Hier gibt es im Standard nur sehr wenige Beispiele und die es gibt verwenden alle einen Tree oder eine List als Ziel.  Ich zeige diese Drag & Drop Funktion mit zwei Grids als Quelle und als Ziel anhand eines einfachen Beispiels.

Um diese Drag & Drop Funktion in einer Maske zu implementieren braucht es zuerst einmal zwei Datenquellen, eine als Quelle und eine als Ziel. Um die Daten auf der Maske anzuzeigen hab ich jeweils ein Grid gewählt. Als Beispiel hab ich einfach eine neue Maske erstellt, die als Datenquelle SalesLine (Zieldatenquelle) und Inventtable (Quelldatenquelle) beinhaltet. Die Anzeige der Daten spielt hier nur eine untergeordnete Rolle.



Nun muss nur noch auf den Grid (SalesLineGrid und InventTableGrid) die Eigenschaft DragDrop auf „Manual“ gestellt werden um Drag & Drop zu aktivieren (Es gibt nur die Möglichkeit auf None oder Manual).




Nun kann man auf der Maske schon die Daten mittels Drag & Drop verschieben. Die Funktion zum Einfügen des Datensatzes aus der Quelle ins Ziel ist natürlich noch nicht vorhanden, man kann aber das optische Verhalten (Datensatz in ein anderes Grid ziehen) schon betrachten. 

Nun müssen noch einige Ereignisse überschrieben werden um die Funktion zu implementieren.

dragOver = hier wird festgelegt welche Aktion durchgeführt wird (Move, Copy, None).

  • Move = Mouse+SHIFT Taste (Default)
  • Copy = Mouse+STRG Taste

So kann z.B. festgelegt werden, das auf dem Grid “InventTableGrid” kein Drag&Drop möglich sein soll. Hierzu wird diese Methode wie folgt überschrieben.

public FormDrag dragOver(FormControl _dragSource, FormDrag _dragMode, int _x, int _y)
{
   FormDrag ret;

   // ret = super(_dragSource, _dragMode, _x, _y);
   // Kein Drag&Drop zulassen
   ret = FormDrag::None;

   return ret;
}

Es können hier natürlich alle möglichen Überprüfungen stattfinden um festzulegen, wann welche Option erlaubt/ nicht erlaubt ist. 

Um die Funktion nun abzuschließen fehlt nur noch das erzeugen des Datensatzes auf der Tabelle SalesLine. Hierzu wird die Method „Drop“ auf dem Grid „SalesLineGrid“ überschrieben.

Um Auftragspositionen aus den Artikelstamm anzulegen könnte die Methode so ausehen

public void drop(FormControl _dragSource, FormDrag _dragMode, int _x, int _y)
{
SalesLine sLine;

//Ist Quelle identisch mit aktuellem Grid (SalesLineGrid)
if (_dragSource.equal(this))
{
//TODO: Hier kann bspw. das Verschieben der Auftragsposition implementiert werden (LineNum)
}
//Ist Quelle InventTableGrid
else if (_dragSource.name() == InventTableGrid.name())
{
// Nur Aktion ausführen wenn Copy oder Move
if(_dragMode == FormDrag::Copy || _dragMode == FormDrag::Move)
{
sLine.initValue();
sLine.SalesId = SalesID.valueStr();
sLine.initFromSalesTable(SalesTable::find(salesLIne.SalesId));
sLine.ItemId = inventTable.ItemId;
sLine.initFromInventTable(InventTable);
sLine.createLine(NoYes::Yes, NoYes::Yes, NoYes::Yes, NoYes::Yes, NoYes::Yes, NoYes::Yes);
salesLine_ds.executeQuery();
}
}
super(_dragSource, _dragMode, _x, _y);
}


In der Maske kann nun aus dem Artikelstamm eine neue Auftragsposition mittels Drag & Drop erstellt werden. Hierfür habe ich noch eine Vorbelegung/ Einschränkung auf die Aufragsnummer vorgenommen um die Auftragsposition erzeugen zu können.

Die Drag & Drop Funktion kann nur von der Artikeltabelle zu den Auftragsposition durchgeführt werden. Umgekehrt funktioniert das nicht, was man auch optisch sehen kann. (Screenshots haben aus irgendwelchen Gründen nicht funktioniert)

Ein weiterer Vorteil bei der Drag & Drop Funktion ist, das Sie auch Maskenübergreifend funktioniert. Hierfür sind gar keine weiteren Änderungen notwendig. Es muss in der Drop Methode, wenn dort Überprüfungen stattfinden, nur der Ursprung auch erlaubt, bzw. mit berücksichtigt wurden sein.

Die gerade erstellte Maske lässt sich schon jetzt zweimal öffnen um dort von der einen zu der anderen Maske Daten mittels Drag & Drop zu übertragen (Inventtable -> SalesLine)

Durch kleine Änderungen kann diese Funktion auch aus der Artikelmaske ausgeführt werden.

Hierzu muss in der Artikelmaske einfach auf dem Grid DragDrop auf Manual gesetzt werden und die Drop Funktion in der neu erstellten Maske leicht angepasst werden.

Beispielprojekte für das einfache Drag&Drop innerhalb einer Maske und die kleine Erweiterung für das Drag&Drop aus der Artikelmaske herraus gibts auch wieder.

In der Maske "BOMDesigner" kann die Drag&Drop Funktion noch anhand einer Baumstruktur (FormTreeControl) als Quelle bewundert werden.

Beispiel SimpleDragAndDrop
Form_SimpleDragDrop.rar (1,99 KB)

Beispiel Projekt für Drag&Drop aus Artikelmaske in die Maske SimpleDragAndDrop
SharedProject_DragAndDropInventTable.rar (11,71 KB)
Tuesday, October 16, 2007 10:27:37 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] - Trackback
 | 

 Tuesday, September 18, 2007
Im letzten Artikel bin ich nur auf das lesen externer Datenquellen in Microsoft Dynamics AX eingegangen, diesesmal wird anhand eines kleine Beispiels eine externe Datenquelle befüllt.

static void ArtikelinExterneDatenquelleeinfuegen(Args _args)

{
CCADOConnection adoConnection;
CCADOCommand adoCommand;

Inventtable inventtable;
;
adoConnection = new CCADOConnection();
//ODBC-Connection
adoConnection.connectionString(strfmt("Dsn=%1",dsnName));
//Connection öffnen
adoConnection.open();

adoCommand = new CCADOCommand();
adoCommand.activeConnection(adoConnection);
//Alle Datensätze der Artikeltabelle
while select inventtable
{
// Neue Anweisung für das Einfügen des Datensatzes
adoCommand.commandText(strfmt("Insert into ExterneArtikelTabelle (Artikelkennung,Artikelname) values ('%1','%2')", inventtable.ItemId, inventtable.itemName));
// Anweisung ausführen
adoCommand.execute();
}
}

Das Erstellen der Verbindung ist identisch, nur habe ich die Klasse CCADOCommand anstelle von CCADORecordSet verwendet. Mit CCADOCommand können Manipulationen, wie das Einfügen oder das Löschen von Datensätzen, an externe Datenquellen vorgenommen werden.

Wenn man noch einen Schritt weiter gehen will kann auch die Tabelle vorher neu erstellt werden um diese dann nutzen zu können. Dies geschieht auch über SQL Befehle.

Hier nochmal ein Beispiel für das anlegen und befüllen einer Tabelle (Artikeltabelle mit den Feldern Artikelnummer und Artikelname).

static void ArtikelinExterneDatenquelleeinfuegen(Args _args)
{
CCADOConnection adoConnection;
CCADORecordSet adoRecordSet;
CCADOCommand adoCommand;
str sql;
Inventtable inventtable;
SysDictTable dictTable = new SysDicttable(inventTable.TableId);
SysDictField dictFieldID = new SysDictField(dicttable.id(), fieldnum(InventTable, ItemID));
SysDictField dictFieldName = new SysDictField(dicttable.id(), fieldnum(InventTable, ItemName));
;
//Neu Verbindung
adoConnection = new CCADOConnection();
adoConnection.connectionString(strfmt("Dsn=%1",dsnName));
adoConnection.open();

//Neu Command
adoCommand = new CCADOCommand();
adoCommand.activeConnection(adoConnection);

//Tabelle löschen wenn sie existiert
/*
sql = strfmt("DROP Table
IF EXISTS %1", dictTable.label());

adoCommand.commandText(sql);
adoCommand.execute();
*/

//Tabelle erzeugen
sql = strfmt("Create Table %1 (%2 %3(%4), %5 %6(%7))",
strrem(dictTable.label(), " "),
//Leerzeichen löschen
strrem(dictFieldID.label(), " "),
dictFieldID.baseType(),
//Datentyp festlegen
dictFieldID.stringLen(),//Größe festlegen
strrem(dictFieldName.label(), " "),
dictFieldName.baseType(),
dictFieldName.stringLen());

adoCommand.commandText(sql);
adoCommand.execute();

//Datensätze in die neue Tabelle schreiben
while select inventtable
{
sql = strfmt("Insert into %1 (%2,%3) values ('%4','%5')",
strrem(dictTable.label(), " "),
strrem(dictFieldID.label(), " "),
strrem(dictFieldName.label(), " "),
inventtable.ItemId,
inventtable.itemName);

adoCommand.commandText(sql);
adoCommand.execute();
}
}


Tuesday, September 18, 2007 9:39:45 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] - Trackback


 Wednesday, September 12, 2007

Erhält man bei seiner täglich Arbeit die Fehlermeldung „Ein Befehl der Datendefinitionssprache kann nicht für () ausgeführt werden. Die SQL Datenbank hat einen Fehler gemeldet.“ oder auf englisch „cannot excecute a data definition language command on (). The SQL database has issued an error.” stellt sich einem meist eine große Hürde in den Weg, weil aus dieser Fehlermeldung nicht eindeutig zu erkennen ist, wo ein Fehler entstanden ist bzw. wieso der Fehler auftritt.

Durch Zufall bin ich drauf gestoßen, wie man genau diese Fehlermeldung produzieren kann und somit Rückschlüsse auf die Ursache des Fehlers schließen kann.

Das Folgende Vorgehen...

  1. Erstellen eines EDT’s vom Typ „string“ mit dem Namen „MY_BaseId“.
  2. Verwenden des EDT’s in einer Tabelle (Name:  MY_BaseTable).
  3. Erzeugen von mehreren Datensätzen in der Tabelle.
  4. Löschen des EDT’s „MY_BaseId“.
  5. Erstellen eines EDT’s vom Typ „real“ mit dem Namen „MY_BaseId“ (gleicher Name wie der gelöschte EDT vom Typ „string“ hatte).
  6. Kompilieren oder synchronisieren der Tabelle „MY_BaseTable“.

...ergibt die genannte Fehlermeldung.

Daraus ist zu schließen, dass der Fehler immer Auftritt, wenn der Typ eines vorhandenen EDT’s den bereits eine oder mehrere Tabellen verwenden, geändert wird.
Ich konnte die Fehlermeldung bzw. den Fehler aber nur reproduzieren, wenn die jeweilige Tabelle ein oder mehrere Datensätze enthielt. War in der Tabelle kein Datensatz vorhanden kam es nicht zu der Fehlermeldung.

Um besagten Fehler oder besagte Fehlermeldung zu beheben müssen entweder alle Datensätze in der Tabelle oder die Tabelle selbst gelöscht werden.

Wednesday, September 12, 2007 7:14:27 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [1] - Trackback
 |  | 

 Monday, September 10, 2007
Ich habe in letzter Zeit ein sehr merkwürdiges Verhalten beim Verlassen von Microsoft Dynamics AX 4.01 feststellen müssen. Es kam beim Abmelden immer zum Absturz der Application Object Server (AOS) Instanz. Das war bei zwei unterschiedlichen Installationen zu beobachten.
Grund hierfür war das bei den aktuelle Applikationen der beiden AOS Instanzen eine Labeldatei (*.ald) gefehlt hatte. Diese fehlende Labeldatei hat dann beim Abmelden den Absturz verursacht.
Nach Neuanlage der fehlende Labeldatei kam es zu keinen Problemen mehr.

Monday, September 10, 2007 8:59:29 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [1] - Trackback


 Monday, September 03, 2007

Eine Bestellung umfasst in Microsoft Dynamics AX immer einen Datensatz der Tabelle „PurchTable“ und wenn die Bestellung einen Artikel enthält, auch einen Datensatz in der Tabelle „PurchLine“. Zusätzlich werden in Abhängigkeit von den Daten der Bestellung (Einmallieferant: Ja/Nein, Intercompany: Ja/Nein, etc.) zusätzliche Datensätze in anderen Tabellen erzeugt bzw. geändert. Beispielhaft sei hier die Tabelle „VendTable“ genannt. In dieser wird ein neuer Lieferant erstellt, wenn beim Erstellen der Bestellung angegeben wurde, dass es sich um einen Einmallieferanten handelt. Ein weiteres Beispiel wäre die Tabelle „MarkupTrans“ in der in Abhängigkeit von den Einstellungen für sonstige Zuschläge ebenfalls weitere Datensätze erzeugt werden.

Die Logik, die das Erstellen der einzelnen Datensätze der verschiedenen Tabellen steuert wird in Microsoft Dynamics AX durch die Klassen „PurchTableType“ (Abbildung 1) und „PurchLineType“ (Abbildung 2), sowie deren abgeleiteten Klassen abgebildet. Diese Klassen steuern das Verhalten bei Anlage, Änderung und Löschung einer Bestellung. Dies beinhaltet auch, welche Werte ein Feld bei welchem Bestellungstyp annehmen darf, was geschieht wenn ein Feld geändert wird, was wird wie gebucht und so weiter.

Diese Klassen werden von überschriebenen Methoden der Tabellen „PurchTable“ und „PurchLine“ aufgerufen. So ruft zum Beispiel die Methode „Insert“ der Tabelle „PurchTable“, die Methode „Insert“ der Klasse „PurchTableType“ auf. Abhängig vom Bestellungstyp wird über die Methode „construct“ bei der Initialisierung eines „PurchTableType“ Objekts gesteuert, welches konkrete Objekt erzeugt wird („PurchTableType_Purch“, „PurchTableType_ReturnItem“, etc.). Dies erfolgt in der Methode „type“ der Tabelle „PurchTable“ oder „PurchLine“. Unter anderem sind weiterhin die Methoden „Update“, „Delete“, „InitValue“, „ValidateField“ und „Delete“ auf die gleiche Weise überschrieben.
Ein Blick in die Methoden der Tabelle „PurchTable“ sollte dies verdeutlichen.

Somit ist die Logik, die für die Steuerung von Bestellungen in Microsoft Dynamics AX verantwortlich ist, vom Prinzip her vergleichbar mit der Logik welche die Aufträge „steuert“ (vergleiche hierzu: Microsoft Dynamics AX API – Teil 1 „Erstellen von Aufträgen“).

Deswegen ist das Erstellen einer Bestellung genau so einfach wie das Erstellen eines Auftrags. Um eine neue Bestellung zu erstellen muss im Wesentlichen nur:

  1. Eine neue Nummer des entsprechenden Nummernkreises gezogen werden.
  2. Die Methode „InitValue“ der Tabelle „PurchTable“ aufgerufen werden.
  3. Die Methode „InitFromVendTable“ der Tabelle „PurchTable“ mit Angabe des Lieferanten Datensatzes aufgerufen werden.
  4. Die Methode „Insert“ der Tabelle  „PurchTable“ aufgerufen werden.

Soll für diese gerade erzeugte Bestellung nun noch eine Artikelposition erzeugt werden, muss im Wesentlichen nur die Methode „CreateLine“ der Tabelle „PurchLine“, mit vorheriger Definition von Bestellungsnummer („PurchLine.PurchId“) und Artikelnummer („PurchLine.ItemId“), aufgerufen werden.

Hierzu ein Beispiel:

void createPurchTableAndLine()
{
   VendAccount vendAccount = "<yourVendAccount>";
   ItemId itemId = "<yourItemId>";

   PurchTable purchTable;
   PurchLine purchLine;
   NumberSeq numberSeq;
   InventTable inventTable;
   ;
   //Bestellungskopf (PurchTable)
   //Neue Bestellungsnummer aus Nummernkreis erzeugen
   NumberSeq = NumberSeq::newGetNumFromCode(
   PurchParameters::numRefPurchId().numberSequence);
   purchTable.PurchId = NumberSeq.num();

   //Bestellungskopf initialisieren
   purchTable.initValue();

   //Initialisierung der lieferantenspezifischen Bestellungsdaten
   purchTable.initFromVendTable(VendTable::find(vendAccount));

   //Bestellungskopf erstellen
   purchTable.insert();

   //Bestellungsposition (PurchLine)
   purchLine.clear();

   //Zuweisen von Bestellungsnummer und Artikelnummer
   purchLine.purchId = purchTable.PurchId;
   purchLine.ItemId = itemId;

   //Bestellungsposition erstellen (ruft PurchLine.insert auf)
   purchLine.createLine(NoYes::Yes, NoYes::Yes, NoYes::Yes,
                        NoYes::Yes, NoYes::Yes, NoYes::Yes);
}

Monday, September 03, 2007 8:02:32 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] - Trackback
 | 

 Saturday, August 25, 2007
Hin und wieder stösst man auf etwas ungewöhnliches. So geschehen vor einiger Zeit:
Es konnten zu einigen Aufträge, die Funktion "Aufträge zuordnen" nicht mehr aufgerufen werden, ohne das der Microsoft Axapta 3.0 Client abstürzt. Nach einigen Nachforschungen war die Fehlerursache klar. Die besagt Funktion erstellt für jeden zugeordneten Datensatz eine neue Einschränkung (nur in Microsoft Axapta 3.0 (welche Service Packs betroffen sind kann ich leider nicht sagen), diese Funktion wurde in Microsoft Dynamics AX 4.0 bereits überarbeitet, sodass es hier nicht mehr zu Fehlern kommen kann). Bei sehr vielen neu erzeugten Einschränkungen hat das dann zum Absturz des Clients geführt.

Folgende Grenzwerte (in Microsoft Axapta 3.0) konnte ich ermitteln:

Anzahl von Einschränkungen (Ranges) die einen Absturz des Axapta Clients verursachen: ab ~1475
Anzahl von Einschränkungen (Ranges) die "nur" eine interne Axapta Fehlermeldung auslösen: ab ~475

In Microsoft Dynamics AX 4.0 konnte ich auch bei mehr als 1475 Einschränkungen keinen Absturz provozieren, es erscheint nur eine Fehlermeldung.

*Alle hier beschriebenen Einschränkungen (Ranges) wurden auf demselben Tabellenfeld vorgenommen.

Saturday, August 25, 2007 8:07:30 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] - Trackback


 Friday, August 17, 2007

Ein Auftrag umfasst in Microsoft Dynamics AX immer einen Datensatz in der Tabelle „SalesTable“ (Auftragskopf) und wenn der Auftrag einen Artikel enthält (Auftragsposition), auch einen Datensatz in der Tabelle „SalesLine“. Zusätzlich werden in Abhängigkeit von den Daten des Auftrags (Einmalkunde: Ja/Nein, Intercompany: Ja/Nein, etc.) zusätzliche Datensätze in anderen Tabellen erzeugt bzw. geändert. Beispielhaft sei hier die Tabelle „CustTable“ genannt. In dieser wird ein neuer Kunde erstellt, wenn beim Erstellen des Auftrags angegeben wurde, dass es sich um einen Einmalkunden handelt. Ein weiteres Beispiel wäre die Tabelle „MarkupTrans“ in der in Abhängigkeit von den Einstellungen für Sonstige Zuschläge ebenfalls weitere Datensätze erzeugt werden.

Die Logik, die das Erstellen der einzelnen Datensätze der verschiedenen Tabellen steuert (die so genannte Geschäftslogik) wird in Microsoft Dynamics AX durch die Klassen „SalesTableType“ (Abbildung 1) und „SalesLineType“ (Abbildung 2), sowie deren abgeleiteten Klassen abgebildet. Diese Klassen steuern das Verhalten bei Anlage, Änderung und Löschung eines Auftrags. Dies beinhaltet auch, welche Werte ein Feld bei welchem Auftragstyp annehmen darf, was geschieht wenn ein Feld geändert wird, was wird wie gebucht und so weiter.

Diese Klassen werden von überschriebenen Methoden der Tabellen „SalesTable“ und „SalesLine“ aufgerufen. So ruft zum Beispiel die Methode „Insert“ der Tabelle „SalesTable“, die Methode „Insert“ der Klasse „SalesTableType“ auf. Abhängig vom Auftragstyp wird über die Methode „construct“ bei der Initialisierung eines „SalesTableType“ Objekts gesteuert, welches konkrete Objekt erzeugt wird („SalesTableType_Sales“, „SalesTableType_ItemReq“, etc.).
Unter anderem sind weiterhin die Methoden „Update“, „Delete“, „InitValue“, „ValidateField“ und „Delete“ auf die gleiche Weise überschrieben. Ein Blick in die Methoden der Tabelle „SalesTable“ oder „SalesLine“ sollte dies verdeutlichen.

Somit gestaltet sich das Erstellen eines neuen Auftrags sehr einfach, da die gesamte Geschäftslogik die hinter einem Auftrag steht, automatisch aufgerufen wird.

Um einen neuen Auftrag zu erstellen muss im Wesentlichen nur

  1. Eine neue Nummer des entsprechenden Nummernkreises gezogen werden.
  2. Die Methode „InitValue“ der Tabelle „SalesTable“ aufgerufen werden.
  3. Die Kundennummer zugewiesen werden.
  4. Die Methode „InitFromCustAccount“ der Tabelle „SalesTable“ aufgerufen werden.
  5. Die Methode „Insert“ der Tabelle  „SalesTable“ aufgerufen werden.

Soll für diesem gerade erzeugten Auftrag nun noch eine Artikelposition erzeugt werden, muss im Wesentlichen nur die Methode „CreateLine“ der Tabelle „SalesLine“, mit vorheriger Definition von Auftragsnummer („SalesLine.SalesId“) und Artikelnummer („SalesLine.ItemId“), aufgerufen werden.

Hierzu ein Beispiel:

void createSalesTableAndLine()
{
   AccountNum custAccount = <yourCustAccount>;
   ItemId itemId = <yourItemId>;

   SalesTable salesTable;
   SalesLine salesLine;
   NumberSeq NumberSeq;
   ;
   //Auftragskopf (SalesTable)
   //Neue Auftragsnummer aus Nummernkreis erzeugen
   NumberSeq = NumberSeq::newGetNumFromCode(
   SalesParameters::numRefSalesId().numberSequence);
   salesTable.SalesId = NumberSeq.num();

   //Auftragskopf initialisieren
   salesTable.initValue();
   salesTable.CustAccount = custAccount;

   //Initialisierung der kundenspezifischen Auftragsdaten
   salesTable.initFromCustTable();

   //Auftragskopf erstellen
   salesTable.insert();

   //Auftragsposition (SalesLine)
   salesLine.clear();

   //Zuweisen von Auftragsnummer und Artikelnummer
   salesLine.SalesId = salesTable.SalesId;
   salesLine.ItemId = itemId;

   //Auftragsposition erstellen (ruft SalesLine.insert auf)
   salesLine.createLine(NoYes::Yes, NoYes::Yes, NoYes::Yes, NoYes::Yes,
                        NoYes::Yes, NoYes::Yes);
}

Friday, August 17, 2007 3:47:28 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] - Trackback
 | 

 Wednesday, August 08, 2007

Immer wieder wurde ich darauf angesprochen, ob es nicht möglich sei, die SQL Server Reporting Services Berichte auch ohne Umweg über einen Browser direkt aus dem Dynamics AX 4.0 Menü aufzurufen.

Leider ist dies im Standard nicht möglich.
So habe ich mich entschlossen ein kleines Tool hierfür zu schreiben.

Mit der SSRS Berichtsviewer Erweitung können nun die SQL Server Reporting Services Berichte direkt aus dem Dynamics AX 4.0 Menü geöffnet werden. Dadurch kann eine einfachere und vor allem durchgängigere Benutzung von Berichten innerhalb von Dynamics AX 4.0 gewährleistet werden, da es für den Benutzer keine Unterschiede mehr zwischen dem Aufruf von Dynamics AX 4.0 Berichten und SQL Server Reporting Services Berichten gibt.


Innerhalb von Dynamics AX 4.0 werden die SQL Server Reporting Services Berichte in einem neuen Berichtsviewer gerendert und dargestellt.


Die SSRS Berichtsviewer Erweiterung für Dynamics AX 4.0 - Version 0.1 kann hier herunter geladen werden:
SSRSReportingExtension_V0.1.zip (403,96 KB)

Der Downlaod enthält eine Anleitung zur Verwendung.
Sollten dennoch einige Fragen durch die Anleitung nicht beantwortet werden können, bitte ich diese über die Kommentarfunktion zu stellen. Gleiches gilt auch für Bugs oder Featurewünsche für kommende Versionen.

Wednesday, August 08, 2007 2:53:22 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] - Trackback


 Saturday, August 04, 2007

Manchmal ist es hilfreich beim Debuggen eines Codeblocks zusätzliche Informationen im Debugger auszugeben. 
Dies kann zum Beispiel der aktuelle Wert eines Tabellenfelds sein.

Ebenfalls ist es machmal hilfreich, zusätliche Überprüfungen von Werten einzelner Variablen oder Tabellenfeldern durchzuführen, wenn der jeweilige Codeblock im Debugger ausgeführt wird.

Hierzu ein Beispiel: 

CustTable custTable;
;
while select custTable
{
   //Den Kundennamen im Debuggerfenster ausgeben.
   debug::printDebug(custTable.Name);

   //Information in einem beliebigen Debugger-Info-Tab ausgeben.
   debug::printTab(DebugPrintTab::Method, "Aufruf einer Methode");

   //Eine Überprüfung eines Wertes durchführen (nur im Debug-Mode).
   debug::assert(CustTable.Name != nullValue(CustTable.Name));

   info(custTable.Name);
}

Alle "Debug::" Anweisungen werden nur beachtet/ausgeführt, wenn der Code im Debugger ausgeführt wird (gesetzter Breakpoint). Wird der Code "normal" ausgeführt, wird in dem Beispiel nur die "info()" Anweisung ausgeführt.

Saturday, August 04, 2007 1:55:55 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] - Trackback
 | 

 Tuesday, July 17, 2007

Manchmal ist es notwendig eine Abfrage (Query) über den Quellcode zu manipulieren um dort bspw. Einschränkungen vorzubelegen. Am häufigsten ist mir das in letzter Zeit bei Berichten passiert, es ist aber auch schon vorgekommen das ich komplette Abfragen in Tabellen abgespeichert habe um diese später nochmals benutzen bzw. manipulieren zu können. In beiden Fällen bin ich dabei auf die gleichen Probleme gestossen.

Möchte man Abfragen (bspw. In Berichten) mittels Quellcode manipulieren kann es Aufgrund der Nutzungsdaten zu Problemen in der Anzeige und in der Ausführung der Abfrage kommen. Denn trotz manueller Änderung an der Abfrage werden beim Aufruf des Dialoges der Abfrage noch die Nutzungsdaten des letzten Aufrufes der aktuellen Abfrage genommen/ geladen. Das führt dann dazu das die Veränderungen nicht angezeigt oder beim Ausführen benutzt werden, sondern genau die Einschränkungen/ Einstellungen die auch im Dialog erscheinen.

Um das zu verhindern können mittels der Klasse SysQueryRun noch zusätzliche Einstellungen getroffen werden. Hierzu wird eine neue Instanz von SysQueryRun angelegt, die mit der aktuellen Abfrage initialisiert wird.

Mittels

sysQueryRun.promptLoadLastUsedQuery(false);

wird festgelegt, dass die Nutzungsdaten des letzten Aufrufes nicht vorbelegt bzw. verwendet werden. Somit hat man nun die Möglichkeit die Abfrage mittels Quellcode zu manipulieren, ohne das es hier zu Problemen der Nutzungsdaten kommt, da diese nicht mehr berücksichtigt werden.

Im Bericht kann das beispielsweise so aussehen

public void init()
{
    SysQueryRun sysQueryRun;

    super();

    element.query().dataSourceNo(1).addRange(fieldnum(Tabelle, Feld)).value("NeuerWert");

    sysQueryRun = new SysQueryRun(element.query());

    sysQueryRun.promptLoadLastUsedQuery(false);

    element.queryRun(sysqueryRun);
}

Weitere nette Möglichkeiten bieten noch folgende Methoden
  • sysQueryRun.promptAllowSave(boolean); - speichern der Abfrage erlauben
  • sysQueryRun.promptShowSorting(boolean); - Sortierung anzeigen
  • sysQueryRun.promptAllowAddRange(QueryAllowadd); - Hinzufügen neuer Einschränkungen erlauben
  • sysQueryRun.promptAllowAddSorting(QueryAllowadd); - Hinzufügen neuer Sortierungen erlauben
  • sysQueryRun.promptAllowAddDataSource(boolean); - Hinzufügen neuer Tabellen erlauben#
  • sysQueryRun.promptShowReset(boolean); - Zurücksetzten der Abfrage
  • sysQueryRun.promptSaveQueryPrUser(boolean);

Da SysQueryRun von QueryRun abgeleitet ist, kann SysQueryRun von jeder Standardquery initialisiert werden um dann die erweiterten Funktionen von SysQueryRun nutzen zu können.

Noch eine kleine Besonderheit, die mir bei den Berichten und deren Aufruf aufgefallen ist:
Wird der Bericht direkt (ohne MenuItem) aufgerufen erhält man immer die Originalen inkl. der per Quellcode getroffenen Einschränkungen angezeigt. Wir der Bericht aber über ein MenuItem aufgerufen erhält man die zu letzt vom Benutzer inkl. der per Quellcode getroffenen Einschränkungen/ Einstellungen. Dabei werden die evtl. vorhandenen Einstellung überschrieben, wenn auf vorhandene Element zugriffen wird (findRange (Wert wird überschrieben) anstelle von addRange (alter Wert wird beibehalten und ein neuer hinzugefügt)). Somit gehen in diesem Fall nicht alle vom Benutzer festgelegten Einstellungen verloren.


Tuesday, July 17, 2007 4:21:13 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [1] - Trackback


 Wednesday, July 04, 2007

Microsoft Dynamics AX bietet eine performante Möglichkeit viele Datensätze einzufügen. Hierzu wird die Klasse RecordInsertList verwendet.

RecordInsertList recordList;
CustTable custtable;
;

recordList= new RecordInsertList(tableNum(custtable));

while
{
//TODO: Datensätze erzeugen ohne insert aufzurufen

recordList.add(custtable); //Datensatz der Liste übergeben
}

recordList.insertDatabase();// Datensätze einfügen

Hierbei werden die Datensätze nicht mehr sofort in die Datenbank geschrieben werden, sondern im RecordListInsert Buffer lokal zwischengespeichert. Die dort enthaltenen Datensätze werden spätestens beim Aufruf der Methode insertDatabase() -das Einfügen der Datensätze wird hier vom Kernel gesteuert, der einen geeigeneten Zeitpunkt zum Einfügen auswählt- in die Datenbank geschrieben.

Bei meinem Versuch auf einem VirtualPC habe ich eine Geschwindigkeitsvorteil von ca. 25% erzielt.

Im AXforum.info (eines der größten Dynamics Foren) kann man noch einen Testjob finden, der deutlich macht, was an Zeit eingespart werden kann, wenn mit RecordInsertList gearbeitet wird.

Update

Bei der Instanzierung gibt es noch optionale Parameter wie

  • Insert Methode der Tabelle überspringen [Default=false]
  • Datenbanklog überspringen [Default=false]
  • Alerts überspringen [Default=false]
  • AOS Validierung überspringen [Default=false]

Ist bei der aktuellen Tabelle einer dieser Punkte (Insert Methode, Datenbanklog...) vorhanden, müssen diese dann mittels der Parameter übersprungen werden, sonst wird aus dem Bulk-Insert wieder ein Single-Record-Insert!

Vielen Dank an SebDra für diese Informationen!
Wednesday, July 04, 2007 10:10:54 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [2] - Trackback


 Friday, June 22, 2007

Möchte man alle Datensätze einer Tabelle löschen, kann hierfür der Befehl "Delete_From" verwendet werden.

Dies funktioniert soweit und es ist auch nichts gegen diese "Art" des Löschen einzuwenden.
Wenn allerdings in der Tabelle mehrere millionen Datensätze gespeichert sind, dauert dies schon recht lange (mehrere Stunden).

Um das Löschen aller Datensätze einer Tabelle zu beschleunigen, kann der SQL Server Befehl (Transact SQL) "TRUNCATE TABLE" verwendet werden.

Dieser Befehl erfernt alle Zeilen aus einer Tabelle, ohne die einzelnen Löschungen zu protokollieren. Der "TRUNCATE TABLE" Befehl ist wesentlich schneller und verwendet weniger Systemressourcen als der "Delete" Befehl.

Microsoft Dynamics AX unterstütz diesen Befehl leider nicht direkt.
Somit muss der Aufruf von "TRUNCATE TABLE" über eine ADO-Connection oder in einem der SQL Server Verwaltungs-Tools erfolgen.

Update:
Der "Truncate Table" Befehl ist doch in Dynamics AX implementiert. Und zwar wird er durch die Methode "tableTruncate" der Klasse "SqlDataDictionary" implementiert.

Beispiel zur Verwendung:

SqlDataDictionary sqlDict;
;
sqlDict = new SqlDataDictionary();
sqlDict.tableTruncate(tablenum(SysDataBaseLog));

Weitere Informationen zum "TRUNCATE TABLE" Befehl können über das MSDN bezogen werden.
http://msdn2.microsoft.com/de-de/library/ms177570.aspx

Friday, June 22, 2007 8:59:24 AM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [2] - Trackback
 |  | 

 Tuesday, June 12, 2007
Das man in Microsoft Dynamics AX mittels SYSCompare nicht nur Quellcode oder AOT Objekte, sondern auch Datensätze vergleichen kann zeigt das Tutorial "Tutorial_CompareContextProvider".
Dieses Tutorial veranschaulicht wie man mittels SysCompareContextProvider und SysComparable die Unterschiede herausfindet und darstellt.

Die Darstellung des Datensatzvergleichs ist hier genauso aufgebaut, wie die Darstellung des Quelltextvergleichs.

Ich habe mich mal darangesetzt und versucht diese Funktionalität im gesamten Dynamics AX für jeden Benutzer zu integrieren.
Prinzipiell habe ich mich an dem Tutorial orientiert und es in der Hinsicht verändert, das es nicht nur für die Debitortabelle funktioniert, sondern für jede beliebige Tabelle in jeder beliebigen Maske. Für die Anzeige der Datensätze in der Auswahl, habe ich die Felder TitleField1 und TitleField2 genommen, die an der Tabelle festgelegt werden können.
Wurd keine Auswahl getroffen, wird auch nichts angezeigt.

Die zu vergleichenen Datensätze werden mittels des aktuell aktiven Datensatz in der Maske bestimmt.

Die auszuwählen Datensätze werden anhand der Abfrage der Maske bestimmt. Das heißt das man bei den Auftragsposition in der Aufragsmaske nur die Auftragspositionen für den aktuell ausgewählten Auftrag angezeigt bekommt.

Wird mehr als ein Datensatz markiert, werden nur die markierten Datensätze in der Auswahl sichtbar. Bei zwei markierten Datensätzen können diese sofort über "Vergleichen" verglichen werden.

Der Aufruf der Funktion erfolgt in jeder Maske mittels Shift+Enter. Es können auch mehrere Vergleiche hintereinander aufgerufen werden.

Diese Funktion ist in jeder Maske, auch im Tabellenbrowser verfügbar.


Anwendungsbeispiel

Aufträge



Auftragsmaske: Vergleich der Aufträge. Es werden nur die markierten Datensätze angezeigt


Aufragspositionen


Auftragsmaske: Vergleich der Auftragspostion. Es werden alle Datensätze zum aktuellen Auftrag angezeigt.


Das Ergebniss wird dann in einer neuen Maske angezeigt.


Ergebniss des Datensatzvergleiches



Für die Integration in die Maske musste ich die Klasse SysSetupFromRun überschreiben. Dort habe ich auch festgelegt wie der Datensatzvergleich aufgerufen wird. Leider konnte ich nur bereits vorhandene TaskIds verwenden und habe mich für Shift+Enter entscheiden. Dieses kann natürlich verändert werden, dazu ist nur ein Blick in das Makro Task erforderlich.

Wie immer auch hierzu das komplette Projekt als erste Beta Version (geschrieben in Microsoft Dynamics AX 4.01) als Download.
Labels, Security Keys usw. habe ich hierfür noch nicht angelegt.
Kommentare und Anregungen sind immer gerne willkommen.
(Verwendung auf eigende Gefahr, es wird keine Haftung übernommen)

SharedProject_CompareRecord_Ver_1.0.zip (3,69 KB)


Tuesday, June 12, 2007 8:43:04 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] - Trackback


 Thursday, June 07, 2007

Möchte man die schon gepflegten Daten eines Dynamics AX 3.0 System über exportieren/importieren in ein Dynamics AX 4.0 SP1 System übernehmen, wird bei diesem Vorgang leider eine Fehlermeldung erzeugt, dass ein Importieren in das Dynamics AX 4.0 SP1 System nicht möglich ist, da die Daten aus einem älteren System stammen.

Dies ist soweit auch in Ordnung, da sich Tabellen und Felder von Version 3.x zu Version 4.0 SP1 verändert haben (Namensänderungen, Feldergänzungen, etc.).
Da bei einigen Tabellen aber nur Felder weggenommen wurden, könnte man theoretisch die Daten aus dem „alten“ 3.x System übernehmen. Ein Beispiel hierfür ist der Kontenplan (LedgerTable).

Damit dies funktioniert muss aber eine Änderung an der aus dem Dynamics AX 3.x System exportierten „.def“ Datei vorgenommen werden. Die erste Zeile der „.def“ Datei muss so bearbeitet werden, dass diese wie folgt lautet:

Wenn beim Export als Dateityp „Binär“ gewählt wurde:
"EXPFORMAT VER. 4.01","Binary"

Wenn beim Export als Dateityp „Komma“ gewählt wurde:
"EXPFORMAT VER. 4.01","Comma"

Wurde die „.def“ Datei entsprechend angepasst, kann es zwar sein, dass beim Import Fehlermeldungen über nicht vorhandene Felder erzeugt werden aber der Importvorgang an sich funktioniert nun.

Anzumerken ist nur noch, dass diese Version der „Datenübernahme“ nur für Spezialfälle gewählt werden sollte. Ein Datenupdate, mit den von Microsoft bereitgestellten Tools, sollte vorgezogen werden.

Thursday, June 07, 2007 7:43:25 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [1] - Trackback
 |  | 

 Friday, June 01, 2007
Fragen in Newsgroups bringen mich manchmal auf Ideen. So geschehen heute mit der Frage, wie Methoden automatisch in (Auto)Berichten angezeigt werden können.
Ich habe mir mal die Mühe gemacht, den Aufruf des AutoReports aus den Maske so umzuschreiben, das automatisch Methoden (Display Methoden sind angedacht, z.Z. werden alle Methoden berücksichtigt) in den AutoReport integriert.
Hierfür ist es notwendig, das auf Tabellenebene eine Methode erzeugt wird deren Name mit "AutoReport" beginnt. Selbstverständlich sollte diese Methode auch einen Rückgabewert enthalten, der im Report angezeigt werden kann.
Alle Methoden werden zum Schluss am Ende der Zeile angezeigt, nachdem alle Felder in der Gruppe "AutoReport" gedruckt wurden sind.

Dieses ist vorläufig nur ein erster "Schnellschuss"!

Für den einen oder anderen bestimm nicht uninteressant, da somit eine beliebige Anzahl von Benutzerdefinierten Methoden in den Autobericht gebracht werden kann.

Um diese Funktion zu erreichen habe ich die Maske "SysTableForm" angepasst, da von dort der Aufruf und Aufbau des AutoReports stattfindet.

Möglich Erweiterungen die mir spontan noch einfallen:
  • Parametrisierung der Postion, sodass die Anzeige nicht immer am Ende erfolgt.
  • Erweiterung der Überprüfung der anzuzeigenen Methoden

Getestet habe ich diese Funktion vorerst nur bei Debitoren und Kreditoren.

Verwendung auf eigende Gefahr, es wird keine Haftung übernommen.

Viel Spass damit

Form_SysTableForm_AddMethod2Report.zip (13,8 KB)

 

Der einfacher Weg ist natürlich, die Methoden (das funktioniert scheinbar nur bei Display) per Drag&Drop in die jeweilige Tabellengruppe zu ziehen :) Danke an NC der darauf nochmal aufmerksam gemacht hat, denn mir war das so erstmal noch nicht bewusst.

Friday, June 01, 2007 9:39:04 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] - Trackback


 Sunday, May 27, 2007

Für Microsoft Dynamics AX 4.0 SP1 stehen auf den Microsoft Webseiten 2 PDF Dokumente zur Verfügen, welche die Hardwareanforungen an eine Systemumgebung mit 100 und 200 Benutzern beschreiben.

http://www.microsoft.com/dynamics/ax/product/hardwaresizing.mspx

Man sollte die dort getätigten Aussagen aber eher als eine Art "grundlegende Richtlinie" verstehen, da die realen Hardwareanforderungen eines einzelnen Dynamics AX System, bedingt durch die spezifischen Anpassungen, in einzelnen Punkten variieren können.

Sunday, May 27, 2007 2:30:50 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] - Trackback
 | 

 Thursday, May 24, 2007

Heute wurde in den Newsgroups die Frage gestellt, ob es nicht möglich sein, nach einem XPO-Import einen "automatischen" CompileForward für alle mit dem XPO-File importierten Klassen zu starten.

Ganz automatisch ist dies leider nicht möglich. Es kann aber eine Art "Installationsjob" geschrieben werden, der zusammen mit den entsprechenden Klassen durch das XPO-File weitergegeben und importiert werden kann.

Nach dem Import des XPO-File muss dieser Job nur noch gestartet werden und es werden die frisch importierten Klassen sowie alle von diesen Klassen abgeleiteten Klasse neu kompilert (CompileForward).

Ein solcher "Installtionsjob" könnte wie folgt aussehen:

static void runAfterImport(Args _args)
{
    //CompileForward all imported (base) classes
    ;
    SysCompilerOutput::compileForward(className2Id("SalesFormLetter"));
    SysCompilerOutput::compileForward(className2Id("Your next base class"));
    SysCompilerOutput::compileForward(className2Id("Your next base class"));
}

Ein manuelles Auswählen des CompileForward Menüpunktes einzelner importierter Klassen kann dadurch entfallen.

Thursday, May 24, 2007 8:38:38 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] - Trackback
 | 

 Friday, May 18, 2007

Anfang der Woche hat Microsoft endlich auch die letzte noch fehlende Microsoft Dynamics AX Development Prüfung

MorphX Solution Development for Microsoft Dynamics AX

veröffentlicht. Bei PearsonVue kann diese Prüfung auch schon gebucht werden.

 

Friday, May 18, 2007 10:11:19 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [2] - Trackback


 Monday, May 14, 2007
Dialog zu erzeugen ist einfach und geht in der Regel sehr schnell. Das einzige Problem bei Dialog und deren Controls ist, das man im Dialog keine Möglichkeit hat auf die Benutzereingangen sofort zu reagieren. Nun das stimmt nicht wirklich, denn auch bei Dialogen und deren Controls kann man auf die Methoden, wie Modified oder Valdidate für jede Control innerhalb des Dialoges erzeugen um auf Benutzereingaben reagieren zu können. Leider geht es nur nicht so einfach wie bei den Forms, in der in der Regel schon alle FormControls im Design vorhanden sind und sehr einfach die entsprechenden Methoden direkt hinter dem aktuellem Control zu finden und anzupassen sind.
Bei Dialog ist dies nicht der Fall, so muss man diese Methoden an dem Objekt hinterlegen, welches den Dialog aufruft.

Ein Beispiel

void DialogOeffnen()
{
Dialog meinDialog;
DialogField dCtrl1, dCtrl2;
;
meinDialog = new Dialog("Neuer Dialog", this);

// Feld Kundennummer hinzufügen
dCtrl1 = meinDialog.addField(Typeid(CustAccount));
// Feld Name hinzüfgen
dCtrl2 = meinDialog.addField(typeid(Name));

// Dialog ausführen
meinDialog.run();
}

Um nun auf Benutzereingaben reagieren zu können um Beispielsweise nach Eingabe der Kundennummer(Feld: dCtrl1) den Name des aktuellen Kunden im zweiten Feld (Feld: dCtrl2) zu erhalten, muss für das Feld Kundennummer die Methode "Modified" überschrieben werden.
Hierzu ist es nötig, dem Dialog bzw. dessen Form inkl. FormRun mitzuteilen, das sich diese Methoden im aktuellen Objekt und nicht im Dialog befinden, denn im Dialog haben wir keinen einfluss.
Das erreicht man mit

meinDialog.doInit();

meinDialog.formRun().controlMethodOverload(true);
meinDialog.formRun().controlMethodOverloadObject(this);

Die Methode "doInit" ist notwendig um FormRun im Dialog zu initialiseren, wird dieses versäumt kommt es zum Laufzeitfehler.

Alle wichtigen Dinge sind nun getan, bis auf die Definition der Methode für das Feld "Kundennummer". Leider kann man den Namen des Feldes bei DialogField nicht manipulieren und muss hier mit den automatisch generierten Controlnamen leben. Da dieses aber immer nach dem gleichen Schema passiert ist das nicht wirklich tragisch. Bei Feldern wird der Name immer wie folgt aussehen


fld + Feldnummer + _ + ArrayIdx

Die Feldnummer kann man in der Regel einfach ermitteln: Das erste Feld hat die Feldnummer 1, das zweite Feld die Feldnummer 2, usw.
Den ArrayIdx hab ich bisher auch nur als 1 gesehen.
Das kann sich natürlich immer anhand der Komplexität verändern.

Zu guter Letzt fehlt noch die Methode "Modified" die die gewünschten Änderungen beinhaltet.

public boolean fld1_1_modified()
{
FormStringControl c = dialog.formrun().controlCallingMethod(); // Die FormControl, von der der Aufruf erfolgt
boolean ret;
;
ret = c.modified(); // Super() der aktuellen FormControl aufrufen ->modified

dControl2.value(CustTable::find(dControl1.value()).Name);
dControl2.enabled(false);
return ret;
}

Eine einfach Klasse die genau dieses auch macht kann man von hier runterladen.

Class_DialogControlMethodOverload.zip (,91 KB)
Monday, May 14, 2007 1:20:27 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [1] - Trackback


 Friday, April 27, 2007

Wie z.B. aus Visual Studio bekannt kann in vielen IDE's der Ausführungsmodus des Quellcodes bestimmt und gewechselt werden.
Beispiele hierfür sind Debugmode oder Releasemode.

Wenn nun im Quelltext auf den Ausführungsmodus abgefragt wird, so kann zum Beispiel im Debugmode erweiterter Quellcode mit in ein Programm kompiliert werden, welcher beispielsweise verschiedene Loggingarten implementiert um eine bessere Problemanalyse durchführen zu können. 
Dies läßt sich auch mit Microsoft Dynamics AX machen.

Als erstes muss ein neues Macro erstellt werden (in diesem Beispiel mit dem Namen RunningMode), welches als globaler Schalter zwischen den einzenen Ausführungsarten eingesetzt wird:

/* Macro for setting the running Mode. */

/* Activate for debug mode. */
#define.Debug('true')

/* Activate for production mode. */
//#define.Debug('')

Jetzt kann an beliebiger Stelle im Quellcode #Debug verwendet werden um auf den Ausführungsmodus zu prüfen: 

static void TestTheRunningMode(Args _args)
{
    #RunningMode
    ;
    print "Code der immer ausgeführt werden soll"
    if(#Debug)
    {
        print "Code der nur im Debug-Mode ausgeführt wird"
    }
    pause;
 

Abhängig davon, wie im Macro RunningMode #Debug definiert wurde, kann nun beliebiger Quellcode aktiviert (ausgeführt) oder deaktiviert (nicht ausgeführt) werden.

Friday, April 27, 2007 6:04:10 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] - Trackback
 | 

 Wednesday, April 25, 2007

Möchte man eine Methode der Klasse Info anpassen oder erweitern, können dabei unerklärliche Fehler auftreten.

Soll zum Beispiel die Methode open(FormRun formRun) erweitert werden und man verwendet hierbei eine Variable die in der classDeclaration deklariert ist, erhält man spätestens zur Laufzeit eine Fehlermeldung (dies sogar bei fehlerfreiem Code, keine Fehlermeldung im Debugger).

Grund hierfür ist, dass in Dynamics AX 4.0 die Klasse Info immer nur beim Öffnen des Clients erzeugt wird und man somit, egal ob die Klasse neu kompiliert wurde oder nicht, immer noch mit Teilen der alten Klassenversion arbeitet.

Um die neue Version der angepassten Info Klasse aufzurufen muss der Client geschlossen und wieder neu geöffnet werden.
Erst dann funktioniert die Anpassung wie gewünscht.

Das geschilderte Verhalten kann bei:

1. Erstellen von neuen Methoden
2. Deklarieren einer Variablen in der classDeclaration und Verwendung dieser in einer anderen Methode

allerdings nicht bei:

1. Anpassungen, die nur innerhalb einer einzelnen Methode durchgeführt werden

beobachtet werden.

Wednesday, April 25, 2007 9:57:04 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] - Trackback
 | 

 Thursday, April 19, 2007

Eigendlich wollte ich hier noch die erweiterten Möglichkeiten bei den Einschränkungen zum Besten geben, das kann man aber schon alles fix und fertig bei Axaptapedia nachlesen.

Daher vorerst nur noch die eine Sache die ich noch beschreiben möchte:

Möchte man eine Einschränkung auf einen Array Feld (die Dimension: Abteilung, Kostenstelle, Kostenträger sind hierfür ein gutes Beispiel) definieren kann diese wie folgt erstellt werden.

queryRange = queryDS.addRange(fieldid2ext(fieldnum(InventTable, Dimension), 1));

Das erstellen der Range bleibt im Grunde gleich, nur das jetzt mittels fieldid2ext noch der Array Index angegeben wird. Der Index beginnt in Microsoft Dynamics Ax immer mit 1.

Da nun diese Sache hier nicht mehr behandelt wird, möchte ich noch eine komplett andere Möglichkeit zeigen Datensätze aus der Datenbank zu holen um diese beispielsweise in Masken anzuzeigen. Anstelle einer Query kann man auch SQL Anweisungen verwenden.


Das wird ein bisschen Tricky...
Ich fange aber heute nur "einfach" an.

Zuerst bauen wir uns einen neue Form und nennen diese ArtikelSQL (Der Name ist hier eigendlich egal, ich habe mich aber für eine Artikelmaske entschieden).
Dann legen wir die InventTable mittels Drag&Drop als Datasource fest. Zum Abschluss zeigen wir noch ein paar Feld (Artikelnummer und Artikelname reicht erstmal aus) über ein neu hinzugefügtes Grid in dieser Maske an. Fertig - beim Aufruf der Maske erhalten wir alle Datensätze die sich in der Tabelle InventTable befinden.

Als nächsten Schritt definieren wir eine Variable in der Classdeclaration

Source SQLAnweisung;

In dieser Variable schreiben wir später unsere SQL Anweisung. Bevor das jedoch gemacht wird, muss als nächstes noch die Methode executeQuery auf der DataSource überschrieben werden.

void executeQuery()
{
    if (SQLAnweisung)
    {
        runbuf(SQLAnweisung, this.cursor());
    }
    else
    {
        super();
    }
}


Neu  hinzugekommen ist hier die IF Abfrage, die prüft ob eine SQLAnweisung erstellt wurde, bzw. ob die Variable SQLAnweisung nicht leer ist. Wurde  keine SQLAnweisung getroffen, wird die Standardquery auf der DataSource ausgeführt. Wurde aber eine SQLAnweisung getroffen wird diese nun Anstelle der Query ausgeführt.

Zu guter letzt fehlt nur noch die SQLAnweisung, die erstellt werden muss.

void initSQLAnweisung()
{
XppCompiler compiler = new XppCompiler();
DictTable dictTable= new DictTable(Tablenum(I