![]() |
Delphi-Version: 5
Die Sache mit dem Listenproperty
Hi zusammen
Da man Pferde ja bekanntlich vom Kopf her aufzäumt, fang ich gleich mal damit an. Meinem Datenmodul habe ich ein Feld mit zugehörigem Setter, Getter und Property verpasst. In private:
Delphi-Quellcode:
und
FContentmastertables: TStringlist;
Delphi-Quellcode:
sowie schliesslich public:
function GetContentmasterTables: TStringlist;
Delphi-Quellcode:
Bei Programmstart greife ich ein erstes mal darauf zu:
property Contentmastertables: TStringlist read GetContentmasterTables;
Delphi-Quellcode:
Dies löst den Getter aus:
procedure TServerInfoFrame.CatalogInformation;
var i, j: Integer; Catalog: string; LIndent : String; CatNames: TStringlist; begin CatNames := TStringList.Create; try ... finally CatNames.Free; end; LIndent := ' - '; for i := 0 to LBxCatalogNames.Items.Count - 1 do begin ... Catalog := LBxCatalogNames.Items[i]; // CM_First.ProcedureReport.Add(LIndent + 'Catalog := '+ Catalog); if Catalog = 'contentmasterdata' then begin Memo1.Lines.Add('*************'); Memo1.Lines.Add(Catalog); Memo1.Lines.Add('------------'); Memo1.Lines.AddStrings(FDMySQLDml.Contentmastertables); // Löst offenbar die AV aus. Der GRund: Keine Rückgabe ...
Delphi-Quellcode:
Spätestens bei der Übrgabe an Result krachts...
function TFDMySQLDml.GetContentmasterTables: TStringlist;
var SqlString : String; I: integer; begin SqlString := 'SHOW TABLES'; FDQueryMain.Open(SqlString); FDQueryMain.First; while not FDQueryMain.Eof do begin FContentmastertables.Add(FDQueryMain.Fields.Fields[0].AsString); FDQueryMain.Next; end; Result.Clear; Result := (FContentmastertables); end; Ich habe jetzt mindestens einen Tag hinter mir. Dabei habe ich mir unter anderem ![]() ![]() Ich habe auch schon versucht, mit TStrings zu arbeiten. Aber irgendwie komm ich da nicht ganz klar. So, wie ich das verstanden habe, kann ich zwar eine Variable als TStrings deklarieren, muss dieser dann aber einen TStrings-Nachkommen "mit Hand und Fuss" zuweisen, bevor ich irgendwas befüllen kann. Da es sich bei der Funktion um einen Getter handelt, kann ich die Methode nicht einfach in eine Prozedur umwandeln. Was mache ich falsch?? Gruss Delbor |
AW: Die Sache mit dem Listenproperty
Was genau soll das Result.Clear; bewirken? An der Stelle ist doch Result noch gar nicht definiert...
|
AW: Die Sache mit dem Listenproperty
Hi Lemmy
Stimmt. Ich hab das aus einem Beispiel - ziemlich gedankenlos - übernommen. Andrerseits sollte es keinen Schaden anrichten. Gruss Delbor |
AW: Die Sache mit dem Listenproperty
Wo wird die Instanz von
Delphi-Quellcode:
erzeugt?
FContentmastertables
|
AW: Die Sache mit dem Listenproperty
Hi Uwe Raabe
Erzeugt wird das Feld im Daamodul.Create und im DataModuleDestroy wieder freigegeben. Das Feld selbst ist nicht das Problem, auch die Daten vom Query werden korrekt übergeben. Gruss Delbor |
AW: Die Sache mit dem Listenproperty
Zitat:
|
AW: Die Sache mit dem Listenproperty
Hi Uwe Raabe
Ups!! Genau das wars! Vielen Dank! Gruss Delbor |
AW: Die Sache mit dem Listenproperty
Da würde ich gleich Nägel mit Köpfen machen:
Delphi-Quellcode:
eine Stringliste als Ergebnis einer Funktion ist nicht so der Bringer.
procedure TFDMySQLDml.GetContentmasterTables(var Liste:TStringlist);
var SqlString : String; I: integer; begin SqlString := 'SHOW TABLES'; FDQueryMain.Open(SqlString); //FDQueryMain.First; {---- ist nicht notwendig} while not FDQueryMain.Eof do begin FContentmastertables.Add(FDQueryMain.Fields.Fields[0].AsString); FDQueryMain.Next; end; Liste.Clear; Liste:= (FContentmastertables); {---- was ist FContentmastertables ????} end; Gruß K-H |
AW: Die Sache mit dem Listenproperty
Mal grundsätzlich ist das so kein guter Ansatz:
|
AW: Die Sache mit dem Listenproperty
Ich würde vermutlich Lazy Initialization benutzen: ein privates Feld vom Typ TStrings oder gleich TStringList. Stellt der Getter nun beim ersten Aufruf fest, dass das Feld noch nil ist, erzeugt er die Liste und befüllt sie. Zum Schluss gibt er dann einfach das Feld zurück, was dann in jedem Fall eine gültige Instanz enthalten sollte. Jeder weitere Getter-Aufruf geht dann schneller, weil die Instanz ja schon vorhanden ist.
|
AW: Die Sache mit dem Listenproperty
Hmm, das finde ich nicht so gut. Damit gibst Du ja die Möglichkeit, dass ein privater Classmember von außen vernichtet werden kann.
Getreu dem Motto, dass jeder seinen eigenen Müll wegräumen soll, erwarte ich dann eine entsprechende Instanz eines TStrings-Nachfahren von außen. |
AW: Die Sache mit dem Listenproperty
Naja, wer das tut, ist ja selber Schuld. Oder gibst Du z.B. TComboBox.Items auch frei? ;)
|
AW: Die Sache mit dem Listenproperty
Hi zusammen
Zitat:
Zitat:
@sakura: Zitat:
Zitat:
Zitat:
Gruss Delbor |
AW: Die Sache mit dem Listenproperty
Hi
Zitat:
Zitat:
Zum Thema Rückgabewert, wenn irgendein Aufrufer die Liste zerstört (Destroy, Free), dann wird es Zugriffsverletzungen geben, entweder später oder beim Beenden. Deswegen die Frage, warum nicht ein Array zurück geben? ...:cat:... |
AW: Die Sache mit dem Listenproperty
Zitat:
Zitat:
|
AW: Die Sache mit dem Listenproperty
Zitat:
...:cat:... |
AW: Die Sache mit dem Listenproperty
@sakura: :thumb:
|
AW: Die Sache mit dem Listenproperty
Damit es Ruhe hat:
Delphi-Quellcode:
Damit kann man dann durch die Liste iterieren, hat aber keinen direkten Zugriff darauf.
type
TDingens = class private FTablenames: TStringList; function GetTablenames(Index: integer): string; function GetTablenameCount: integer; public constructor Create; destructor Destroy; override; property TablenameCount: integer read GetTablenameCount; property Tablenames[Index: integer]: string read GetTablenames; end; ... constructor TDingens.Create; begin FTablenames := TStringList.Create; FillTablenamesFormSomewhere(FTablenames); end; destructor TDingens.Destroy; begin FTablenames.Free; inherited; end; function TDingens.GetTablenames(Index: integer): string; begin Result := FTablenames[Index]; end; function GetTablenameCount: integer; begin Result := FTablenames.Count; end; |
AW: Die Sache mit dem Listenproperty
Zitat:
...:cat:... |
AW: Die Sache mit dem Listenproperty
*Pff* :tongue:
|
AW: Die Sache mit dem Listenproperty
Hi Sakura
Genaugenommen wird die Anfrage bei jedem lesezugriff auf das Property ausgeführt. Die Alternative wäre, die SQL-Anfrage ausserhalb des Datenmoduls dann da ausführen, wo ich sie brauche. Zum Bleistift in einem PageControl zur Anzeige von Metadaten auf der Mainform. Damit hätte ich dann eine weitere Prozedur in der Mainform, die es allenfalls zu ändern (und erstmal zu finden) gibt. Und wenn ich mehrere Informationen, für deren Beschaffung das Datenmodul zuständig ist, von ausserhalb abfrage, habe ich auch ausserhalb eine entsprechende Anzahl Methoden, die mir diese Infos beschaffen - und sollte sich irgendwas ändern, zum Bleistift an der Datenbank/Tabellenstruktur, darf ich alll diese Methoden ausserhalb des Datenmoduls da ändern, wo ich sie angelegt habe. Das muss nicht zwingend die Mainform sein, das kann eine andere Form oder ein Frame - im schlimmsten Fall pro Prozedure/SQL-Statement - sein. Wenn ich das im alles im Datenmodul habe, muss ichs auch nur da ändern. Auch wenn ich Methoden von ausserhalb nur aufrufe - wenn sich die Art des Aufrufens ändert (Parameter können sich ändern etc), darf ich alle diese Aufrufe an verschiedenen Stellen des Programmes ändern.
Delphi-Quellcode:
Eine gute Richtlinie ist es aber immer so zu entwickeln, als könnten solche Informationen an mehreren Stellen gleichzeitig gebraucht werden, das macht spätere Erweiterungen leichter. Auch bei Desktopanwendungen ist Multi-Threading schon lange keine Ausnahme mehr.
Ja eben: genau deswegen ist es besser, das Datenmodul stellt die Infos als Propery bereit. Ansonsten müsste jede dieser Stellen das SQL-Statement aufrufen. Das Statement selbst, bzw. desse Ausführung, dürfte vo der Performance her kaum ins Gewicht fallen. Und nochmals: sollte ich mit einem andern als dem Hauptthread auf das Property zugreifen, dann nur in einer Criticalsection. Anders sieht es allerdings aus, wenn ich meine BilderDatenbank abfrage: Hier werden die Ergebnisse pro Datensatz in eine Klasse geschrieben (und diese in eine Objectliste gepackt), so dass die Abfrage selbst im Normalfall nur einmal durchgeführt werden muss. Gruss Delbor |
AW: Die Sache mit dem Listenproperty
Hi,
leider hast du offenbar überhaupt nicht verstanden, was ich geschrieben habe. Zitat:
Zitat:
Zitat:
Zitat:
Warum lädst Du mit jeder Anfrage sich nicht ändernde Daten neu? Das ist grundsätzlich kein gutes Vorgehen. Selbst wenn es schnell geht, macht es schnell einen spürbaren Unterschied in der Geschwindigkeit, je nachdem, wie oft das gemacht wird. :gruebel: ...:cat:... |
AW: Die Sache mit dem Listenproperty
Hi Sakura
Ich hab mir DeddyHs Atwort zuunterst auf Seite eins nochmal angesehen. Meinst du die? Das könnte dann etwa so umgesetzt werden:
Delphi-Quellcode:
Stimmt, so wird die Abfrage nur einmal durchgeführt, vorausgesetzt die Tabelle ändert sich nicht. Was sie allerdings zumindest zur Laufzeit nie tun sollte.
function TFDMySQLDml.GetContentmasterTables: TStringlist;
var SqlString : String; I: integer; begin if FContentmastertables = Nil then then begin FContentmastertables := Tstringlist.Create; SqlString := 'SHOW TABLES'; FDQueryMain.Open(SqlString); FDQueryMain.First; while not FDQueryMain.Eof do begin FContentmastertables.Add(FDQueryMain.Fields.Fields[0].AsString); FDQueryMain.Next; end; FDQueryMain.Close; end; Result := (FContentmastertables); end; Gruss Delbor PS: Oder meinst du etwa dies aus Beitrag 18: Zitat:
Hier greift das Datenmudul nach oben*, was bei einer Trennung der Programmlogiken in verschiedene Schichten eben vermieden werden soll. *Zumindest, wenn ich den Aufruf richtig interpretiere. |
AW: Die Sache mit dem Listenproperty
Kombinier doch einfach alles:
Delphi-Quellcode:
type
TDingens = class private FTablenames: TStringList; function GetInternalTablenames: TStringList; function GetTablenames(Index: integer): string; function GetTablenameCount: integer; property InternalTablenames: TStringList read GetInternalTablenames; public destructor Destroy; override; property TablenameCount: integer read GetTablenameCount; property Tablenames[Index: integer]: string read GetTablenames; end; ... destructor TDingens.Destroy; begin FTablenames.Free; inherited; end; function TDingens.GetInternalTablenames: TStringList; begin if not Assigned(FTablenames) then begin FTablenames := TStringList.Create; (* Hier jetzt der Code zum Befüllen *) end; Result := FTablenames; end; function TDingens.GetTablenames(Index: integer): string; begin //Hier wird auf die interne Property zugegriffen Result := InternalTablenames[Index]; end; function GetTablenameCount: integer; begin //Hier auch Result := InternalTablenames.Count; end; |
AW: Die Sache mit dem Listenproperty
Hi DeddyH
Hier habe ich zumindest unter FireDac ein Problem. In einer definierten Verbindung kann ich genau eine Datenbank angeben - zumindest muss ich das so interpretieren, nachdem Embarcadero in allen Definitionsbeispielen immer genau eine Datenbank angibt und dabei nichts von einer Verbindung zu mehreren Datenbanken dazuschreibt. Einzig Verbindungspooling bleibt da möglich zu sein. Aber wie ich das bis jetzt verstanden habe, ist ein Pool eine Menge verschiedener Verbindungen, und ich denke, so würde dies auch der Server interpretieren. Und das führt dann dazu, dass jede Verbindung in diesem Pool aus Sicht des Servers ihre eigenen Sessionvariablen hat. Als Beispiel die Veränderung des Wertes FMaxAllowedPacket. Die entsprechende Tabelle zeige ich mir so an:
Delphi-Quellcode:
Den Wert kann ich dann so ändern:
'SELECT Variable_Value FROM performance_schema.SESSION_VARIABLES WHERE VARIABLE_NAME = ''MAX_ALLOWED_PACKET''';
Delphi-Quellcode:
FDQueryMain.SQL.Text := 'SET @@global.Max_ALLOWED_PACKET = '+ IntToStr(Variable_Value);
Das akzeptiert der Server, weil es sich dabei um den Wert handelt, der für meine Verbindung gilt. Zumindest interpretie ich das so, da ich keine Rückmeldung wegen fehlender Rechte erhalte. Ob das wirklich tut, was es soll, kann ich erst kontrollieren, wenn das Programm mal ohne AV startet. Unter DBExpress hats funktioniert... Führe ich nun aber ein einfaches 'Show Tables' aus, erhalte ich in jedem Fall die Tabellennamen der DB, mit der ich aktuell Verbunden bin. Das heisst aber, dass ich unter der aktuellen Verbindung nur lesend und mit einem SELECT-Statement auf die Serverdatenbank und deren Tabellen zugreifen kann. Die Tabellennamen in in der DB performance_schema erhalte ich so nicht. Aber nachdem ich mir deinen Code nochmal angeschaut habe: Ich bin offensichtlich noch nicht ganz wach. Dein Code zielt eigentlich auf das DRY-Prinzip ab. Mit meinem kurz davor geposteten Quelltext müsste ich die Passagen zur Erstellung der Liste jedesmal wiederholen. Trotzdem lasse ich die ersten 3 Abschnitte jetzt mal stehen; vielleicht hat ja jemand eine Lösung, wie das Problem behoben werden könnte. Auf jeden Fall mal vielen Dank für deinen Code! Gruss Delbor |
Alle Zeitangaben in WEZ +1. Es ist jetzt 00:17 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