Findset(True, False) in Verbindung mit IF Codeunit.RUN

16. September 2021 14:35

Ich habe folgendes programmiert:
Code:
ImportData.SETRANGE("Processed at", 0DT);
IF ImportData.FINDSET(TRUE, FALSE) THEN BEGIN
 REPEAT
    ProcessSingleImportData(ImportData);
    ImportData.VALIDATE("Processed at", CREATEDATETIME(TODAY, TIME));
    ImportData.MODIFY(TRUE);
    COMMIT;
  UNTIL ImportData.NEXT = 0;
END;


Die Funktion ProcessSingleImportData enthält dabei ein IF Codeunit.Run (mit GETLASTERRORTEXT schreibe ich dabei was in eine Protokoll-Tabelle).

Das ist denke ich im Grunde eine relativ übliche Aufgabe - man hat eine große Menge Daten und man möchte sie verarbeiten und weil man nicht möchte, dass der ganze Job abbricht, nur weil in einem einzigen Datensatz ein Fehler auftritt, arbeitet man mit IF Codeunit.RUN und speichert Fehler ggf. weg.

Allerdings bekomme ich folgende Fehlermeldung bei Ausführung der IF Codeunit.RUN - Zeile:
Die unten aufgeführten C/AL-Funktionen sind während Schreibtransaktionen eingeschränkt, da mindestens eine Tabelle gesperrt wird. Form.RunModal ist in Schreibtransaktionen nicht zulässig. Codeunit.Run ist in Schreibtransaktionen nur zulässig, wenn der Rückgabewert nicht verwendet wird. OK := Codeunit.Run() ist z. B. nicht zulässig. Report.RunModal ist in Schreibtransaktionen nur zulässig, wenn RequestForm = FALSE. Report.RunModal(...,FALSE) ist z. B. zulässig. XmlPort.RunModal ist in Schreibtransaktionen nur zulässig, wenn RequestForm = FALSE gilt. XmlPort.RunModal(...,FALSE) ist z. B. zulässig. Verwenden Sie die COMMIT-Funktion, um die Änderungen vor dem Aufruf zu speichern, oder strukturieren Sie den Code anders.


Die Ursache ist der TRUE-Parameter in ImportData.FINDSET(TRUE, FALSE). Dabei passiert ein Locktable und deswegen kann man kein IF Codeunit.Run verwenden.

In der Hilfe zu FINDSET steht zum TRUE-Parameter:
Set this parameter to true if you want to modify any field value within the current key.
This parameter only applies if the ForUpdate parameter is true.
If you set this parameter to false, then you can still modify the records in the set, but these updates will not be performed optimally.
The default value is false.


Ich möchte schon, dass die Updates optimally performed werden. Aber ich möchte auch IF Codeunit.Run nutzen. Was soll ich tun?

Ich vermute mal fast, dass es in diesem Fall, bei dem ich ein Commit nach jedem Datensatz mache, sinnlos ist, den True-Parameter zu setzen und ich einfach mit Findset(False, False) arbeiten sollte (Lösung A)? Ansonsten könnte ich das Commit auch an den Anfang setzen, das würde dann auch mit Findset(TRUE, FALSE) funktionieren, nur ist der TRUE-Parameter in dem Fall dann witzlos, weil ich das Locktable durch das Commit direkt wieder aufhebe (Lösung B)?

Ich tendiere momentan zu Lösung A. Oder sollte man es komplett anders machen? Verbirgt sich hinter dem "will not be performed optimally" noch irgendwas, sodass der True-Parameter außer dem Locktable noch ein weiteres Feature bietet?

Vielen Dank euch!
Zuletzt geändert von InfoWissler am 16. September 2021 15:01, insgesamt 1-mal geändert.

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

16. September 2021 14:43

Ich würd's einfach mal mit den FINDSET Defaults (also false) ausprobieren und dann testen wie die Performance ist.

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

16. September 2021 15:02

Die Performance des Durchgehens der ImportData-Records an sich ist in Ordnung.

(War sie zunächst nicht, bis wir einen SQL-Index passend zum verwendeten Filter angelegt hatten. Früher hatten wir immer NAV-Keys angelegt und die mit SETCURRENTKEY verwendet, aber da hatte sich zuletzt in vielen Fällen rausgestellt, dass das gerade kontraproduktiv war -was ich bis heute nicht verstanden habe, aber das wäre nochmal ein eigenes Thema wert sonst- und seitdem legen wir SQL-Indizes an, die sich der SQL-Server scheinbar selber automatisch nimmt ohne SETCURRENTKEY-Anweisung. Evtl. doch eine Frage in dem Zusammenhang: der SQL-Index geht auf das Feld "Processed at". Sollte ich dann evtl. Findset(True, True) machen mit Commit am Anfang, weil ich genau das Feld auch ändere, auf das ich filtere? Habe gerade den Filter "ImportData.SETRANGE("Processed at", 0DT)" im Ausgangspost einmal ergänzt, den hatte ich zunächst nicht drin)

Ich fänd halt interessant, was mit "optimally" gemeint ist. Evtl. gar nicht die Performance. Und/oder andere Faktoren, da hatte ich gehofft, dass das evtl. jemand weiß. Evtl. ist ja auch nur das Locktable gemeint, was den Vorteil hat, dass mir da kein anderer Prozess reingrätschen kann, aber das ist so lange sichergestellt bis jemand auf die Idee kommt, den Job manuell parallel zu seinem automatischen Lauf zu starten, was nicht vorkommen sollte. Es gibt keinen anderen Prozess, der sonst auf die zu verarbeitenden Datensätze zugreifen würde. Wenns nur das ist, ists mir halt egal und ich nehme Findset(false, false).

Update: jetzt rolle ich das Thema NAV-Keys hier doch auch nochmal auf: wir hatten bis vor kurzem noch den veralteten Stand zur Verwendung von FIND('-') vs. FINDSET, dass wir FINDSET dann verwendet haben, wenn die Anzahl der Datensätze, die gefunden werden, wahrscheinlich unter 50 (Preset-Number, die es in NAV 2016 nicht mehr gibt) bleibt. Inzwischen wissen wir, dass wir in NAV 2016 generell bei Repeat Until immer Findset verwenden sollten mit passenden Parametern außer es ist wahrscheinlich, dass nicht alle Datensätze durchlaufen werden müssen, weil es z.B. eine Prüfung ist und es eine Abbruchbedingung gibt - nur dann wäre FIND('-') für REPEAT UNTIL zu verwenden. Bisher haben wir bei großen Jobs, bei denen mit REPEAT UNTIL gearbeitet wird, mit FIND('-') gearbeitet. evtl. hätten wir ja in Verbindung mit SETCURRENTKEY dann doch einfach FINDSET verwenden sollen und dann wär alles gut gewesen?

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

16. September 2021 15:49

Du kannst es schon bei Deinem FINDSET(TRUE) belassen, nur musst Du den COMMIT immer VOR dem IF CODEUNIT.RUN absetzen (und nicht danach). Und die Record-Variable ImportData an die Funktion ProcessSingleImportData am besten per VAR übergeben. Den anschließenden MODIFY auf die ImportData-Variable würde ich per neuen Instanz der Variable machen, denn Du änderst beim MODIFY das gefilterte Feld, u.U. kann Dir passieren, das Du dadurch nicht alle Datensätze erwischst (wenn Du wie jetzt MODIFY ohne die neue Instanz machst).

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

16. September 2021 16:27

Jupiter hat geschrieben:Du kannst es schon bei Deinem FINDSET(TRUE) belassen, nur musst Du den COMMIT immer VOR dem IF CODEUNIT.RUN absetzen (und nicht danach).

Dann kann er sich das FINDSET(TRUE) aber auch gleich sparen, denn der Parameter besagt, dass die Daten mit der Absicht, sie zu ändern gelesen werden sollen, und es wird eine Transaktion gestartet, welche direkt danach durch den COMMIT wieder beendet wird. (Seine vorgeschlagene Variante A)
Alles, was innerhalb der IF Codeunit.RUN abläuft ist eine in sich gekapselte, abgeschlossene Transaktion.

Einen passenden NAV-Schlüssel zu den gesetzten Filtern anlegen ist für die Performance natürlich optimal.
Da braucht man dann auch nicht noch explizit einen SETCURRENTKEY absetzen, damit der SQL-Server ihn verwendet, denn für das Suchen der Daten in der Tabelle nutzt der SQL-Server immer nur den Index, den er für richtig hält.
Was man mit SETCURRENTKEY übergeben hat, wird anschließend nur in der ORDER BY Klausel für die Reihenfolge der Ausgabe berücksichtigt.

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

16. September 2021 16:39

Jupiter hat geschrieben:Und die Record-Variable ImportData an die Funktion ProcessSingleImportData am besten per VAR übergeben.

Aus Performancegründen? Mein Credo ist beim Thema Var eigentlich, dass ich das nur verwende, wenn ich die Variable auch wirklich in der Funktion, in die ich sie per Var übergebe, ändern möchte. Hatte aber auch schonmal gehört, dass Var performanter ist, nur bisher noch nicht den Druck, dass genau ein Funktionsaufruf so langsam war, dass ich den hätte verbessern müssen.

Jupiter hat geschrieben:Den anschließenden MODIFY auf die ImportData-Variable würde ich per neuen Instanz der Variable machen, denn Du änderst beim MODIFY das gefilterte Feld, u.U. kann Dir passieren, das Du dadurch nicht alle Datensätze erwischst (wenn Du wie jetzt MODIFY ohne die neue Instanz machst).

Das ist soweit ich weiß nicht mehr aktuell. Früher habe ich das auch so gemacht, dass ich mir mit ImportHead2.GET die Variable nochmal zum Modifzieren geholt hätte, aber in NAV 2016 (und ich glaube auch in einigen früheren Versionen) ist das aber kein Thema mehr, dass sich NAV da verhaspeln kann, wenn man auf Felder filtert, deren Werte man dann in der Schleife anpasst.

@Timo, alles klar, danke. Dann würde ich demnächst mal einen NAV-Key statt eines SQL-Index anlegen und mal schauen, ob das den gleichen Effekt hat, wenn ich wieder das Problem habe, dass ein Repeat Until langsam ist aufgrund der Filterung, weil es dazu noch keinen passenden Schlüssel gibt.

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

16. September 2021 17:12

InfoWissler hat geschrieben:
Jupiter hat geschrieben:Den anschließenden MODIFY auf die ImportData-Variable würde ich per neuen Instanz der Variable machen, denn Du änderst beim MODIFY das gefilterte Feld, u.U. kann Dir passieren, das Du dadurch nicht alle Datensätze erwischst (wenn Du wie jetzt MODIFY ohne die neue Instanz machst).

Das ist soweit ich weiß nicht mehr aktuell. Früher habe ich das auch so gemacht, dass ich mir mit ImportHead2.GET die Variable nochmal zum Modifzieren geholt hätte, aber in NAV 2016 (und ich glaube auch in einigen früheren Versionen) ist das aber kein Thema mehr, dass sich NAV da verhaspeln kann, wenn man auf Felder filtert, deren Werte man dann in der Schleife anpasst.

Ich bin mir ziemlich sicher dass das nachwievor relevant ist. Hatte das Problem definitiv schon in NAV 2017.

InfoWissler hat geschrieben:@Timo, alles klar, danke. Dann würde ich demnächst mal einen NAV-Key statt eines SQL-Index anlegen und mal schauen, ob das den gleichen Effekt hat, wenn ich wieder das Problem habe, dass ein Repeat Until langsam ist aufgrund der Filterung, weil es dazu noch keinen passenden Schlüssel gibt.

Das würde ich ohnehin immer in NAV machen und nie in SQL.

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

16. September 2021 17:30

InfoWissler hat geschrieben:Aus Performancegründen?

Zum einen ja, aus Performancegründen, zumindest bei der massenhaften Datenverarbeitung bringt es was, glaube ich. Zum anderen kann Dir später passieren, dass die Funktion ProcessSingleImportData den Datensatz doch ändert (da z.B. neue Programmierung hinzugekommen ist), spätestens dann bekommst Du bei Deinem MODIFY in der Haupt-Schleife eine Fehlermeldung "Der Datensatz wurde inzwischen geändert"

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

20. September 2021 09:41

enh hat geschrieben:
InfoWissler hat geschrieben:
Jupiter hat geschrieben:Den anschließenden MODIFY auf die ImportData-Variable würde ich per neuen Instanz der Variable machen, denn Du änderst beim MODIFY das gefilterte Feld, u.U. kann Dir passieren, das Du dadurch nicht alle Datensätze erwischst (wenn Du wie jetzt MODIFY ohne die neue Instanz machst).

Das ist soweit ich weiß nicht mehr aktuell. Früher habe ich das auch so gemacht, dass ich mir mit ImportHead2.GET die Variable nochmal zum Modifzieren geholt hätte, aber in NAV 2016 (und ich glaube auch in einigen früheren Versionen) ist das aber kein Thema mehr, dass sich NAV da verhaspeln kann, wenn man auf Felder filtert, deren Werte man dann in der Schleife anpasst.

Ich bin mir ziemlich sicher dass das nachwievor relevant ist. Hatte das Problem definitiv schon in NAV 2017.

Ich bin mir ziemlich sicher, dass das nicht mehr relevant ist. Ein Kollege meinte das vor ein paar Jahren, dass das Problem nicht mehr aktuell ist und ich war da auch sehr skeptisch und habe Tests gemacht mit COUNT vorher und nachher und es hat funktioniert. Sicher, dass es in der 2017er-Version nicht ein Bug in einem bestimmten Build war oder dass das Problem evtl. doch eine andere Ursache hatte?

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

20. September 2021 17:24

Ich bin mir sicher dass ich schon mehrfach vergessen hatte das ordentlich zu programmieren und dabei auf die Nase gefallen bin. Kann sein dass es bei Nicht-Primärschlüssel funktioniert oder sowas in der Art, so genau hab ich das nicht mehr in Erinnerung. Aber es ist mir zu 100% schon mehrfach in NAV 2017 passiert dass ich einfach in der Variable in der ich gefiltert hatte geändert habe und dabei dann nicht über alle Datensätze gelaufen bin weil NAV dann umsortiert hat.

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

20. September 2021 17:33

Hallo,

ich kann mir nicht vorstellen das das funktioniert, den Primary- Key der Schelife zu ändern, und dann zu glauben der läuft dann mit dem ursprünglichen Recordset weiter. Das u.U. so aussieht, als ob es funktioniert kann auch an schlecht gewählten Testdaten liegen, oder an einem Modify(false) statt einem Modify(true);

Ich wäre da vorsichtig, spätestens, wenn der Modify andere Records deines gelesenen Recordsets ändert, wird es brenzlig.

Gruß Fiddi

Re: Findset(True, False) in Verbindung mit IF Codeunit.RUN

21. September 2021 08:19

Und selbst, wenn FINDSET(TRUE,TRUE) wie in der Dokumentation beschrieben funktionieren sollte, so kann dir niemand versichern, dass es nicht in irgendeinem Cumulative Update versehentlich doch nicht funktioniert.
Solche "unerwartete Programmverhalten" haben vor allem die "alten Hasen" leider schon zu häufig erlebt, daher die Empfehlung:
Wenn man Felder der aktuellen Sortierreihenfolge ändern möchte/muss, sollte man dies immer über eine zweite Record-Variable machen, da nur dann gewährleistet ist, dass der Datensatzzeiger im ursprünglichen Recordset immer noch an der erwarteten Stelle steht.