Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   OnChange behindert Post (https://www.delphipraxis.net/203119-onchange-behindert-post.html)

ioster 15. Jan 2020 15:37

Datenbank: MS SQL Server • Version: 2008 • Zugriff über: FireDAC

OnChange behindert Post
 
Hallo zusammen,

nachdem ich grundlegende Probleme mit der FireDAC-Connection überwunden habe, ist nun bei der Entwicklung einer Belegmaske ein Fehler aufgetreten, der im Zusammenhang mit dem OnChange-Ereignis von TField angesiedelt ist.

Ich habe für den Beleg zwei Tabellen in denen Kopf- und Positionsdaten gespeichert werden. Im AfterOpen der Tabellen habe ich an das OnChange-Ereignis von bestimmten Feldern Prozeduren gehängt, um Adress- und Artikelstammdaten nachzulesen oder den Gesamtpreis von Position und Gesamtbeleg zu aktualisieren.

Delphi-Quellcode:
if Dataset.FindField('ArtNr') <> nil then
      Dataset.FieldByName('ArtNr').OnChange := ArtikelEingabe;

    if Dataset.FindField('VKNetto') <> nil then
      Dataset.FieldByName('VKNetto').OnChange := PreisBerechnung;
    if Dataset.FindField('Anzahl') <> nil then
      Dataset.FieldByName('Anzahl').OnChange := PreisBerechnung;
    if Dataset.FindField('Positionsrabatt1') <> nil then
      Dataset.FieldByName('Positionsrabatt1').OnChange := PreisBerechnung;
    if Dataset.FindField('Positionsrabatt2') <> nil then
      Dataset.FieldByName('Positionsrabatt2').OnChange := PreisBerechnung;
    if Dataset.FindField('Positionsrabatt3') <> nil then
      Dataset.FieldByName('Positionsrabatt3').OnChange := PreisBerechnung;
    if Dataset.FindField('Positionsrabatt4') <> nil then
      Dataset.FieldByName('Positionsrabatt4').OnChange := PreisBerechnung;
Die Preisberechnung funktioniert auch wunderbar. Allerdings habe ich das Problem, dass die Daten aus der Kopftabelle nicht richtig weggeschrieben werden. Die vorab eingegebenen Adressstammdaten und Belegsummen sind nach dem Navigieren in der Datenmenge weg.

Nun bin ich selber schon auf die Idee gekommen, den Status des Datasets zu prüfen und ein Post abzusenden, sofern die Datenmenge sich im dsedit oder dsinsert befindet. Das habe ich die Routine für die Preisberechnung eingebaut und nun auch in das OnBeforeInsert und OnBeforeEdit der Positionstabelle. Offenbar scheinen die Feldinhalte der Kopftabelle schon vorher abhanden gekommen zu sein. Auf dem Bildschirm werden die Werte solange richtig angezeigt bis man einen anderen Beleg aufruft.

Ein ähnliches Szenario mit 1:n-Beziehungen zwischen Tabellen habe ich auch in anderen Masken wie Artikel- und Adressstamm. Dort tritt dieses Problem allerdings nicht auf, aber an den Tabellen und Feldern hängen keine OnChange-Ereignisse.

Die Preisberechnung sieht folgendermaßen aus, wobei ich einräume, dass die ein oder andere Feinheit in der Berechnung fehlt. Das ist aber für die Speicherung der Daten nicht weiter relevant.

Delphi-Quellcode:
procedure TDatenmodul.Preisberechnung(Field: TField);
  var
    PosRab       : double;
    PosZuschlag  : double;
    VKNetto      : double;
    Zaehler      : integer;
    RabBetrag    : double;
    ZuschlBetrag : double;
    MerkGesNetto : double;
    MerkGesBrutto : double;
    MerkUSt      : double;
  begin
    RabBetrag   := 0;
    ZuschlBetrag := 0;

    MerkGesNetto := Field.DataSet.FieldByName('VKNettoGesamt').AsFloat;
    MerkGesBrutto := Field.DataSet.FieldByName('VKBruttoGesamt').AsFloat;
    MerkUst      := Field.DataSet.FieldByName('USt').AsFloat;

    with Field.DataSet Do
      begin
        VKNetto := Fieldbyname('Anzahl').asfloat * Fieldbyname('VKNetto').asfloat;

        for Zaehler := 1 to 4 do
          begin
            if (Fieldbyname('Positionsrabatt' + inttostr(Zaehler)).AsFloat <> 0) then
              begin
                if (Fieldbyname('Rabattart' + inttostr(Zaehler)).AsString = '=') then
                  PosRab := Fieldbyname('Positionsrabatt' + inttostr(Zaehler)).asfloat
                else
                  PosRab := VKNetto * Fieldbyname('Positionsrabatt' + inttostr(Zaehler)).AsFloat / 100;

                VKNetto  := VKNetto - PosRab;

                RabBetrag := RabBetrag + PosRab;
              end;
          end;

        for Zaehler := 1 to 4 do
          begin
            if (Fieldbyname('Zuschlag' + inttostr(Zaehler)).AsFloat <> 0) then
              begin
                if (Fieldbyname('Zuschlagart' + inttostr(Zaehler)).AsString = '=') then
                  PosZuschlag := Fieldbyname('Zuschlag' + inttostr(Zaehler)).asfloat
                else
                  PosZuschlag := VKNetto * Fieldbyname('Zuschlag' + inttostr(Zaehler)).AsFloat / 100;

                VKNetto     := VKNetto + PosZuschlag;

                ZuschlBetrag := PosZuschlag;
              end;
          end;

        if not(State in [dsedit, dsinsert]) then
          Edit;

        Fieldbyname('VKNettoGesamt').asfloat  := VKNetto;
        Fieldbyname('RabattBetrag').AsFloat   := RabBetrag;
        Fieldbyname('ZuschlagsBetrag').AsFloat := ZuschlBetrag;

        Fieldbyname('Ust').AsFloat           := Fieldbyname('VKNettoGesamt').AsFloat * Fieldbyname('SteuerProzent').AsFloat / 100;
        Fieldbyname('VKBruttoGesamt').AsFloat := Fieldbyname('VKNettoGesamt').asfloat + Fieldbyname('Ust').asfloat;

        Post;
      end;

    with Datenmodul.AngebotKopf Do
      begin
        Edit;
        Fieldbyname('VKNetto').AsFloat := Datenmodul.AngebotKopf.Fieldbyname('VKNetto').AsFloat - MerkGesNetto + Field.DataSet.FieldByName('VKNettoGesamt').asfloat;
        Fieldbyname('VKBrutto').AsFloat := Datenmodul.AngebotKopf.Fieldbyname('VKBrutto').AsFloat - MerkGesBrutto + Field.DataSet.FieldByName('VKBruttoGesamt').asfloat;
        Fieldbyname('Ust').AsFloat     := Datenmodul.AngebotKopf.Fieldbyname('USt').AsFloat - MerkUST + Field.DataSet.FieldByName('USt').asfloat;

        if Field.Dataset.FieldByName('Steuerschluessel').Asinteger > 0 then
          Fieldbyname('USt' + inttostr(Field.Dataset.FieldByName('Steuerschluessel').Asinteger)).asfloat := Fieldbyname('USt' + inttostr(Field.Dataset.FieldByName('Steuerschluessel').Asinteger)).asfloat - MerkUSt + Field.DataSet.FieldByName('USt').asfloat;

        Post;
      end;
  end;
Viele Grüße
Ingo

hoika 21. Jan 2020 17:54

AW: OnChange behindert Post
 
Hallo,
Zitat:

Die vorab eingegebenen Adressstammdaten und Belegsummen sind nach dem Navigieren in der Datenmenge weg.
Das DataSet.Post schließt ja das DataSet.
Ich würde zum Speichern eine eigene Query benutzen.

Uwe Raabe 21. Jan 2020 20:45

AW: OnChange behindert Post
 
Zitat:

Zitat von hoika (Beitrag 1455724)
Das DataSet.Post schließt ja das DataSet.

Wie kommst du denn da drauf?

Zitat:

Zitat von hoika (Beitrag 1455724)
Ich würde zum Speichern eine eigene Query benutzen.

Das ist nicht nötig. Die TFDQuery ist sehr wohl in der Lage, das selbst auszuführen.

himitsu 21. Jan 2020 22:24

AW: OnChange behindert Post
 
Beim Post wird, entsprechend der geänderten Felder (TField: OldValue<>Value) und dem DataSet-State ein Statement generiert, unter Bezug auf die FieldInfos des Queries.
Bei Insert+Post ein INSERT INTO, bei Edit+Post ein UPDATE und bei Delete natürlich DELETE FROM.

Erstmal wäre es besser, wenn die QueryKomponente das ID/Key/Index-Feld(er) des Datensatz kennen, um ein optimales WHERE genierien zu können.
Und manchmal kann die Code-Generation auch schief laufen.
Schön ist, dass bei Problemen oft garkeine Fehlermeldung kommt und dann garnichts gemacht wird.
Tipp: Hänge einen SQL-Monitor an die Connection oder schaue im Activity-Log der Datenbank, ob und was für ein SQL-Statment beim Post abgeschickt wird.

Aus diesem Grund kann man hier bei vielen DB-Komponenten optional den Tabellennamen angeben, falls er nicht automatisch erkannt wird,
und neben dem SQL kann man dann auch ein InsertSQL, UpdateSQL und DeleteSQL angeben, wenn eines/alle dieser SQL nicht automatich aus dem SQL (SELECT) generiert wird.

Delphi-Referenz durchsuchenTFDQuery.Table
Delphi-Referenz durchsuchenTFDQuery.UpdateObject
Delphi-Referenz durchsuchenTFDQuery.KeyFieldCount


Und nach dem Post wird eventuell noch ein UpdateRecord ausgeführt, wobei dort das WHERE des Sql (SELECT) ersetzt wird, durch das/die KeyFields,
um die aktuellen Inhalte des grade geposteten Datensatzes zu bekommen, falls z.B. ein Trigger dort anschließend nochmal was geändert hat.
Kommt hier kein Result (RecordCount=0) zurück, dann wird dieser Datensatz natürlich gelöscht, da er scheinbar auf der DB nicht mehr existiert (durch Trigger gelöscht).

hoika 21. Jan 2020 23:03

AW: OnChange behindert Post
 
Hallo,
Zitat:

Wie kommst du denn da drauf?
Alles schon vorgekommen...

Das Zusammenspiel datensensitiver Elemente beim DML und
bei gleichzeitiger Nutzung zur Anzeige führt doch nur zu Ärger.
Und debuggbar ist das meistens nicht.

Ich mache da aus eigenen Erfahrungen einen Bogen darum.

Uwe Raabe 22. Jan 2020 09:33

AW: OnChange behindert Post
 
Zitat:

Zitat von hoika (Beitrag 1455737)
Das Zusammenspiel datensensitiver Elemente beim DML und
bei gleichzeitiger Nutzung zur Anzeige führt doch nur zu Ärger.
Und debuggbar ist das meistens nicht.

Das kann ich schlicht und einfach nicht bestätigen. Ich benutze datensensitive Controls und Datasets mit Append, Edit, Post, Delete seit Jahren ohne irgendwelche Probleme. Wie bei jeder anderen Technologie muss man halt wissen, wie man damit umgehen muss. Probleme gibt es im Gegenteil eher mit solchen Anwendungen, die das um alles in der Welt vermeiden wollen.

Aber das geht hier schon etwas am Thema vorbei.

Uwe Raabe 22. Jan 2020 09:42

AW: OnChange behindert Post
 
Zitat:

Zitat von ioster (Beitrag 1455223)
Im AfterOpen der Tabellen habe ich an das OnChange-Ereignis von bestimmten Feldern Prozeduren gehängt, um Adress- und Artikelstammdaten nachzulesen oder den Gesamtpreis von Position und Gesamtbeleg zu aktualisieren.

Das ist vermutlich die falsche Vorgehensweise. Ein Post auf ein DataSet innerhalb eines OnChange-Events eines Fields dieses DataSets kann nicht funktionieren!

himitsu 22. Jan 2020 10:43

AW: OnChange behindert Post
 
Das OnChange der sichtbaren Controls reagiert ja, wenn sich der Inhalt dieses Controls ändert
und das passiert auch z.B. beim Open, Close, Scroll, Refresh des DataSets und dort sollte sowas wie Edit/Insert/Post/Delete in einem Event besser nicht vorkommen.

Warum werden die Berechnungen nicht in einem Trigger in der Datenbank erledigt, bzw. im BeforePost, schon beim Speichern des Datensatzes?
Auch als CalcField oder schon im SELECT können während des Ladens Werte berechnet werden.

Im TField.OnChange sollte auf dessen DataSet auch niemals "Post" ausgeführt werden.

Einfaches Beispiel:
FieldA.OnChange ändert FieldB und FieldC und macht anschließend Post
* FieldB.OnChange ändert auch irgendwas und macht ebenfalls Post
weiter im FieldA.OnChange, kommt nun FieldC dran ... was nicht geht, weil schon Post schon durch ist
und immernoch im FieldA.OnChange, käme nun das POST, was oben ausgegraut wurde, da sich das nächste Event dazwischen schob

jobo 22. Jan 2020 10:46

AW: OnChange behindert Post
 
Mir scheint die Preisberechnung etwas "verwickelt". Sie ist an recht vielen Stellen eingebunden und verursacht gefühlt dutzende Posts. Und ist tatsächlich sichergestellt, dass die Kopfdaten (Dataset) "richtig steht", also synchron zu dem "Field" Dataset?

Außerdem werden hier offenbar redundante Infos abgelegt, die man problemlos während der Anzeige als berechnete Felder liefern kann oder täusche ich mich.

Das Ziel wäre m.E. eine vom Dataset(s) entkoppelte Berechnung über Paramter und das einmalige Auslösen dieser Berechnung, sowie einmalige Speichern der Berechnung.

hoika 22. Jan 2020 18:46

AW: OnChange behindert Post
 
Hallo,
Zitat:

Wie bei jeder anderen Technologie muss man halt wissen, wie man damit umgehen muss
Da hast du vollkommen Recht!
Deshalb lasse ich es ja sein ;)


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:33 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz