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.
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.
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.
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)
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.
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.
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(InventTAble)); str SELECTAnweisung = 'SELECT * FROM '+dictTable.name();
SQLAnweisung = 'void SQLSTMT('+dictTable.name()+' '+dictTable.name()+')\n{\n'+SELECTAnweisung+';\n}\n'; if (!compiler.compile(SQLAnweisung)) { setprefix("@SYS57538"); info (SQLAnweisung); error (compiler.errorText()); SQLAnweisung = ''; <> } }Diese Methode erstellt die SQL Anweisung. Es reicht nicht aus einfach ein SELECT Statement zu schreiben, da der Compiler das nicht versteht. Anstelle eines einzelenen SELECT Statement schreibt man einfach eine Methode drumherum, die dann über runbuf ausgeführt wird. Das eigendliche SELECT Statement sieht wie folgt aus und wird gleich der Variable SELECTANweisung zugewiesen. SELECT * FROM InventTable
Über diese einfache Select Anweisung erhalten wir alle Datensätze aus der Tabelle InventTable. Auf den ersten Blick ist es nicht einfach zu erkenne aber aus 'void SQLSTMT('+dictTable.name()+' '+dictTable.name()+')\n{\n'+SELECTAnweisung+';\n}\n';
wird in der lesbaren Ansicht
void SQLSTMT(InventTable InventTable) { SELECT * FROM InventTable; }
und der globalen Variablen SQLAnweisung zugewiesen. Nur um sicher zugehen ob alles richtig geschrieben wurden und keine Kompilierungsfehler enthalten sind, wird diese Anweisung noch dem Compiler übergeben und kompiliert.
compiler.compile(SQLAnweisung)
Wurden keine Fehler gefunden ist alles Prima, ansonsten wird die Variable noch gelöscht um beim Ausführen der Methode executeQuery keine Fehler zu erhalten. Die Ausgabe der Fehlermeldung vom Compiler ist optional.
Der Aufruf der Methode "initSQLAnweisung" Methode erfolgt noch dem super() in der Methode init der Maske.
Durch die SQL Anweisung hat man den Vorteil auf eine sehr einfache Art&Weise sehr komplexe Abfragen gestalten zu können. Einige Nachteile gibt es leider auch, die Standardfunktionen wie Filtern oder Sortieren funktionieren im genannten Beispiel nicht mehr.
Das komplette Beispiel noch als xpo zum Download. Wie immer wurde auch dieses Beispiel in Microsoft Dynamics Ax 4.0 erstellt. Form_ArtikelSQL.zip (1.08 KB)
In Microsoft Dynamics AX werden alle Benutzerberechtigungen über SecurityKeys gesteuert. SecurityKeys können für Forms, Formcontrols, Tables, Tablefields, MenuItems, etc. in deren Eigenschaften hinterlegt werden.
Für Klassen ist dies zwar mit Hilfe eines MenuItems und der Implementation der Methode "static void Main(Args _args)" ebenfalls möglich, doch kann für eine einzelne Methode kein SecurityKey vergeben werden.
Es gibt allerdings Situationen wo die Codeausführung, abhängig von der jeweiligen Berechtigung des Benutzers, gesteuert werden soll/muss. In einem solchen Fall muss im X++ Code eine Überprüfung der Berechtigungen des Benutzers durchgeführt werden.
Möchte man prüfen, ob ein Benutzer Zugriff auf einen SecurityKey hat, kann dies mit der Methode hasSecurityAccess erfolgen.
if ( hasSecurityKeyAccess(securitykeyNum(CustSetup), AccessType::View) ) { //Code ausführen, wenn entsprechende Berechtigung vorhanden ist. }
Möchte man prüfen, ob ein Benutzer Zugriff auf eine Tabelle hat, geht dies mit der Methode hasTableAccess.
if ( hasTableAccess(tablenum(CustTable), AccessType::Edit) ) { //Code ausführen, wenn entsprechende Berechtigung vorhanden ist. }
Muss nicht nur die Tabelle, sondern auch ein einzelnes Feld überprüft werden, kann dies mit der Methode hasFieldAccess gemacht werden.
if ( hasFieldAccess(tablenum(CustTable), fieldnum(CustTable, AccountNum), AccessType::Delete) ) { //Code ausführen, wenn entsprechende Berechtigung vorhanden ist. }
Der Parameter AccessType bestimmt hierbei auf welche Berechtigung das jeweilige Element gepürft wird (Kein Zugriff, Anzeigen, Bearbeiten, Erstellen, Vollständige Kontrolle).
Alle Methoden (hasSecurityAccess, hasTableAccess, hasFieldAccess) sind globale Methoden, die in der Klasse Global definiert sind. Somit können diese Methoden an jeder beliebigen Stelle im Quellcode verwendet werden. Es ist egal ob es sich um die Methode einer Form, Datasource, Klasse oder Tabelle handelt.
Nachdem nun in diesem Artikel schon die ersten Schritte unternommen und vorhandene Elemente manipuliert wurden, werden nun auch neue Elemente hinzuzugefügt.
Einfügen einer neuen Formcontrol im Design.
FormRun fr; Args args = new ARgs(); FormStringControl fsc; ; args.name(formstr(InventTable)); fr = new FormRun(args); fr.init(); // Neue Control (Tabellenfeld "AltItemID") im Design anfügen fsc = fr.design().addDataField(fr.dataSource(1).id(),fieldnum(InventTAble, altitemid)); fr.run(); fr.wait();
In dem o.g. Beispiel wird das Feld Ersatz-Artikelnummer direkt im Design der Form eingefügt. Bei einem Datenfeld ist es wichtig das als Datasource immer die ID der FormDatasource übergeben wird um die richtige Referenz zu erhalten. Anstelle eines Datenfeldes kann natürlich auch jedes andere Formcontrol eingefügt werden, was wiederrum anderen Möglichkeiten der weiteren Verwendung bieten kann. Möchte man kein Datenfeld erzugen kann mit
.addControl(... auch ein beliebiges FormControl hinzugefügt werden. Über den Enum FormControlType wird die Art des Controls in Methode addControl als erstes Parameter festgelegt.
element.design().addControl(FormControltype::String, "Neue Control");
Um das Feld Ersatz-Artikelnummer Beispieltsweise im Überblick - Grid anzuzeigen muss die Erzeugung der FormControl auch auf dem Grid geschehen.
Hierzu wird die FormGridControl „Grid“ aus der Maske gesucht und dort das neue Feld hinzugefügt.
FormRun fr; Args args = new ARgs(); FormGridControl fgc; ; args.name(formstr(InventTable)); fr = new FormRun(args); fr.init(); // Zugriff auf FormControl "Grid" aus dem Design fgc = fr.design().controlName("Grid"); // Dem Grid ein neues Control hinzufügen fgc.addDataField(fr.dataSource(1).id(),fieldnum(InventTAble, altitemid)); fr.run(); fr.wait();
Eine neues FormControl wird also immer vom Vater (Übergeordneten) Control aus erzeugt, im zweifelsfall also mindestens auf dem Design der Form.
parentControl.addControl(…
es muss immer die FormControl gefunden werden auf dem die FormControl erzeugt werden soll. Neue FormControls können nur auf Container FormControls, wie Grid, TabPage, Group usw. erzeugt werden.
Über X++ können alle Eigenschaften der FormControls manipuliert werden. Je nach ControlTyp stehen unterschiedliche Eigenschaften zur Verfügung.
FormGroupControl groupCtrl; FormStringControl stringCtrl; ; //Neue Gruppe im Design einfügen groupCtrl = element.design().addControl(FormControlType::Group, "Gruppe"); //Neue Zeichenfolge der Gruppehinzufügen stringCtrl = groupCtrl.addControl(FormControlType::String, "ZeichenFolge"); //Zuweisung der DataSource stringCtrl.dataSource(element.dataSource().name()); //Zuweisung des Feldes // stringCtrl.dataField(fieldnum(InventtAble, ItemID)); //Zuweisung einer Methode der Tabelle stringCtrl.dataMethod("ItemName");
stringCtrl.enabled(false); stringCtrl.visible(true);
In dem Beispiel wird eine Gruppe direkt im Design erzeugt und in dieser Gruppe wird ein Zeichenfolge eingefügt, die über die Methode ItemName auf der InventTable Daten anzeigt.
Überschreiben der Standardevents ausserhalb der Form
Auch Events wie Modified oder Validate können ausserhalb der Form überschrieben werden. Hierzu muss der aufgerufenden Form mitgeteilt werden, das dieses nicht mehr in der Maske passieren soll, sondern in dem aufrufenden Element (zb. In der Class).
// Das Überschreiben der Methoden erlauben fR.controlMethodOverload(true); // Das Objekt festlegen in der die Methoden definiert sind fR.controlMethodOverloadObject(this);
Nun kann man nicht pauschal für alle Controls eine Methode definieren, sondern muss für jede Control eine eigende Definition erzeugen. Die Syntax ist aber immer die gleiche und fängt mit der Benamung der Methode an. D.h. wurden 10 FormControls zur Laufzeit eingefügt müssen für jeden Event der verändert werden soll eine neue Methode erzeugt werden. Eine Ausnahme, die aber oft zur Regel werden kann, ist das nur für jeden unterschiedliche Namen der FormControls zusätzliche Methode erzeugt werden müssen.
ControlName_MethodenName
Als Beispiel kann die Control ctrlInventTable_ItemId (Artikelnummer im Reiter Übersicht) wie folgt aussehen um die Änderung auf diesem Feld abzufragen
Public boolean ctrlInventTable_ItemID_modified() { FormStringControl c = formrun.controlCallingMethod(); // Die FormControl, von der der Aufruf erfolgt boolean ret; ; ret = c.modified(); // Super() der aktuellen FormControl aufrufen ->modified
return ret; }
Oben genannte Methode entspricht der Standard Methode "modified" auf der FormControl "ctrlInventTable_ItemID"
public boolean modified() { boolean ret;
ret = super();
return ret; } In den og. Fall hätte man sich das Überschreiben der Methode Modified sparen können, da nichts gemacht wird als die Standardfunktion aus Dynamics AX aufzurufen. Es werden aber alle wichtigen Elemente dargestellt um einen ordnungsgemäße Funktionalität zu gewährleisten.
Achtung: Gibt es mehrere Controls in der Form die dieselben Namen haben, werden auch die überschriebenen Methoden von all diesen Controls benutzt! Somit muss die Unterschreidung welche Control das Event gerade ausgelöst hat hier abgefragt werden. Es können aber nur Controls die zur Laufzeit per X++ erzeugt wurden den gleichen Namen erhalten. Beim "normalen" Erstellen einer Form kann das nicht vorkommen...
Ab der Version 4.0 von Dynamics AX ist es möglich, an einem AOS, die Neuanmeldung von Benutzern zu sperren.
Um Benutzern die Anmeldung an einem AOS zu untersagen muss der entsprechende AOS auf dem Reiter „Serverinstanzen“ der Maske „Onlinebenutzer“ angewählt werden und der Button „Neue Clients ablehnen“ betätigt werden. Hierbei wird der AOS in den Status „Belastung“ geschaltet. Ab diesem Zeitpunkt akzeptiert dieser AOS keine Neuanmeldungen mehr.
Mit dem Button „Neue Clients akzeptieren“ kann dies Rückgängig gemacht werden.
Der AOS wird in den Status „Aktiv“ gesetzt.
Allerdings bringt das Untersagen von Neuanmeldungen in einer Dynamics AX Umgebung mit nur einem AOS auch einige Gefahren mit sich.
Wird der Client bzw. die Benutzersitzung des Benutzers mit administrativer Berechtigung geschlossen, und hat dieser keine weiteren aktiven Sitzungen mit ebenfalls administrativen Berechtigungen geöffnet, so hat sich der Administrator selbst vom System ausgesperrt. Die Sperrung von Neuanmeldungen kann nun nicht mehr rückgängig gemacht werden.
Es ist dann nur noch möglich, den AOS neu zu starten um sich wieder anmelden zu können, da bei einem Neustart eines AOS dessen Status automatisch wieder auf „Aktiv“ gesetzt wird.
Das man in Microsoft Dynamics Ax mittels Drag&Drop einfach Tabellenfelder (Fields) oder Tabellenfeldgruppen (Fieldgroups) in Masken (Forms) einfügen kann ist allgemein bekannt. Mittels Tabellenfeldgruppen können die gewünschten Felder in die Masken integriert werden. Dadurch ist das Hinzufügen oder Entfernen der Tabellenfelder auf einfache Art Zentral auf Tabellenebene möglich, ohne die Maske anpassen zu müssen. Was für mich bis dato noch neu war: Es ist auch möglich auf einem Grid eine Tabellenfeldgruppe zu hinterlegen und direkt diesem FormControl alle Felder, die der Tabellenfeldgruppe hinterlegt wurden, zuzuweisen. Microsoft hat in der aktuellen Version von Microsoft Dynamics Ax 4.0 damit auch schon angefangen, dieses in den Forms aktiv zu nutzen. Bisher ist mir das zumindest noch nicht aufgefallen. In den Masken zur Adressverwaltung ("Address...") werden hier nun auf dem Reiter "Überblick" die Felder mittels Feldgruppe auf dem Grid hinzugefügt (bsp: Form "AddressCountryRegion") oder es wird die Tabellenfeldgruppe direkt dem Grid zugeordnet (bsp: Form "AddressZipCodes"). Die Zuordnung erfolgt immer über die Eigenschaft "DataGroup". Die Eigenschaft "DataSource" muss selbstverständlich auch hierzu vorher gefüllt werden um dann mittels Lookup in der Eigenschaft "DataGroup" eine Auswahl auf alle Tabellenfeldgruppen der aktuell ausgewählten Tabelle zu erhalten. Eigenschaft der FormGridControl "Grid"
Nach Auswahl der DataGroup werden alle Felder automatisch dem aktuellen Objekt zugeordnet. FormGridControl erhält nach Zuweisung der Eigenschaft "DataGroup" alle Felder der ausgewählten Feldgruppe
Man muss aber beachten, das ein manuelles hinzufügen von Feldern nicht mehr funktioniert. D.h. im FormDesigner sieht alles prima aus, alle Felder, auch die manuell hinzugefügten, werden angezeigt, beim Aufruf der Maske sind aber nur die Felder sichtbar, die auch in der aktuellen Tabellenfeldgruppe hinterlegt wurden. Wird jedoch die Eigenschaft AutoDataGroup auf "Yes" gesetzt ist ein manuelles hinzufügen von Elementen nicht mehr möglich! Auch bereits vorhandene Elemente werde, sofern manuell hinzugefügt und nicht in der aktuellen Feldgruppe hinterlegt, wieder entfernt. Wird eine Feldgruppe mittels Drag&Drop in der Form hinzugefügt, ist die Eigenschaft AutoDataGroup schon standardmäßig auf "Yes" gesetzt. Es gilt aber immer: Sobald die DataGroup befüllt ist, werden alle Elemente die nicht der aktuellen Feldgruppe auf der Tabelle zugeordnet wurde nicht mehr angezeigt/ berücksichtigt. Meiner Meinung wieder ein Schritt in die richtige Richtung, denn dadurch lassen sich Anpassungen an einer Form weiter minimieren. Das kann wieder einen verminderten Anpassungsaufwand, speziell bei Upgrades bedeuten, da bei Masken meiner Erfahrung nach mit die größte Zeit aufgewendet werden muss.
Wie schon in diesem Artikel beschrieben ist es auch unter Dynamics AX 4.0 möglich, den Text der Titelleiste zu verändern.
Hierzu ein kurzes Beispiel:
void workspaceWindowCreated(int _hWnd) { // Put workspace window specific initialization here. str orgTitleBarText, newTitleBarText; int posBracket, lenTitle; ; //Show the configuration file name in the titlebar - START
//Without session id: orgTitleBarText = WinAPI::getWindowText(_hWnd); lenTitle = strLen(orgTitleBarText); posBracket = strScan(orgTitleBarText, "[", 1, lenTitle);
newTitleBarText = subStr(orgTitleBarText, 1, posBracket); newTitleBarText = strfmt("%1%2]", newTitleBarText, xInfo::configuration());
WinAPI::setWindowText(_hWnd, newTitleBarText);
//Show the configuration file name in the titlebar - END }
Die Anpassung der Methode "workspaceWindowCreated" der Klasse "Info" liefert folgenden Text in der Titelleiste:

Hierbei wird der Name der Dynamics AX Client Configuration, die für diese Session verwendet, wird innerhalb der eckigen Klammern angezeigt.
Es gibt Tage da wundert man sich über Dinge mit denen man alltäglich zu tun hat... So geschehen mit QueryRanges und deren QueryValues...
Beim Definieren einer QueryRange ist darauf zu achten, dass die QueryValue als Datentyp der QueryRange festgelegt wird. Wird beim Filtern auf einem Preis (Real) ein String als Kriterium festgelegt erhält man je nachdem ob dieser String in einen gültigen Realwert gewandelt werden konnte unterschiedliche Resultate. Möglicherweise werden, wenn ein ungültiges Kriterium festgelegt wurde, alle Datensätze zurückgegeben.
Unter Microsoft Dynamics AX 3.0 war es recht einfach den Text in der Titelleiste zu verändern. Dies war sogar auf mehere verschiedene Arten möglich. Unter Dynamics AX 4.0 funktionieren diese bekannten Methoden leider nicht mehr.
Allerdings existiert unter Dyanamics AX 4.0 eine neue Möglichkeit den Text in der Titelleiste zu verändert. Hierfür ist es nur erforderlich die Methode "workspaceWindowCreated" der Klasse "Info" zu überschreiben.
Wie dies genau gemacht werden kann, ist in dem Artikel Configuration in title bar auf Axaptapedia beschrieben.
Der Umgang mit Queries ist auch in Microsoft Dynamics AX sehr einfach, sofern man mit den Objekten vertraut ist. Angefangen mit einem einfachen Query, der kann entweder auf Basis eines im AOT (Query) definierten Querys erzeugt werden oder auch komplett neu mit x++ erzeugt werden kann.
Folgende Okjekte werden verwendet
Query (Abfrage) QueryRun (führt die Abfrage aus) QueryBuildDataSource (DataSource in der Abfrage) QueryBuildRange (Range (Einschränkung) auf der DataSource)
Kurzes Beispiel:
Das Ergebniss ist die Ausgabe alle Artikel bei denen die Artikelgruppe "Teile" hinterlegt wurde.
Auf Basis eines im AOT definierten Queries
//Ein neues Query Objekt, verwendet wird die Query (Im AOT) "InventTable" Query queryInventTable = new Query(querystr(InventTable));
QueryRun queryRun; QueryBuildDataSource queryDS; QueryBuildRange queryRange; InventTable inventTable; ; // Datasource zuordnen queryDS = queryInventTable.dataSourceTable(tablenum(InventTable));
// Prüfen ob Range schon vorhanden if (queryDS.findRange(fieldnum(InventTable, ItemGroupID))) queryRange = queryDS.findRange(fieldnum(InventTable, ItemGroupID)); else queryRange = queryDS.addRange(fieldnum(InventTable, ItemGroupID));
queryRange.value("Teile");
// queryRun mit dem aktuell neu erstellen query auf Basis des Queries "InventTable" erzeugen queryRun = new QueryRun(queryInventTable);
// alle Datensätze ausgeben while (queryRun.next()) { inventTable = queryRun.get(tablenum(InventTable)); print InventTable.ItemID; } pause;
Komplett in X++ definierte Query
//Ein neues leeres Query Objekt Query query = new Query(); QueryRun queryRun; QueryBuildDataSource queryDS; QueryBuildRange queryRange;
InventTable inventTable; ; // Datasource (Tabelle InventTable) hinzufügen queryDS = query.addDataSource(tablenum(InventTable));
// Range definineren queryRange = queryDS.addRange(fieldnum(InventTable, ItemGroupID)); queryRange.value("Teile");
// alternative // Range definineren query.dataSourceTable(tablenum(InventTable)).addRange(fieldnum(InventTable, ItemGroupID)).value("Teile");
// queryRun mit dem aktuell neu erstellen query erzeugen queryRun = new QueryRun(query);
// alle Datensätze ausgeben while (queryRun.next()) { inventTable = queryRun.get(tablenum(InventTable)); print InventTable.ItemID; }
Bei diesem einfachen Query macht es keinen Unterschied ob im AOT definiertes Query verwendet wird, oder ob man die definition komplett in X++ vornimmt. Benutzt man ein Query das schon irgendwo definiert wurde und möchte dieses verwenden, sollte man immer auf schon vorhandene Objekte (siehe findRange) zurückgegriffen werden sofern schon vorhanden.Ansonsten fügt man immer wieder dasselbe Objekt hinzu, bei Ranges hat das dann zufolge, das in der Abfrage nicht mehr auf die aktuell definierte Range zurückgegriffen wird, sondern auf alle davor definieren Ranges ebenfalls. Das kann sehr gut in dem unterem Beispiel getestet werden, indem man einfach eine neue Range auf demselben Feld wie schon zuvor definiert hinzufügt und eine anderen Wert festlegt.
Das Ergebnis ist die Ausgabe alle Artikel bei denen die Artikelgruppe "Teile" und "Lampen" hinterlegt wurde.
// Range definineren queryRange = queryDS.addRange(fieldnum(InventTable, ItemGroupID)); queryRange.value("Teile");
// Neue Range auf demselben Feld. ein andere Wert wird festgelegt queryRange = queryDS.addRange(fieldnum(InventTable, ItemGroupID)); queryRange.value("Lampen");
Der Zugriff auf einzelne Elemente (wie DataSource oder Range) der Query ist jeweils immer gleich. Bei der DataSource sollte immer beachtet werden, dass es ggf. mehrere Objekte vom selben Typ (Tabelle) geben könnte. Ein gutes Beispiel hierfür sind wieder die Artikel (InventTable) mit den drei verknüpften Lagermodulparameter (InventTableModule). Wir hierbei der Zugriff auf die DataSource über tablenum gesteuert erhält man immer ein und dieselbe DataSource und nicht wie evtl gewünscht alle drei DataSources. Der Zugriff sollte dann über den DataSource-Namen erfolgen, der immer eindeutig ist, sogar dann wenn das hinzufügen per x++ geschieht.
Hinzufügen von drei neuen DataSources und der Zugriff auf jeder DataSource der aktuellen Abfragen, inkl. Ausgabe des Namen
Query query = new Query(); counter dsCount; ; // Datasource (Tabelle InventTable) hinzufügen queryDSInvent= query.addDataSource(tablenum(InventTable));
//neue relation auf Lagermodulparameter (3mal) queryDSInvent.addDataSource(tablenum(InventTableModule)).relations(true); queryDSInvent.addDataSource(tablenum(InventTableModule)).relations(true); queryDSInvent.addDataSource(tablenum(InventTableModule)).relations(true);
/ Ausgabe aller DataSources die im aktuellen Query vorhanden sind for (dsCount = 1; dsCount <= query.dataSourceCount(); dsCount++) { print query.dataSourceNo(dsCount).name(); } pause;
Wird der DataSource kein Name zugewiesen erzeugt Dynamics AX automatisch einen eindeutigen Namen (Tabellenname_Zähler).
Hinterlegt man nun noch bei den LagerModulparametern eine Range (Verkauf, Einkauf, Lager) auf den Lagertyp hat man im groben die Standardquery (es fehlt noch die Lagerortverwaltung Tabelle) der Artikelmaske nachgebaut.
// Lager queryDS = queryDSInvent.addDataSource(tablenum(InventTableModule)); queryDS.relations(true); queryDS.joinMode(JoinMode::InnerJoin); queryRange = queryDs.addRange(fieldnum(InventTableModule,ModuleType)); queryRange.value(queryValue(ModuleInventPurchSales::Invent));
Prinzipiell kann man mit dieser Query auch die Query auf der Maske InventTable überladen. Diee Anzeige der Artikel klappt weiterhin wunderbar nur die Referenzen auf die zusätzlichen FormDataSources geht hierbei verloren. Dadurch funktioniert die Anzeige und Bearbeitung der Daten aus diesen FormDataSources dann nicht mehr.
Spasseshalber aber noch der Quellcode, der auch die Query der Form überlädt. Job_QueryEinfachFormRun.xpo (3,05 KB)
Viele Wege führen nach Rom, so auch in Microsoft Dynamics AX beim Anpassen der Masken (Forms).
Eine Form direkt anzupassen kann auf den ersten Blick immer ein einfachere Weg sein, nur sollte man bedenken das es hier bei einem Update mit die meißten Schwierigkeiten bzw. die meißte Arbeit geben kann. Dabei kann man fast jede Anpassung an der Maske auch über X++ steuern.
Hier ein paar einfache Beispiele dafür:
Manipulation der Datasource.
FormRun fr; Args args = new Args(); FormDataSource fds; ; // Form Name InventTable args.name(formstr(InventTable)); fr = new FormRun(args); // init der Form fr.init(); // Datasource der Form fds = fr.dataSource(1); // Neue Range auf der Datasource erzeugen fds.query().dataSourceTable(tablenuM(InventTable)).addRange(fieldnum(InventTable, itemID)).value(“Wert”); fr.run(); fr.wait();
In dem o.g. Beispiel wird eine neue Range auf der Datasource „InventTable“ erzeugt, ohne einen direkten Eingriff auf der Form zu machen.
Weitere Möglichkeiten der Datasource Manipulation
// Datasource darf nicht editierbar sein fds = fr.dataSource(1); fds.allowEdit(False);
Prinzipiell kann über X++ alles manipuliert werden, was auch direkt in der Form manipuliert werden kann.
Der Zugriff auf einzelne Felder der Datasource erfolgt über die Methode object(), es muss nur noch die Kennung des Objektes übergeben (Feldnummer) um Zugriff zu erhalten.
// Artikelnummer ausblenden fds = fr.dataSource(1); fds.object(fieldnum(InventTable, ItemID)).visible(false);
Man erhält den vollen Zugriff auf die Eigenschaften und kann nach belieben die Objekte verändern. Das einzige was zusätzlich noch gemacht werden muss, ist den Aufruf der Maske zu verändern. So kann ein neues MenuItem erstellt werden, welches das Ursprüngliche MenuItem ersetzt oder es wird einfach das MenuItem verändert. So wird anstelle der Maske eine Klasse aufgerufen, die die Manipulation an der Maske vornimmt. Performanceeinbußen habe ich noch nicht oder nur im geringen Maße feststellen können.
Die einzigen wirklichen Probleme die ich bei solchen Formanpassungen hatte, war das Einfügen einer neuen Formdatasource. Man kann die FormDatasource zur Laufzeit in die Form einfügen, leider muss die Form aber geschlossen und wieder neu aufgerufen werden um Zugriff auf die Formdatasource zu erhalten und um diese dann im Design der Form nutzen zu können. Bei manchen Forms kam es beim Neuaufruf der Form zu sehr seltsamen Verhalten, wie zum Beispiel, das Formcontrols die automatisch Deklariert wurden (Autodeclaration = Yes) ihre Wertigkeit verlieren. So wurde bespielsweise aus einer FormStringControl eine FormDataSource. Hier ist also bei der Anwendung Vorsicht geboten.
Anbei noch ein Job der die Kreditorenmaske manipuliert. Es wird hier eine neue Datasource (Bestellungen) angefügt und in einem neuen Grid angezeigt. Seltsamerweiße klappt dieses Vorgehen mit der Kreditormaske sehr gut, bei den Debitoren bekam ich nur o.g. Fehlermeldungen bei Aufruf.
Der ursprünglich Link scheint nicht zu funktionieren. Hier nochmal ein neuer Versuch
Job_MaskeKreditorBestellung.xpo (2,52 KB)Und wenn das auch nicht funktionieren sollte, nochmal als Text...
// Changed on 15 Mär 2007 at 21:14:18 by jinx (starside.eu) // Manipulation des Aufrufes der Maske Kreditoren static void MaskeKreditorBestellung(Args _args) { FormRun fr; Args args = new Args(); FormRun neuformRun; FormBuildDataSource formBuildDataSource; FormGridControl fgc; FormGroupControl fGroupCtrl; ; // Form Name VendTable -> Kreditoren args.name(formstr(VendTable)); fr = new FormRun(args);
// Neue DataSource einfügen formBuildDataSource = fr.form().addDataSource("PurchTable"); // Tabelle der Datasource zuordnen formBuildDataSource.table(tablenum(PurchTable)); // Datasource mit der Tabelle "VendTable" verknüpfen // Wichtig: Unbedingt den Namen der Datasource übergeben! formBuildDataSource.joinSource(fr.form().dataSource(1).name()); // Art der Verknüpfung festlegen formBuildDataSource.linkType(1);
formBuildDataSource.allowCreate(false); formBuildDataSource.allowDelete(false); formBuildDataSource.allowEdit(false);
// Neue Gruppe Erzeugen fGroupCtrl = fr.form().design().addControl(FormControlType::Group,"PurchOderGroup"); // Die Neue Datasource der neuen Gruppe zuordnen fGroupCtrl.dataSource(formBuildDataSource.id()); // Neues Grid erzeugen fgc = fGroupCtrl.addControl(FormControlType::Grid,"PurchOderGrid"); // Die Neue Datasource dem neuen Grid zuordnen fgc.dataSource(formBuildDataSource.id()); // Feld "Bestellnummer" ins Grid einfügen fgc.addDataField(formBuildDataSource.id(), fieldnum(PurchTable, PurchID)); // Feld "Kreditorennummer" ins Grid einfügen fgc.addDataField(formBuildDataSource.id(), fieldnum(PurchTable, OrderAccount));
// Die Form in der wir gerade die Datasource eingefügt haben, den Args übergeben args.object(fr.form());
// Neue FormRun aufgrund der Manipulierten Form erzeugen und aufrufen neuformRun = classFactory.formRunClass(args); neuformRun.init(); neuformRun.run(); neuformRun.wait(); }
Auch in Microsoft Dynamics AX kann man mittels ADO (ActiveX Data Objects) auf praktisch jede Art von Datenbanken zugriffen. Die Benutzung ist denkbar einfach und wird durch folgende Objekte gehandhabt.
- CCADOConnection (Datenbankverbindung)
- CCADORecordSet (Datensatzsammlung)
- CCADOFields (Feldsammlung)
- CCADOField (Datenfeld)
Wurde schon eine ODBC Verbindung konfiguriert ist die Verwendung der CCADOConnection denkbareinfach und könnte für eine ODBC Verbindung mit dem Name "altDatenKunden" die Beispielsweise auf eine mySQL Datenbank verweist wie folgt aussehen.
CCADOConnection adoConnection = new CCADOConnection(); adoConnection.open("DNS=altDatenKunden"); Die Parameter werden hier immer als string übergeben und können sich Unterscheiden je nachdem auf welche Datenbank zugegriffen werden soll. Um CCADORecordSet zu inizialisieren muss vorher erst noch eine SQL Anweisung definiert werden. Die SQL Anweisung wird als String zusätzlich zur CCADOConnection dem Konstruktor übergeben.
str SQLstring = "SELECT * From Artikeltabelle"; CCADORecordSet adoRecordSet = new CCADORecordSet();
adoRecordSet.open(SQLstring, adoConnection);
Über adoRecordSet enthält man nun Zugriff auf alle Datensätze aus der Tabelle "Artikeltabelle". Mittels einer Schleifen kann man nun auf jeden einzelen Datensatz zugreifen.
while (!adoRecordSet.EOF()) { adoRecordSet.moveNext(); }
Um nun Zugriff auf die Daten zu erhalten brauchen wir noch CCADOFields, bzw. CCADOField
CCADOFields adoFields; CCADOField adoField;
adoFields = adoRecordSet.fields(); adoField = adoFields.itemName("Artikelnummer");
print adoField.value();
Durch diese Anweisungen wird nun das Feld "Artikelnummer" des aktuellen Datensatzes ausgegeben.
Direkte Zuweisungen zum Datenfeld:
- Über den Feldnamen
adoField = adoFields.itemName("Artikelnummer");
- Über die Feldnummer
adoField = adoFields.itemIDx(1);
Über die Methode
adoFields.count()
erhält man die gesamte Anzahl der Felder, der aktuellen Tabelle und erhält so Zugriff auf jedes einzelne Tabellenfeld auch ohne deren Benennung und/ oder Anzahl zu kennen..
for( i =1 ; i<= adoFields.count() ; i++) { adoField = adoFields.itemIdx(i); print adoField.value(); }
Die Methode value() der Klasse CCADOField gibt den Wert des Feldes immer als den richtigen Axapta Basis Wertetyp (str, int, real usw.) zurück. Somit muss man sich über eine Typenwandlung in der Regel keine Gedanken machen.
Zum Abschluss noch ein komplettes Bespiel.
CCADOConnection Connection; str SQL; CCADORecordSet adoRecordSet; ; // Neue ADO Connection Connection = new CCADOConnection();
// Verdingung zur Datenbank öffnen Connection.open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\altDatenKunde.mdb");
// Neuer RecordSet adoRecordSet = new CCADORecordSet();
// SQL Anweisung sql = strfmt("SELECT * FROM %1","Artikeltabelle");
// Datensatzsammlung mit Verbindung zur Datenbank und anhand der SQL Anweisung adoRecordSet.open(sql, connection);
// Alle Datensätze while (!adoRecordSet.EOF()) { // Ausgabe des Erstfeldes print adoRecordSet.fields().itemIdx(1).value(); // Ausgabe des Feldes "Artikelbestand" print adoRecordSEt.fields().itemName("Artikelbestand").value();
// Nächster Datensatz adoRecordSet.moveNext(); } adoRecordSet.close(); //Recordset schließen Connection.close(); //Connection schlißen pause;
Möchte man nach einem Upgrade von Microsoft Dynamics AX 4.0 auf Microsoft Dynamics AX 4.01 einen neuen Benutzer anlegen erhält man die Fehlermeldung "".
Dies läßt erst einmal auf ein Problem im Quellcode von Microsoft Dynamics AX schließen.
Grund für die Fehlermeldung ist aber kein Fehler im Quellcode, sondern ein fehlerhafter Datensatz in der Tabelle "SysPerimeterNetworkParms". In diesem Datensatz steht im Feld "PNType" ein ungültiger Wert.
Wird dieser Wert auf einen gültigen Wert (None) geändert, können auch wieder neue Benutzer im System angelegt werden.
Folgender Job kann zur Behebung des Problems verwendet werden: (Verwendung auf eigene Gefahr. Es wird keine Garantie oder Haftung für die Funktion und Richtigkeit des Quellcodes gegeben. )
static void CorrectAxaptaUserImportError(Args _args) { SysPerimeterNetworkParams p; DataArea a; ; while select a { changecompany(a.Id) { p = null; ttsbegin; while select forupdate p { p.PNType = PerimeterNetworkType::None; p.update(); } ttscommit; } } }
Im Microsoft PartnerSource ist ein Dokument erhältlich, welchen den Updateprozess beschreibt, der durchzuführen ist, wenn man ein Update von Microsoft Dynamics AX 4.0 auf Microsoft Dynamics AX 4.01 durchführen möchte. Alles in allem ist dieses Dokument eine sehr gute Informationsquelle, die fast alle Einzelheiten, die bei dem Update zu beachten sind, erläutert. Bedauerlich ist nur, dass eine sehr wichtige "Kleinigkeit" nicht in diesem Dokument erwähnt wird. Hält man sich strickt an die Dokumentation, so wird man leider feststellen, dass sich der AOS nach der Installation von Microsoft Dynamics AX 4.01 nicht mehr starten lässt. Es wird zwar ein Eintrag im EventLog erzeugt, dieser ist aber nicht besondern hilfreich (Error 110). Grund für diese Fehlermeldung sind die beiden StoredProzedures die ab Microsoft Dynamics AX Version 4.0 in jeder Microsoft Dynamics AX Datenbank vorhanden sein müssen. Da die Datenbank an sich nicht "geupdatet" wird, werden diese SP`s ebenfalls nicht geändert und bleiben damit auf dem Stand von Microsoft Dynamics AX 4.0. Und leider scheint Microsoft Dynamics AX 4.01 genau mit diesen SP`s nicht zusammen arbeiten zu können. Die Lösung des Problems gestaltet sich zum Glück recht einfach: Die SP's müssen einfach nur gegen die beiden SP's einer Microsoft Dynamics AX 4.01 Datenbank ausgetauscht werden und schon funktioniert der AOS wie erwartet.
Wie schon unter "Screenshots von Dynamics AX Masken erstellen" beschrieben, war es auch schon zu Dynamics AX 3.0 möglich, automatisierte Screenshots von Dynamics AX Masken erzeugen zu lassen, um diese dann später in seinen Dokumentationen zu verwenden. Leider enthält Dynamics AX 4.0 die hierfür benötigten Klassen nicht mehr. Allerdings soll bald ein Tool für Dynamics AX 4.0 erscheinen (TaskRecorder), welches diese und noch weitaus mehr Funktionalitäten bietet. Wer sich dieses Toll einmal genauer ansehen möchte und einen PartnerSource-Zugang besitzt, der kann sich im PartnerSource eine Beta Version dieses Tools downloaden. Genaue Informationen über den TaskRecoder (Features, Installation, etc.) können ebenfalls über das PartnerSource bezogen werden. Nach meinen ersten Erfahrungen wird hiermit eine gute Basis geschaffen um eine einheitliche und schnell zu erstellende Dokumentation von Prozessen und Anpassungen zu erstellen.
Wenn die MSDB Datenbank eines SQL Servers einen Datenbankfehler meldet und man keine funktionierende / fehlerfreie Sicherung hat, stellt dies meist ein größeres Problem dar, da diese nicht mit DBCC CHECKDB repariert werden kann.
Abhilfe schafft meist nur das neu Erstellen der gesamten MSDB Datenbank.
(Leider gehen hierbei unter anderem die eingerichteten Sicherungsjobs verloren)
Die MSDB kann wie folgt beschrieben neu erstellt werden (SQL Server 2000):
- Im SQL Server Enterprise Manager die Eigenschaften des Datenbankservers öffnen.
(Rechtsklick auf den Datenbankserver -> Eigenschaften)
- Auf dem Reiter „Allgemein“ auf den Button „Startparameter“ klicken.
- Den Parameter "-T3608" hinzufügen.
- Den Datenbankserver stoppen und neu starten.
- Überprüfen, dass der SQL Server Agent gestoppt ist.
- Die MSDB Datenbank auswählen und über „Extras -> SQL Query Analyzer“ den QueryAnalyzer starten.
- Die MSDB Datenbank mit folgendem Skript abhängen
use master go sp_detach_db ‚msdb’ go
- Die defekte MSDB Datenbank (msdbdata.mdf, msdblog.ldf) löschen oder umbenennen (auf Fileebene).
- Das "instmsdb.sql" Skript mit dem QueryAnalyzer ausführen.
(Liegt unter: …\Microsoft SQL Server\MSSQL\Install)
- Im SQL Server Enterprise Manager wieder den Startparameter "-T3608" entfernen.
- Den Datenbankserver stoppen und neu starten.
Jetzt sollte wieder eine funktionierende und fehlerfreie MSDB Datenbank vorliegen.
Bei einem SQL Server 2005 sollte dies genau so funktionieren.
Ein weiteres Problem beim Update auf die neue Version Microsoft Dynamics AX 4.0 kann der Name des Unternehmenskontos sein. Wird ein Unternehmenskonto verwendet, dass in seinem Namen ein „&“ enthält, z.B. „A&B“, werden einige Prozesse beim „Datenaktualisierung nachsynchronisieren“ mit einem Fehler abgebrochen. Im Ereignislog ist dann ein Eintrag des Dynamics AX 4.0 Servers zu finden, der wie folgt lautet (Ausschnitt): „…[Microsoft][ODBC SQL Server Driver][SQL Server]Falsche Syntax in der Nähe von '&'.. The SQL statement was…“ Daraus wird ersichtlich, das dass „&“ Zeichen als SQL Statement erkannt wird und die SQL Anweisung so fehlerhaft interpretiert wird. Dieses Problem kann nur gelöst werden, indem man in dem zu updatenden Microsoft Dynamics AX 3.0 ein neues Unternehmenskonto erstellt das kein „&“ Zeichen beinhaltet (Am einfachsten geht dies mit der Dublizierfunktion der Unternehmenskonten).
Für die Planung und Durchführung eines Updates stehen folgende Informationsquellen zur Verfügung:
- Microsoft Dynamics AX Upgrade Tools Guide (PDF Dokument, Erhältlich im PartnerSource)
- Microsoft Dynamics AX 4.0 Implementation Guide (CHM File, Dynamics AX 4.0 Dokumentation)
- Inside Microsoft Dynamics AX 4.0 (Buch, Microsoft Press)
Diese enthalten eine gute Beschreibung der einzelnen Schritte die erforderlich sind, um von der Version 3.0 auf die Version 4.0 von Microsoft Dynamics AX zu updaten.
Allerdings können noch einige Probleme bei dem Updateprozess entstehen, die leider nicht Besprochen werden und für die auch in den bekannten Newsgroup, Blogs und Communitys noch keine Lösungsvorschläge gibt.
So sollte ein Upgrade immer in dem Layer durchgeführt werden, in dem die Anpassungen durchgeführt wurden (z.B. CUS oder VAR). Dies hat zur Folge das man u.U. die Anwendung mehrfach kompilieren muss, da man erst nach dem einlesen der Lizenzdatei zugriff auf diese Layer erhält.
Weiterhin kann man beim Durchführen der Synchronisierung folgenden Fehler erhalten: "Cannot execute a data definition language command on (). The SQL database has issued an error."
Ursache hierfür kann sein, dass der ConfigurationKey "CSESpain" ist in dem Microsoft Dynamics AX 3.0 System, welches geupdatet werden soll, nicht angeschaltet ist/war. Dadurch sind drei Felder ("Action", "CustVendParameter", "CustVendAccount") der Tabelle "SalesPurchaseCycle" deaktiviert, die allerdings den eindeutigen Index "SalesPurchaseCycle" bilden. Dadurch werden Datensätze in die Tabelle geschrieben, die nicht dem eindeutigen Index entsprechen. Beim Update auf Microsoft Dynamics AX 4.0 wird dieser Index überprüft bzw. neu geschrieben und das Synchronisieren wird mit einem Fehler abgebrochen, da die Datensätze der Tabelle nicht eindeutig sind.
Das Problem kann gelöst werden, indem man auf der Tabelle "SalesPurchaseCycle" den eindeutigen Index "SalesPurchaseCycleIdx" auf "Enabled = NO" setzt. Anschließend sollten alle Datensätze in dieser Tabelle in allen Unternehmen gelöscht werden und der Index "SalesPurchaseCycleIdx" wieder auf "Enabled = YES" gesetzt werden.
Die Deaktivierung des ConfigurationKey "CSESpain" reicht leider nicht aus, da es dann zu Problemen beim "Datenaktualiserung nachsynchronisieren" kommen kann, da dort die einzelnen Datensätze überprüft werden (5 Prozesse werden deswegen mit einem Fehler beendet).
Weiterhin sei noch angemerkt, dass alle Anpassungen in Bereich der "Forms" zu erheblichen Problemen beim Codeupgrade führen können. Viele Formelemente wurden in der neuen Version 4.0 unbenannt, was dazu führen kann, dass eine einzige Anpassung (z.B. Änderung nur einer Formproperty) mehrere hundert Fehler erzeugen kann.
Bsp.: Änderung einer Property der Form "CompanyInfo" im upzudatenden Microsoft Dynamics AX 3.0 System führte zu 126 Fehlern im Microsoft Dynamics AX 4.0 System.
Deswegen sollte man vor einem Update genauestens überlegen/überprüfen, ob es überhaupt Sinn macht die getätigten Anpassungen mit zur neuen Version (4.0) zu migrieren.
Oft können getätigte Anpassungen durch Erweiterungen im Standard abgelöst werden, die eine ähnliche Funktionalität bieten. In machen Fällen ist es unter Berücksichtigung der benötigten Zeit sogar Sinnvoller, die Anpassung erneut im Microsoft Dynamics AX 4.0 System vorzunehmen.
Eine Migration der Anpassungen sollte nur in Erwägung gezogen werden, wenn keine andere Lösung gefunden werden kann.
Auszug aus der aktuellen Pressemitteilung von Microsoft:
Microsoft gibt Startschuss für Microsoft Dynamics AX 4.0
Microsoft bietet die neue Version der kaufmännischen Software Microsoft Dynamics AX ab sofort auf dem deutschen Markt an. Microsoft Dynamics AX 4.0 ist eine anpassbare Businessmanagementlösung, die Unternehmen hilft, fundierte und profitable Geschäftsentscheidungen zu treffen. Die Software, deren Nutzeroberfläche allen anderen Produkten von Microsoft gleicht, lässt sich nahtlos mit Microsoft Office 2003 und der Microsoft Windows Server System-Familie verzahnen. Microsoft Dynamics AX 4.0 richtet sich an mittelständische Unternehmen und den gehobenen Mittelstand.
mehr unter:
http://www.microsoft.com/germany/presseservice/detail.mspx?id=531742
Im Mai 2007 soll im Vieweg Verlag ein neues Buch über Microsoft Dynamics AX 4.0 erscheinen.
Der Schwerpunkt des Buches soll auf den Grundlagen von Dynamics Ax 4.0 und den Neuerungen zur Vorgängerversion liegen. Genaueres ist noch nicht bekannt.
Oft wird ein Export von Dynamics AX Daten in ein Exceldokument benötigt. Z.B. für einfache Auswertungen oder für Datenimporte in andere Systeme.
Hier ein kurzes Beispiel, wie man aus Dynamics AX ein neues Exceldokument per Code erstellen kann.
static void CreateExcelDokument(Args _args) { SysExcelApplication xlsApplication; SysExcelWorkBooks xlsWorkBookCollection; SysExcelWorkBook xlsWorkBook; SysExcelWorkSheets xlsWorkSheetCollection; SysExcelWorkSheet xlsWorkSheet; SysExcelRange xlsRange; CustTable custTable; int row = 1; str fileName; ; // Name des Exceldokuments. fileName = "C:\\test.xsl";
// Excel initalisieren und öffnen. xlsApplication = SysExcelApplication::construct(); xlsApplication.visible(true);
// Neues Excel Worksheet erzeugen. xlsWorkBookCollection = xlsApplication.workbooks(); xlsWorkBook = xlsWorkBookCollection.add(); xlsWorkSheetCollection = xlsWorkBook.worksheets(); xlsWorkSheet = xlsWorkSheetCollection.itemFromNum(1);
// Zellenüberschriften in das Worksheet schreiben. xlsWorkSheet.cells().item(row,1).value('Account Num'); xlsWorkSheet.cells().item(row,2).value('Name');
row++;
// Excel Worksheet mit Daten füllen (Excel-Zellen füllen). while select custTable { xlsWorkSheet.cells().item(row,1).value(custTable.AccountNum); xlsWorkSheet.cells().item(row,2).value(custTable.Name); row++; }
// Prüfen ob das Dokument schon existiert. if(WinApi::fileExists(fileName)) { WinApi::deleteFile(fileName); }
// Excel Dokument speichern. xlsWorkbook.saveAs(fileName);
// Excel schließen. xlsApplication.quit(); xlsApplication.finalize(); }
Seit ende Juli / Anfang August steht auch die lokalisierten Versionen von Microsoft Dynamics AX 4.0 für Microsoft Partner bereit, die einen PartnerSource Zugang haben.
Lokalisierungen stehen u.a. für volgende Länder zur Verfügung
Denmark France Germany Great Britain Ireland
Eine ausführliche Liste ist ebenfalls im PartnerSource erhältlich.
Wer auf der Suche nach Dokumentation zu / über Dynamics Ax 4.0 ist, sollte mal einen Blick auf diese Seite werfen: Using Microsoft Dynamics AX
Um den Text, der in der Titelleiste des Dynamics AX Clients angezeigt wird, zu ändern muss man folgendes machen:
- Auf dem Clientrechner in das Axapta Client Installationsverzeichnis wechseln.
- Im Unterverzeichnis "Client\Bin" die Datei "Axsys$$.KTD" mit einem Texteditor öffnen.
($$ steht hierbei für das entsprechde Länderkürzel. Z.B. de)
- Das Label "#1076" wie gewünscht anpassen.
Nach einem (Neu)Start des Dynamics AX Clients wird nun der in dem Label eingetragene Text in der Titelleiste des Dynamics AX Clients angezeigt.
|