![]() |
Datenbank: IB • Version: 6.x • Zugriff über: IBObjects
TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Hallo,
wir haben Aktionen, die sich zu großen Teilen gleichen. Dazu gibt es einen Programmteil, der für jede Aktion angepasst wird. Der Programmteil arbeitet mit einer Menge DB-Tabellen, die zum Teil aktionsübergreifend und zum Teil aktionsspezifisch sind. Die Datenbankfelder sind zur Designtime in die Queries importiert, um leichter darauf zugreifen zu können. Nun kommt es allerdings vor, dass einzelne Felder sich ändern, z.B. hat das Feld PRODUKT in der einen Aktion die Länge 80 und in einer anderen die Länge 255 (TField.Size). Es ist zu aufwendig bei jeder neuen Aktion von Hand alle Felder in den Queries mit den Längen der DB-Felder zu vergleichen. Hat aber z.B. das Feld in der Query die Länge 80 und das DB-Feld die Länge 255, so können Strings > 255 Zeichen eingelesen werden, es entstehen aber am Ende ein paar Zeichen Datenmüll. Das will ich verhindern, da zu vermuten ist, dass vielleicht unentdeckte Zugriffsverletzungen im Hintergrund stattfinden. Ich habe gefunden, dass man Size wie folgt anpassen kann:
Delphi-Quellcode:
for A := 0 to Dataset.FieldCount-1 do
begin AField := Dataset.Fields[A]; if AField is TStringField then TStringField(AField).Size := Dataset.FieldDefs.Find(AField.FieldName).Size; end;
Wir verwenden Delphi 5 und die IBObjects (Payware, nicht aktuell, kein Herstellersupport mehr), daran kann auch kurzfristig nichts geändert werden. Wer kennt sich evtl. besser mit speziell diesen Komponenten oder allgemein mit DB-Queries so gut aus, dass er mir zu diesem Problem einen Lösungsansatz anbieten kann? Ich habe bereits alle Ereignisse der TIBOQuery getestet (incl. OnCallback - falls ich das richtig verstanden habe): Entweder ist FieldDefs noch leer oder die Query ist bereits "geöffnet" und es kommt die oben genannte Fehlermeldung. Vielleicht lässt sich mit einem Interceptor etwas machen, aber ich habe den Quelltext von TIBOQuery nicht. |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Zitat:
Persistente Felder haben das Problem, dass die Definitionen von denen der Datenbank abweichen können. (Z.B. wenn Felder in der DB nachträglich verlängert wurden) Bei persistenten Feldern in Delphi gibt es leider nur eine Alles-oder-Nichts-Strategie. Entweder man hat persistente Felder oder man hat sie nicht und dann gelten die Felddaten aus der unterliegenden Datenbank. Leider ist es nicht möglich im Objektinspektor gezielt einige Felddaten anzupassen (z.B. nur EditFormat ändern). :cry: Bei diesen Nachteilen von persistenten Feldern, sollte man diese vermeiden wo es nur geht. Der "leichtere Zugriff", weil Delphi für jedes persistente Feld eine Komponente bereitstellt, gleicht die Nachteile nicht aus. Es gibt mindestens 5 Wege um auf ein Feld zuzugreifen:
Delphi-Quellcode:
Also mein Rat:
var
strName: string; begin strName := Query.Fields[0].AsString; strName := Query.FieldByName('LastName').AsString; // bevorzugter Weg strName := Query.FieldValues['LastName']; strName := Query['LastName']; // inbesondere beim schreibenden Zugriff sinnvoll strName := QueryLastName.AsString; // nur bei persistenten Feldern Lösche die persistenten Felder und benütze die Variante "bevorzugter Weg" von oben. |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Da es tausende Referenzen auf persistente Datenbankfelder in unserem Code gibt, kann ich diese nicht alle umarbeiten. Wir haben uns jetzt darauf geeinigt Meldungen auszugeben, wenn Differenzen in den Feldlängen auftreten und dann diese zu entfernen, bevor das Programm in den Produktiveinsatz kommt. Das ist keine schöne Lösung, aber bis die Neuimplementierung des gesamten Programms fertig ist, die nebenbei läuft, ist sie gut genug.
|
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Ich würde zusätzlich auch empfehlen im Sourcecode vermehrt die Klasse TDataset zu benützen.
Ich vermute mal dass der Code strukturmässig so aussieht:
Delphi-Quellcode:
Alles ist fest verdrahtet und unflexibel.
procedure TForm21.CalculateBruttowert;
begin Datamodule21.Query12.Edit; Datamodule21.Query12Bruttowert.Value := Datamodule21.Query12Nettowert.Value * (1.0 + DataModule13.Query1Mwst.Value / 100.0); ... Datamodule21.Query12.Post; end; Jede Änderung zieht einen Rattenschwanz nach sich. Und nun mit dem Dataset:
Delphi-Quellcode:
Jede Wette, es gibt Hunderte Stellen, die man so verbessern kann.
procedure TForm21.CalculateBruttowert(ds:TDataset; mwstprozent:double);
begin ds.Edit; ds['Bruttowert'] := ds.FieldByName('Bruttowert').AsFloat * (1.0 + mwstprozent / 100.0); ... ds.Post; end; // Aufruf CalculateBruttowert(Datamodule21.Query12, DataModule13.Query1['Mwst']); |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Du hast absolut recht, es gibt hunderte solcher Stellen - oder anders gesagt besteht unser gesamter Quellcode aus sowas. Und es gibt noch massig andere, viel schwerwiegendere Design-Flaws mit denen wir uns rumärgern. Ich habe z.B. schon tonnenweise globale Variablen entsorgt. Visuelle Vererbung wäre auch toll einsetzbar gewesen. Deswegen wird ja das ganze Programm neu entwickelt. Es hat einfach keinen Sinn, da noch etwas zu refaktorieren, wir müssen nur damit klarkommen, bis die neue Version fertig ist, also noch einige Monate.
|
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Leider hat es aber auch was mit Performance zu tun. Der Zugriff über FieldbyName ist bis zu 4x langsamer als wenn man ein FeldObjektt benutzt. Deshalb verwenden wir zwar, ausser in datensensitiven Formularen zwar auch FieldByName, aber bei Schleifen mit entsprechenden Optimierungen, so dass es nur einen Aufruf ausserhalb der Schleifen gibt:
Delphi-Quellcode:
var
QryBestandID : TField; begin ... QryBestandId := QryBestand.FieldByName('Id'); ... while not QryBestand.Eof do begin if QryBestandId.AsFloat ... ... qryBestand.Next; end; end; |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
@Union: Genau, da spart man so einige Nanosekunden, weil der String nicht mit allen Feldnamen der Query verglichen werden muss - Buchstabe für Buchstabe. Das ist bei heutigen Rechnerleistungen schon ein erheblicher Performancegewinn. Aber ich erwische mich auch manchmal bei solchen "sinnigen" Optimierungen...
Das einzige, was es heute noch zu optimieren gilt, sind Netzwerkzugriffe, Festplattenzugriffe und vielleicht Bildausgaben. Und vielleicht verschachtelte Schleifen mit Millionen von Durchläufen. 3GHz sind 3 Milliarden Takte pro Sekunde, das Ganze in der Regel auf mehreren Kernen (die anderen Prozesse unterbrechen den Hauptprozess nicht mehr so oft = weniger zeitintensive Prozesswechsel notwendig). Wenn eine komplette Operation 10 Takte dauert, dann sollte man pro Operation dank Pipelining (Jede Operation ist in 10 Einzelschritte zerlegt - während der zweite Schritt der ersten Operation läuft, läuft parallel der erste Schritt der zweiten Operation - durch Abhängigkeiten kann es allerdings sein, dass die nächste notwendige Operation erst feststeht, wenn die vorige abgeschlossen ist, dann beginnt die nächste erst nach dem letzten Schritt/Takt der vorigen Operation) im Schnitt trotzdem nicht mehr als 2 bis 3 Takte rechnen müssen. Das Vergleichen eines Buchstabens ist übrigens eine Operation. Bei einer durchschnittlichen Feldnamenlänge von 20 Buchstaben und unter der Annahme, dass die Feldnamen immer komplett verglichen werden müssten (was sie nicht müssen) und 20 Feldern pro Query, sind das im schlimmsten Fall 400 Operationen, nur um den richtigen Pointer zu finden - also ein Bruchteil einer Mikrosekunde. Worst-Case! |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Zitat:
Wenn diese "Nanosekunden" sich allerdings addieren, kommt u.U. ganz schön was heraus - z.b. wenn es vorher 870 µs und hinterher 446 µs sind. Und sich dadurch die durchschnittliche Verarbeitungsdauer einer Einheit von 2,081 s auf 1,598 s reduziert. Und der Kunde 5000 Einheiten verarbeiten muss. Das ergibt eine Zeitersparnis von ca. 40 Minuten. Daher können die Mitarbeiter die Einheiten schneller abarbeiten und erzielen eine größere Performance. Bei 20-30 Mitarbeitern spart dadurch die Firma jeden Tag Geld durch den Performancegewinn. Unwesentlich, ich weiss ;) |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Zitat:
Ich mache jetzt Feierabend und anschließend Wochenende. |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Mein Tipp:
Im AfterOpen eines Datensatzes XY zerstörst Du alle persistenten Felder dieses Datensatzes und weisst Sie anschließend neu zu. So etwa
Delphi-Quellcode:
Das für jedes Dataset und -wupps- hast Du die richtigen Längen und(!) keine weitere Änderungen bei dir im Code.
Procedure TMyDataModule.MyDatasetAfterOpen(Dataset : TDataset);
Begin if not MyDatasetInitialized then begin MyDatasetField1.Free; ... MyDatasetField99.Free; MyDatasetInitialized := True; end; MyDatasetField1 := MyDataSet['Field1']; ... MyDatasetField99 := MyDataSet['Field99']; End; |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
@Furtbichler: Das ist mal ein radikaler Ansatz, den werde ich definitiv am Montag testen und mich dann hier wieder melden. Es wird aber wohl gegen Feierabend werden, da der Vormittag bereits verplant ist und daraus meist neue dringende Aufgaben entstehen... Aber der Ansatz ist es auf alle Fälle Wert die Sache noch einmal anzufassen!
Danke und schönes Wochenende! |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Gestern hatte ich keine Zeit mehr diese Sache anzugehen. Soeben habe ich das Vorgehen etwas genauer unter die Lupe genommen und bin auf folgende Schwierigkeiten gestoßen:
Trotzdem danke für die Idee. |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Hi,
IBObjects erstellt die Felder ja schon beim Prepare, nicht erst nach dem Öffnen. So könnte man nach dem Prepare den Check durchführen. U.U. den Select in eine Temporäre Query, Preparen und mit der richtigen Query vergleichen. Frank |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Wie ich im ersten Beitrag schrieb, sind die FieldDefs, die die wahren Stringlängen aus der DB enthalten, erst nach dem Öffnen der Query verfügbar. Ich will die Query auch nicht zweimal öffnen - auch keine temporäre Query. Da ich das Ganze nicht für jede Query extra programmiere, sondern zentral für alle Queries, kenne ich auch das ausgeführte SQL nicht. Sollte dieses mal kein reines SELECT enthalten, wäre eine mehrfache Ausführung u.U. schlichtweg falsch.
|
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Zitat:
Setz doch einfach mal das Ereignis AfterPrepare. Und dann eine Schleife durch die Felder:
Delphi-Quellcode:
Frank
for ...
if qry.Fields[2].Size <> qry.FieldDefs[2].Size then qry.Fields[2].Size := qry.FieldDefs[2].Size; [EDIT] Kann natürlich Probleme geben, wenn das Active der Query auch schon zur Entwurfszeit gesetzt ist. Aber das kann ich ja nicht wissen. [/EDIT] |
AW: TField.Size zur Laufzeit anpassen ohne doppeltes Öffnen der Query
Dann ist das offenbar in einer neueren Version geändert worden. Wir haben eine etwas ältere, welche genau kann ich auf die Schnelle nicht sagen. Jedenfalls habe ich alle Ereignisse selbst ausprobiert mit unserer Version der IBObjects, bevor ich diesen Thread begonnen habe. Bei unserer Version sind sie im AfterPrepare noch nicht verfügbar.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:58 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz