Monday, March 22, 2010

Die firstonly-Falle

Dies ist eine Übersetzung des englischsprachingen Beitrags the firstonly trap.

Die Verwendung des Firstonly-Qualifizierer in einem X++ Select Statement kann die Ausführungsgeschwindigkeit erheblich steigern. Also warum den Qualifizierer nicht auch für update_recordset und delete_from anwenden? Das war meine Idee und so begann ich folgenden Code umzuschreiben:

ttsbegin;
select firstonly forupdate myTable where myTable.NotUniqueField == 'anyValue';
myTable.delete();
ttscommit;

nach

delete_from firstonly myTable where myTable.NotUniqueField == 'anyValue';

Leider entsprach das Resultat nicht den Erwartungen. Alle Datensätze die mit den Abfragekriterien übereinstimmten wurden gelöscht. Zuerst dache ich, delete_from firstonly leide an der gleichen Ungenauigkeit wie select firstonly und deshalb sei nicht sichergestellt, dass immer nur ein Datensatz verarbeitet würde. Aber in Wirklichkeit sieht es eher so aus, dass der Firstonly-Qualifizierer weder für update_recordset noch für delete_from implementiert wurde (auch wenn der X++ Kompiler es akzeptiert).

Andrerseits, T-SQL bietet die Möglichkeit das TOP-Schlüsselwort in Select-, Update- und auch Delete-Anweisungen zu verwenden - und warum sollte der Kompiler schlucken was keinen Sinn macht? Ich könnte mir vorstellen, dass diese Funktionalität in späteren Versionen zur Verfügung steht. Ich werden den Firstonly-Qualifizierer weiterhin gebrauchen, aber nur wenn die Where-Kriterien einen eindeutigen Datensatz referenzieren.

delete_from firstonly myTable where myTable.UniqueField == 'keyValue';
// and
update_recordset firstonly myTable setting AnyField = 'anyValue' where myTable.UniqueField == 'keyValue';

Vielleicht wird Code von heute auch in späteren AX-Versionen verwendet, wenn update_recordset und delete_from den Firstonly-Qualifizierer unterstützen. Ausserdem finde ich, dass es das Lesen des Codes ein wenig erleichtert.

Also tappt nicht auch in diese Falle ;)

The firstonly trap

Using the firstonly qualifier on a X++ Select Statement can increase speed performance. So why don't use it for update_recordset and delete_from too? This was my Idea and I was going on to rewrite this code:

ttsbegin;
select firstonly forupdate myTable where myTable.NotUniqueField == 'anyValue';
myTable.delete();
ttscommit;

into that

delete_from firstonly myTable where myTable.NotUniqueField == 'anyValue';

Unfortunately, the result was not as expected: All records which matched the condition were lost. First I thought the problem was, that a delete_from firstonly has the same inaccuracy as a select firstonly and therefore it wouldn't be granted to proceed only one record. But actually it seems that the firstonly qualifier is neither implemented for update_recordset nor for delete_from (even the X++ compiler accept it).

On the other hand, T-SQL provides the TOP clause in select, update and delete statement too - and why the compiler should swallow it, even it makes no sense? I could imagine, that this features will be implemented in later versions. I will use the firstonly qualifier again, but only if the where conditions refers a unique record.

delete_from firstonly myTable where myTable.UniqueField == 'keyValue';
// and
update_recordset firstonly myTable setting AnyField = 'anyValue' where myTable.UniqueField == 'keyValue';

May be, code from today is still active in later AX versions, when update_recordset and delete_from are supporting the firstonly qualifier. Further I like it as a hint when reading and try to understand source code.

So be warned to fall into that trap :)

Monday, March 8, 2010

Einfacher X++ Taschenrechner

Vielen Lesern dürfte die Rechenfunktion des Steuerlements vom Typ FormRealControl bekannt sein. Falls nicht, hier eine kleine Einführung: Wenn man in einem Feld vom Typ Gleitkommazahl eine komplette Kalkulation eintippt (Abb. a) und anschliessend die Eingabe bestätigt, wird dem Feld der ausgerechnete Wert zugewiesen (Abb. b).

Abb. a:

Abb. b:

So dachte ich, den Nutzen dieses Features dürfte auch durch x++ möglich sein. Das Resultat war schlussendlich eine statische Methode zum Auswerten von Kalkulationen.

So sieht die statische Methode aus:
/// <summary>
/// Calculates a simple math task
/// </summary>
/// <param name="_expression">
/// A simple mathematic expression. +-/* operators and brackets can be used
/// </param>
/// <returns>
/// The calculated result. If the expression does not match the expected format, 
zero is returned.
/// </returns>
/// <remarks>
/// Uses functionality from <c>FormRealControl</c>, therefore client execution 
is required.
/// </remarks>
static client real calcExpression(str _expression)
{
    SysFormRun formRun;
    Args args = new Args();
    FormBuildControl buildCtrl;
    FormRealControl realCtrl;
    
    ;
    args.name(formstr(Dialog));
    formRun = classFactory.formRunClass(args);
    
    buildCtrl = formRun.form().design().addControl(FormControlType::Real, 
    classstr(FormRealControl));
    formRun.init();
    realCtrl = formRun.design().control(buildCtrl.id());
    realCtrl.pasteText(_expression);
    return realCtrl.realValue();
}

Und so kann sie verwendet werden:
info(strfmt('%1', MyClass::calcExpression('5+(5*2)')));

Als Anmerkung bleibt zu sagen, dass die Funktion über alle Einschränkungen verfügt, über die auch das Rechen-Feature des FormRealControl-Objekt verfügt.

Simple x++ Calculator

May be you know the AX GUI calculator of the FormRealControl. If not, I will explain in a short way: If you enter a proper calculation expression in a real field (picture a) and commit the input expression will be calculated and the result set to the field (picture b).

picture a:

picture b:

So I found this functionality can also be used trough x++. The result was a static method which can evaluate an expression to be calculated.

That's the code behind:
/// ;
/// Calculates a simple math task
/// ;
/// ;
/// A simple mathematic expression. 
/// +-/* operators and brackets can be used
/// 
/// 
/// The calculated result. If the expression
/// does not match the expected format, 
/// zero is returned.
/// ;
/// ;
/// Uses functionality from 
/// FormRealControl, 
/// therefore client execution 
/// is required.
/// ;
static client real calcExpression(str _expression)
{
    SysFormRun formRun;
    Args args = new Args();
    FormBuildControl buildCtrl;
    FormRealControl realCtrl;
    
    ;
    args.name(formstr(Dialog));
    formRun = classFactory.formRunClass(args);
    
    buildCtrl = formRun.form().design()
        .addControl(FormControlType::Real, 
        classstr(FormRealControl));
    formRun.init();
    realCtrl = formRun.design()
        .control(buildCtrl.id());
    realCtrl.pasteText(_expression);
    return realCtrl.realValue();
}
And that's how we use it:
info(strfmt('%1', 
    MyClass::calcExpression('5+(5*2)')));

Keep in mind, this function inherits all limits which the FormRealControl calculation feature owns.