Täglich seine Erfolge feiern

Wer kennt das Gefühl nicht. Man arbeitet den ganzen Tag und kommt scheinbar nicht vom Fleck im Projekt. Das ist dann natürlich sehr unbefriedigend.

Der große Tag kommt jedoch irgendwann. Das Projekt ist Abgeschlossen und man kann seine Arbeit dem Kunden präsentieren. Zeitdruck, Kostendruck, Berge von Problemen liegen alle nun hinter einen und man kann sich dem Lobgesang des Kunden hingeben. Wir feiern unseren Erfolg.

Bis wir jedoch diesen Tag erreicht haben gilt es in der Regel eine lange Durststrecke zu überstehen. Wir brauchen auf den Weg soetwas wie kleine Oasen. Kleine Erfolge, die wir feiern können.

Seit mehreren Wochen übe ich mich darin meine täglichen Erfolge zu Feiern und ich fühle mich gut dabei. Sehr gut sogar. Wenn ich es etwas überschlage komme ich auf gut zwei bis fünf Erfolge pro Tag. Und am Ende des Tages reflektiere ich noch einmal über alle meine Erfolge. Mein kleiner CCD im Ohr ist also auch glücklich, da ich täglich reflektiere.

Lust bekommen es mal selber zu probieren? Schreibe dir eine Aufgabe auf ein Blattpapier und hake sie ab sobald du sie erledigt hast. Genieße es den Vorgang beim abhaken – nimm von mir aus einen grünen Stift zum abhaken. Du hast es geschaft, der Punkt ist erledigt! Super! 🎉🎉🎉

Blogeintrag geschrieben ✅

Joins in Queries

Durch die Normalisierung der Datenbank wirst du eher früher als später die Notwendigkeit haben Daten aus zwei oder mehreren Tabellen in einer Query abzufragen. Zum Glück verhält sich Dynamics AX hier nicht anders als SQL.

Es gibt vier JoinMode’s InnerJoin, OuterJoin, ExistsJoin und NoExistsJoin.

InnerJoin
Selektiert Datensätze, welche in beiden Tabellen vorhanden sind und gibt deren Daten zurück.
Entsprechung in SQL:

SELECT T1.ACCOUNTNUM AS ACCOUNTNUM,T1.DATAAREAID AS DATAAREAID,T1.PARTITION AS PARTITION,T1.RECID AS RECID,T2.DATAAREAID AS DATAAREAID2,T2.PARTITION AS PARTITION2 
FROM CUSTTABLE T1 CROSS JOIN CONTACTPERSON T2 
WHERE (T1.PARTY=T2.CONTACTFORPARTY AND (T1.DATAAREAID = T2.DATAAREAID) AND (T1.PARTITION = T2.PARTITION))

OuterJoin
Selektiert alle Datensätze, welche in der übergeordneten Tabelle vorhanden sind. Egal ob in der untergeordneten Tabelle Datensätze verknüpft sind.
Entsprechung in SQL:

SELECT T1.ACCOUNTNUM AS ACCOUNTNUM,T1.DATAAREAID AS DATAAREAID,T1.PARTITION AS PARTITION,T1.RECID AS RECID,T2.DATAAREAID AS DATAAREAID2,T2.PARTITION AS PARTITION2
FROM  CUSTTABLE T1 LEFT OUTER JOIN CONTACTPERSON T2 
    ON (T1.PARTY=T2.CONTACTFORPARTY AND (T1.DATAAREAID = T2.DATAAREAID) AND (T1.PARTITION = T2.PARTITION))

ExistJoin
Es werden nur Datensätze zurück gegeben, welche in der untergeordneten Tabelle verknüpfte Datensätze enthalten. Wichtig, es wird nur die übergeordnete Tabelle geladen.
Entsprechung in SQL:

SELECT T1.ACCOUNTNUM AS ACCOUNTNUM,T1.DATAAREAID AS DATAAREAID,T1.PARTITION AS PARTITION,T1.RECID AS RECID 
FROM CUSTTABLE T1 
WHERE EXISTS (
    SELECT 'x' 
    FROM CONTACTPERSON T2 
    WHERE (T1.PARTY=T2.CONTACTFORPARTY AND (T1.DATAAREAID = T2.DATAAREAID) AND (T1.PARTITION = T2.PARTITION)))

NotExitJoin
Es werden nur Datensätze zurück gegeben, welche in der untergeordneten Tabelle keine verknüpften Datensätze enthalten. Wichtig, es wird nur die übergeordnete Tabelle geladen.
Entsprechung in SQL:

SELECT T1.ACCOUNTNUM AS ACCOUNTNUM,T1.DATAAREAID AS DATAAREAID,T1.PARTITION AS PARTITION,T1.RECID AS RECID 
FROM CUSTTABLE T1 
WHERE NOT (EXISTS (
    SELECT 'x' 
    FROM CONTACTPERSON T2 
    WHERE (T1.PARTY=T2.CONTACTFORPARTY AND (T1.DATAAREAID = T2.DATAAREAID) AND (T1.PARTITION = T2.PARTITION))))

 

Verbindungsfehler beim Connection zum Team Foundation Server TF400367

Ich habe folgenden Fehler immer wieder bekommen als ich Versucht habe meinen Team Foundation Server 2015 Update 3 mit meinen Contoso Dynamics AX 2012 R3 UC9 zu verbinden.

TF400367 -The request could not be performed due to a host type mismatch

Die Lösung war ziemlich einfach. Ich hatte die Team Foundation Server URL „unvollständig“ im MS Dynamics AX angegeben.

http://ax2012r2a:8080/tfs statt http://ax2012r2a:8080/tfs/DefaultCollection

Für die TFS-SDK ist es wichtig zusätzlich noch die Collection anzugeben indem das Team-Projekt liegt.

Ranges in Queries

Ranges sind ein Werkzeug um in einer Query unserem Benutzer Filter (Ranges) anzubieten. Beim Öffnen eines Formulars, welches die Query als Datenquelle z.B. verwendet, wird dann folgender Dialog angezeigt.

rangesdialog

Um eine Range zu definieren haben wir folgende Operatoren als Filtermöglichkeit. Die Werte gibt man in der Property Value an in der Range:

Operator Beschreibung Beispiel
= Sucht Datensätze, indem das angegebene Range-Feld gleich dem Wert ist =Apfel
! Sucht Datensätze, indem das angegebene Range-Feld ungleich dem Wert ist !Apfel
< Sucht Datensätze, indem das angegebene Range-Feld kleiner ist als der Wert <10
> Sucht Datensätze, indem das angegebne Range-Feld größer ist als der Wert >20
.. Sucht Datensätze, indem das angegebene Range-Feld zwischen den Werten liegen oder mit einem der Werte übereinstimmen 20..50
, Sucht Datensätze, indem das angegebene Range-Feld einem der Werte in der Liste entsprechen Apfel, Kirsche, Birne
? Sucht Datensätze, welche das angegebene Range-Feld dem angegebenen Wert entspricht wobei das ?-Zeichen für ein beliebiges Zeichen steht Ap??l
* Sucht Datensätze, indem das angegebne Range-Feld vor dem Stern entspricht und nach dem Stern beliebig ist Apf*

Zusätzlich können wir in der Property Status einer Range folgende Werte vergeben:

Open Ermöglicht es den Benutzer die Range anzupassen oder gar zu entfernen
Lock Die Range wird dem Benutzer zwar angezeigt, aber er kann sie nicht bearbeiten
Hide Der Benutzer bekommt die Range nicht angezeigt und er hat keine Möglichkeit sie anzupassen oder zu entfernen

Um eine Range zu definieren zieht man einfach ein Feld aus der Datenquelle der Query in den Ranges-Node hinein und konfiguriert die Properties Value und Status.

Ergebniss einer Query anzeigen

Du hast eine Query erstellt und möchtest sie kurz kontrollieren? Du hast sie geöffnet; die Parameter-Abfrage kam jedoch kein Ergebniss? Kenn ich! Vorweg, ich habe keine Ahnung warum es so ist – aber eine Lösung wie man das Problem umgehen kann.

Zuerst erstellst du eine einfache View über New View im Kontext Menü vom Node AOT->Data Dictionary->Views. Im Property Query der neuen View schreibst du den Namen der Query rein, welche du Anzeigen möchtest. Anschließend musst du noch die Felder in die Feldliste der View aufnehmen. Speichern (wichtig sieh hier) und View danach öffnen.

Sollte bei dir Folgender Fehler kommen beim Öffnen der View: You are not authorized to access table ‚ViewX‘ (ViewX). Contact your system administrator.
Liegt es daran, dass du keine Felder zum Anzeigen in der View angegeben hast.

youarenotauthorizedtoaccesstable

Cannot select a record in View

Über folgenden Fehler bin ich schon öfters gestolpert.Er ist auch ein fiese Sau 🐷.

Ich erstelle mir schnell eine View um zum Beispiel das Ergebnis einer Query zu testen. Neue View angelegt, Query als Datenquelle reingezogen, Felder in die Feldliste der View hinzugefügt und View geöffnet. *boom* Cannot select a record in View… (siehe Bild).

Man schaut kurz in seinen AOT und alles sieht gut aus. Es wird nicht einmal mehr angezeigt, dass die View noch nicht gespeicher wurde oder zumindest nicht richtig! Beim Öffnen der ungespeicherten View verschwindet die rote Markierung nämlich.

Mit euer View ist in der Regel alles in Ordnung. Ihr müsst sie nur abspeichern und könnt sie erst dann öffnen. Passiert übrigens auch, wenn ihr Änderungen an der View vornimmt und sie vor dem Öffnen nicht speichert.

Der Grund hierfür ist, dass erst beim Speichern die View im SQL-Server angelegt bzw. upgedatet wird.

fehlerbeimoeffneneinerview

Text vom Image für die Suchmaschinen:
Cannot select a record in View3 (View3). The SQL database has issued an error.SQL error description [Microsoft][SQL Server Native Client 11.0][SQL Server]Invalid object name ‚VIEW3‘.

TryCatch und Transaktionen in X++

Transaktionen in X++ sind dank ttsBeginn und ttsCommit schnell gemacht. Es gibt jedoch ein paar Sachen zu beachten. Hier soll es um die Besonderheit bei TryCatch im Zusammenspiel mit Transaktionen gehen.

Ein vereinfachtes Beispiel sieht so aus:

static void TransactionExceptionJob1(Args _args)
{
    try
    {
        ttsBegin;
        throw Exception::Error;
        ttsCommit;
    }
    catch
    {
        info("Es ist ein Fehler aufgetreten.");
    }
}

Der Fehler wird ausgelöst; ein Rollback wird ausgeführt und der Fehler wird anschließend im Info-Log ausgegeben.

Jetzt kann man sehr schnell auf die Idee kommen den TryCatch-Block zwischen ttsBegin und ttsCommit zu packen um den Fehler abzufangen ohne einen Rollback. Du wirst dann schnell feststellen, dass dein Catch-Block nie ausgeführt wird bei einem Fehler. Das liegt daran, dass innerhalb einer Transaktion nur die Exceptions DuplicateKeyException und UpdateConflict abgefangen werden können. Auf alle anderen Exceptions kannst du nur außerhalb reagieren und nur nach einem Rollback.

Mit einen kleinen Programm zum Probieren ist es am einfachsten zu verstehen. Du kannst im Try-Block die verschiedenen auskommentierten Exceptions ausprobieren.

static void TransactionExceptionJob2(Args _args)
{
    try
    {
        ttsBegin;
        try
        {
            // Datenbank-Aktionen z.B. Insert, Update oder Delete
            //throw Exception::DuplicateKeyException;
            //throw Exception::UpdateConflict;
            throw Exception::Error;
        }
        catch(Exception::DuplicateKeyException)
        {
            // Wird nicht Ignoriert
            // Hier kann reagiert werden
            info("DuplicateKeyException abgefangen");
        }
        catch(Exception::UpdateConflict)
        {
            // Wird nicht Ignoriert
            // Hier kann reagiert werden
            info("UpdateConflict abgefangen");
        }
        catch(Exception::Error)
        {
            // Wird Ignoriert
            info("Ich werde nie aufgerufen");
        }
        catch
        {
            // Wird Ignoriert
            info("Ich werde nie aufgerufen");
        }
        ttsCommit;
    }
    catch
    {
        // Fehler kann hier abgefangen werden.
        // Transaktion wurde aber bereits zurückgerollt.
        info("Außerhalb der Transaction wurde abgefangen");
    }
}

Bei verschachtelten Transaktionen greift das gleiche Prinzip. Alle Exceptions bis auf die Ausnahmen können erst nach dem Rollback abgefangen werden.

Eine vereinfachte Demo:

static void TransactionExceptionJob3(Args _args)
{
    try
    {
               
        ttsBegin;
        try
        {
            ttsBegin;
            throw Exception::Error;      
            ttsCommit;
        }
        catch
        {
            // Wird ignoriert
            info("Catch-Block außerhalb der zweiten Transaktion");
        }
        ttsCommit;
    }
    catch
    {
        // Fehler kann hier abgefangen werden.
        // Transaktion wurde aber bereits zurückgerollt.
        info("Catch-Block außerhalb der ersten Transaktion");
    }
}

Muss man halt wissen… 😇