Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   Delphi Problem mit Generator (https://www.delphipraxis.net/214921-problem-mit-generator.html)

TurboMagic 4. Apr 2024 19:04

Datenbank: Firebird • Version: 5.0 • Zugriff über: FireDAC

Problem mit Generator
 
Hallo,

in einer Anwendung verwende ich FireDAC mit Firebird 5.0.
Einige Tabellen haben einen generator zur erzeugung des eindeutigen Primärschlüssels und
in der Regel funktioniert das auch.

Ich habe für das Programm Update auch einen Mechanismus geschrieben der mit der alten
Version der Software zuerst ein transparentes Backup anlegt. Die neue Version der Software
findet das und stellt es unter einem anderen Dateinamen wieder her. Danach werden die Inhalte
aus dieser Datei mittels FDBatchMove in die von der neuen Programmversion mitgelieferte
Datenbankdatei übernommen. Und es werden die Werte der Generatoren übernommen.

Nun habe ich festgestellt, dass es danach für den jeweils ersten Versuch einen neuen Satz zu
einer der Tabellen die einen generator benutzen hinzuzufügen mit einer Verletzung des
Primärschlüssels knallt:

"[FireDAC][Phys][FB]violation of PRIMARY or UNIQUE_KEY constraint "INTEG_104" on table
"MY_FAILURELIST". Problematic key value is ("FAILURE_ID" = 227).

Schaue ich mir die DB vor dem ersten Schreibzugriff an stelle ich fest, dass der Wert des
betreffenden Generators auf 227 steht. Schaue ich mir die Tabelle an gibt es auch schon
einen Eintrag mit 227 als Schlüsselwert.

Woher kommt das? Bei Späteren Schreibzugriffen werden scheinbar problemfrei Datensätze erzeugt...

Der Trigger in der DB ist so definiert:
Code:
CREATE TRIGGER BI_KASSE_TSE_FAILURE_ID FOR KASSE_TSE_FAILURES
ACTIVE BEFORE
  INSERT
POSITION 0
AS
BEGIN
  IF (NEW.FAILURE_ID IS NULL) THEN
      NEW.FAILURE_ID = GEN_ID(KASSE_FAILURE_ID_GEN, 1);
END;
Hier der Code der in diese Tabelle schreibt:
Delphi-Quellcode:
function TFailureLog.AddEntry(FailureType      : TFailureType;
                              FailureCode      : Int32;
                              const FailureText : string): Integer;
var
  FailureTypeStr : string;
begin
  Result := -1;

  if not self.FSuppressLogging then
  begin
    try
      FailureTypeStr := ConvertFailureTypeToString(FailureType);


      // Relevant, damit FireDAC beim Append später nicht wegen dem noch leeren
      // Primärschlüssel meckert.
      FQuerySave.UpdateOptions.AutoIncFields := 'FAILURE_ID';

      FQuerySave.SQL.Text := 'select FAILURE_ID, DEVICE_ID, FAILURE_TYPE, ' +
                             'FAILURE_CODE, FAILURE_TEXT, FAILURE_REMARK ' +
                             'from KASSE_TSE_FAILURES';

      FQuerySave.Open;
      FQuerySave.Append;

      FQuerySave.FieldByName('DEVICE_ID').AsInteger    := FCurrentDEVICEID;
      FQuerySave.FieldByName('FAILURE_TYPE').AsString  := FailureTypeStr;
      FQuerySave.FieldByName('FAILURE_CODE').AsInteger := FailureCode;
      FQuerySave.FieldByName('FAILURE_TEXT').AsString  := FailureText;
      FQuerySave.FieldByName('FAILURE_REMARK').AsString := '';

      FQuerySave.Post;

      Result := FQuerySave.FieldByName('FAILURE_ID').AsInteger;
      FQuerySave.Close;
    except
      On e:Exception do
        log.SendException(cCategory, 'Fehler beim Speichern eines Fehlerlog '+
                                     'Eintrags: ' + e.Message, e);
    end;
  end;
end;

IBExpert 5. Apr 2024 06:39

AW: Problem mit Generator
 
mal ganz doof gefragt:

warum machst du in deiner AddEntry Routine den extrem seltsamen Umweg über ein konstantes
Select Sql und überlässt dann den Dataset Komponenten da irgendwas mit Append zu machen?

Warum umständlich wenn man es auch einfach über einen Insert Befehl gehen würde ...

(und der könnte dann über returning auch den gerade generierten wert vom Generator
zurückmelden, so das du den auch in deinem Dataset zur weiteren Identifikation des
Datensatzes haben kannst.

Lemmy 5. Apr 2024 07:13

AW: Problem mit Generator
 
Zitat:

Zitat von TurboMagic (Beitrag 1535326)
Und es werden die Werte der Generatoren übernommen.

wie? Über ein Select Max() oder liest Du den Wert des Generators aus (Select gen_ID(X,0) from )?

Grüße

TurboMagic 5. Apr 2024 07:37

AW: Problem mit Generator
 
Zitat:

Zitat von IBExpert (Beitrag 1535342)
mal ganz doof gefragt:

warum machst du in deiner AddEntry Routine den extrem seltsamen Umweg über ein konstantes
Select Sql und überlässt dann den Dataset Komponenten da irgendwas mit Append zu machen?

Warum umständlich wenn man es auch einfach über einen Insert Befehl gehen würde ...

(und der könnte dann über returning auch den gerade generierten wert vom Generator
zurückmelden, so das du den auch in deinem Dataset zur weiteren Identifikation des
Datensatzes haben kannst.

Danke für den Hinweis. Ich glaube mich jedoch zu erinnern, dass ich diese Art der Umsetzung
auf Basis einer Forumsdiskussion hier gemacht habe. Das funktioniert ja auch grundsätzlich.
Nur irgendwie im Ablauf des Update Mechanismus scheint es ein Problem zu geben.

TurboMagic 5. Apr 2024 07:38

AW: Problem mit Generator
 
Zitat:

Zitat von Lemmy (Beitrag 1535344)
Zitat:

Zitat von TurboMagic (Beitrag 1535326)
Und es werden die Werte der Generatoren übernommen.

wie? Über ein Select Max() oder liest Du den Wert des Generators aus (Select gen_ID(X,0) from )?

Grüße

Hallo,

hier die Generator Kopieren Routine:

Delphi-Quellcode:
procedure TDBCopy.CopyGenerators;
var
  Generators : TStringList;
  Generator : string;
  Value     : Int64;
  Msg       : string;
begin
  Generators := TStringList.Create;

  try
    FSourceSQLConnection.GetGeneratorNames('', '', '', Generators);

    for Generator in Generators do
    begin
      Value := GetCurrentGeneratorValue(FGeneratorReadQuery, Generator);

      SetGeneratorValue(FGeneratorWriteQuery, Generator, Value);

      Msg := 'Generator Wert gesetzt für: ' + Generator + 'Wert: ' + Value.ToString;
      FLog.Add(Msg);
      log.Send(LevelViolet, cCategory, Msg);
    end;
  finally
    Generators.Free;
  end;
end;

function TDBCopy.GetCurrentGeneratorValue(Query: TFDQuery;
                                          const GeneratorName: string): Int64;
begin
  Query.Open('SELECT GEN_ID(' + GeneratorName +', 0) AS GENERATOR_VALUE '+
             'FROM RDB$DATABASE');
  try
    if not Query.Eof then
      Result := Query.FieldByName('GENERATOR_VALUE').AsLargeInt
    else
      Result := -1;
  finally
    Query.Close;
  end;
end;

procedure TDBCopy.SetGeneratorValue(Query              : TFDQuery;
                                    const GeneratorName : string;
                                    Value              : Int64);
begin
  try
    Query.ExecSQL('ALTER SEQUENCE ' + GeneratorName +
                  ' RESTART WITH ' + Value.ToString);
  except
    on e:Exception do
      log.SendException(cCategory, 'Fehler beim Setzen des Generators: ' +
                        GeneratorName + ' Wert: ' + Value.ToString +
                        ' Grund: ' + e.Message, e);
  end;
end;
Grüße
TurboMagic

TurboMagic 5. Apr 2024 07:52

AW: Problem mit Generator
 
Noch ein sachdienlicher Hinweis:
im Rahmen dieses Updateablaufes wird von Firebird 2.5.x auf 5.0 aktualisiert.
Ich hoffe mal nicht, dass das hier eine Rolle spielt, denn wie gesagt:
nach einmaligem Fehler funktioniert die weitere Verbuchung in der neuen Version problemfrei.

Grüße
TurboMagic

Frickler 5. Apr 2024 09:19

AW: Problem mit Generator
 
Zitat:

Zitat von TurboMagic (Beitrag 1535345)
Ich glaube mich jedoch zu erinnern, dass ich diese Art der Umsetzung
auf Basis einer Forumsdiskussion hier gemacht habe.

"Diese Art der Umsetzung" wurde früher gerne verwendet, weil sie kompatibel ist zum "TTable"-Ansatz der BDE und damit zu Datenbanken wie Paradox oder dBase (navigierender Zugriff oder ISAM).

P.S.: ich meine, es gab eine Änderung bei FB4, was den Startwert eines Generators betrifft. Denn das Problem betrifft - wenn ich das richtig sehe - immer den letzten Datensatz. Setz doch probehalber den Startwert auf "Value + 1" und schau, was passiert.

Delphi.Narium 5. Apr 2024 09:59

AW: Problem mit Generator
 
Oder statt
Delphi-Quellcode:
Query.ExecSQL('ALTER SEQUENCE ' + GeneratorName +                 ' RESTART WITH ' + Value.ToString);
für Value.ToString
Delphi-Quellcode:
select Max(id) + 1 from tabelle
nehmen.

Oder, da es ja bei diesem Update ein grundsätzliches Problem ist:
Delphi-Quellcode:
Value := 1 + GetCurrentGeneratorValue(FGeneratorReadQuery, Generator);

Oder hier einfach den nächsten Generatorwert auslesen, also sinngemäß:
Delphi-Quellcode:
Value := GEN_ID(GeneratorName, 1);


Das Problem scheint zu sein, dass in der "alten" Datenbank der Wert des Generators der zuletzt vergebene Wert zu sein scheint, während die "neue" Datenbank diesen Wert als den nächsten zu vergebenden Wert betrachtet. Beim ersten Lesen des Generatorwertes kracht es daher einmalig. Danach ist der Generatorwert dann "höher" als die letzte ID und somit ist der Fehler (unelegant) behoben.

Grob formuliert:

Alte Datenbank erhöht den Wert des Generators und gibt ihn dann aus.
Neue Datenbank gibt den Wert des Generators aus und erhöht ihn dann.

Daraus resultiert die einmalig "doppelte" Ausgabe eines Wertes, was dann nach einem einmaligen Fehler "korrigiert" erscheint.

TurboMagic 5. Apr 2024 12:33

AW: Problem mit Generator
 
Hallo,

danke an alle für den Tipp mit Firebird 4.0.
Das hab' ich damals scheinbar in der Änderungshistorie überlesen.
Ich schaue mir diese und eure Lösungsvorschläge in der nächsten Programmiersitzung
für die betreffende Anwendung gleich an.

Grüße

TurboMagic

TurboMagic 5. Apr 2024 15:59

AW: Problem mit Generator
 
Hallo,

ok, ich hab' jetzt schon die Stelle in den FB 4.0.4 relase notes gefunden.
Das Verhalten von Sequences wurde tatsächlich abgeändert.

Ich schau' mir jetzt an, wie ich damit umgehe.

Grüße
TurboMagic


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:07 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