Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Die Sache mit dem Listenproperty (https://www.delphipraxis.net/191901-die-sache-mit-dem-listenproperty.html)

Delbor 1. Mär 2017 20:28

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:
FContentmastertables: TStringlist;
und
Delphi-Quellcode:
function GetContentmasterTables: TStringlist;
sowie schliesslich public:
Delphi-Quellcode:
property Contentmastertables: TStringlist read GetContentmasterTables;
Bei Programmstart greife ich ein erstes mal darauf zu:
Delphi-Quellcode:
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
      ...
Dies löst den Getter aus:

Delphi-Quellcode:
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;
Spätestens bei der Übrgabe an Result krachts...

Ich habe jetzt mindestens einen Tag hinter mir. Dabei habe ich mir unter anderem dies und das.

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

Lemmy 1. Mär 2017 20:36

AW: Die Sache mit dem Listenproperty
 
Was genau soll das Result.Clear; bewirken? An der Stelle ist doch Result noch gar nicht definiert...

Delbor 1. Mär 2017 21:22

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

Uwe Raabe 1. Mär 2017 21:35

AW: Die Sache mit dem Listenproperty
 
Wo wird die Instanz von
Delphi-Quellcode:
FContentmastertables
erzeugt?

Delbor 1. Mär 2017 21:43

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

Uwe Raabe 1. Mär 2017 21:56

AW: Die Sache mit dem Listenproperty
 
Zitat:

Zitat von Delbor (Beitrag 1362979)
Andrerseits sollte es keinen Schaden anrichten.

Ähm - doch! Da Result an der Stelle nicht definiert ist, kann alles Mögliche drin stehen (auch nil). In jedem Fall ist der Aufruf Result.Clear zu entfernen. Schau mal, ob es dann funktioniert.

Delbor 1. Mär 2017 22:28

AW: Die Sache mit dem Listenproperty
 
Hi Uwe Raabe

Ups!! Genau das wars! Vielen Dank!

Gruss
Delbor

p80286 2. Mär 2017 13:36

AW: Die Sache mit dem Listenproperty
 
Da würde ich gleich Nägel mit Köpfen machen:
Delphi-Quellcode:
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;
eine Stringliste als Ergebnis einer Funktion ist nicht so der Bringer.

Gruß
K-H

sakura 2. Mär 2017 13:47

AW: Die Sache mit dem Listenproperty
 
Mal grundsätzlich ist das so kein guter Ansatz:
  • Wenn mehrere Threads gleichzeitig auf die Eigenschaft zugreifen, wirst Du sehr seltsame Ergebnisse bekommen
  • Ändert sich das Ergebnis so rasant, dass es Sinn mach mit jedem Zugriff alles neu zu ermitteln
  • Macht evtl. eine lokale StringListe mehr Sinn/bzw als Rückgabetyp ein Array?
  • ...
...:cat:...

DeddyH 2. Mär 2017 14:59

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.

TBx 2. Mär 2017 19:11

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.

DeddyH 2. Mär 2017 19:15

AW: Die Sache mit dem Listenproperty
 
Naja, wer das tut, ist ja selber Schuld. Oder gibst Du z.B. TComboBox.Items auch frei? ;)

Delbor 2. Mär 2017 23:54

AW: Die Sache mit dem Listenproperty
 
Hi zusammen
Zitat:

Ich würde vermutlich Lazy Initialization benutzen: ein privates Feld vom Typ TStrings oder gleich TStringList.
Eigentlich habe ich ja genau dies - mit TStrings statt TStringlist - umzusetzen versucht und erstmal eine AV erhalten.

Zitat:

Was genau soll das Result.Clear; bewirken? An der Stelle ist doch Result noch gar nicht definiert...
...
Zitat von Delbor:
Andrerseits sollte es keinen Schaden anrichten.

Ähm - doch! Da Result an der Stelle nicht definiert ist, kann alles Mögliche drin stehen (auch nil). In jedem Fall ist der Aufruf Result.Clear zu entfernen. Schau mal, ob es dann funktioniert.
Nach der Antwort von Uwe Raabe in Beitrag sechs und da es seit Entfernung des "Result:=True;" genau das tut, was es sollte, bin ich überzeugt, dass ich FContentmastertables auch als TStrings deklarieren könnte. Das würde auch deinem verlinkten (bis auf TStringlist) Beispiel unter Lazy Initialization entsprechen.

@sakura:
Zitat:

Mal grundsätzlich ist das so kein guter Ansatz:

Wenn mehrere Threads gleichzeitig auf die Eigenschaft zugreifen, wirst Du sehr seltsame Ergebnisse bekommen
Es gibt in dieser Anwendung nur den einen Thread. Einen zweiten wird es wohl nicht geben. Denn trotz dem Einsatz von MySQL handelt es sich um eine Desktopanwendung. Und auch wenn es mehrere Threads neben dem Haupttread geben sollte. müssten diese in einer Criticalsection auf den Speicher zugreifen. Dazu käme wohl noch der Zugriff auf ein Objekt...
Zitat:

Ändert sich das Ergebnis so rasant, dass es Sinn mach mit jedem Zugriff alles neu zu ermitteln
Das Ergebnis dient dazu, die Tabellen der DB anzuzeigen. Das Property soll die Tabellen nach oben zur Verfügung stellen; die Gui muss nur daruf zugreifen. Ich weiss: dazu gibt es bei Firedac die Connection-Komponente.

Zitat:

Macht evtl. eine lokale StringListe mehr Sinn/bzw als Rückgabetyp ein Array?
Da FContentmastertables ein privates Feld des Datenmoduls ist und in dessen Create-bzw Destroy-Prozedure erzeugt/gelöscht wird, macht eine private Liste keinen Sinn, ausser dass sie bei jedem Zugriff erstellt/zerstört werden müsste.

Gruss
Delbor

sakura 3. Mär 2017 08:51

AW: Die Sache mit dem Listenproperty
 
Hi
Zitat:

Zitat von Delbor (Beitrag 1363083)
Es gibt in dieser Anwendung nur den einen Thread. Einen zweiten wird es wohl nicht geben. Denn trotz dem Einsatz von MySQL handelt es sich um eine Desktopanwendung. Und auch wenn es mehrere Threads neben dem Haupttread geben sollte. müssten diese in einer Criticalsection auf den Speicher zugreifen. Dazu käme wohl noch der Zugriff auf ein Objekt...

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.
Zitat:

Zitat von Delbor (Beitrag 1363083)
Das Ergebnis dient dazu, die Tabellen der DB anzuzeigen. Das Property soll die Tabellen nach oben zur Verfügung stellen; die Gui muss nur daruf zugreifen. Ich weiss: dazu gibt es bei Firedac die Connection-Komponente.

Warum dann lädst Du die Liste der Tabellen mit jeder Anfrage, anstatt nur einmal. Ändern werden sich die Tabellen ja wohl kaum...?

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:...

TBx 3. Mär 2017 08:56

AW: Die Sache mit dem Listenproperty
 
Zitat:

Zitat von sakura (Beitrag 1363089)
Hi
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:...

[Ironie]oooch, das wurde doch schon abgebacken:
Zitat:

Zitat von DeddyH (Beitrag 1363063)
Naja, wer das tut, ist ja selber Schuld. Oder gibst Du z.B. TComboBox.Items auch frei? ;)

[/Ironie]

sakura 3. Mär 2017 09:00

AW: Die Sache mit dem Listenproperty
 
Zitat:

Zitat von TBx (Beitrag 1363091)
[Ironie]oooch, das wurde doch schon abgebacken:
Zitat:

Zitat von DeddyH (Beitrag 1363063)
Naja, wer das tut, ist ja selber Schuld. Oder gibst Du z.B. TComboBox.Items auch frei? ;)

[/Ironie]

Ich weiß, und auch das finde ich persönlich eher unschön, ist aber den alten Zeiten zu danken und lässt sich heute auch nicht mehr ändern...

...:cat:...

TBx 3. Mär 2017 09:08

AW: Die Sache mit dem Listenproperty
 
@sakura: :thumb:

DeddyH 3. Mär 2017 09:22

AW: Die Sache mit dem Listenproperty
 
Damit es Ruhe hat:
Delphi-Quellcode:
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;
Damit kann man dann durch die Liste iterieren, hat aber keinen direkten Zugriff darauf.

sakura 3. Mär 2017 09:50

AW: Die Sache mit dem Listenproperty
 
Zitat:

Zitat von DeddyH (Beitrag 1363097)
Damit es Ruhe hat

Ach komm, biete doch noch einen Listen-Enumerator an :mrgreen:

...:cat:...

DeddyH 3. Mär 2017 10:16

AW: Die Sache mit dem Listenproperty
 
*Pff* :tongue:

Delbor 3. Mär 2017 10:55

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

sakura 3. Mär 2017 11:44

AW: Die Sache mit dem Listenproperty
 
Hi,

leider hast du offenbar überhaupt nicht verstanden, was ich geschrieben habe.

Zitat:

Zitat von Delbor (Beitrag 1363109)
Genaugenommen wird die Anfrage bei jedem lesezugriff auf das Property ausgeführt.

Korrekt.
Zitat:

Zitat von Delbor (Beitrag 1363109)
Die Alternative wäre, die SQL-Anfrage ausserhalb des Datenmoduls dann da ausführen, wo ich sie brauche.

Diese Änderung habe ich nicht einmal angedeutet, wäre mir auch nie in den Sinn gekommen.
Zitat:

Zitat von Delbor (Beitrag 1363109)
genau deswegen ist es besser, das Datenmodul stellt die Infos als Propery bereit.

Datenmodul ist eine einfache, nicht sehr schöne Lösung, aber praktikabel. Da wollte ich auch nicht ran.
Zitat:

Zitat von Delbor (Beitrag 1363109)
Und nochmals: sollte ich mit einem andern als dem Hauptthread auf das Property zugreifen, dann nur in einer Criticalsection.

Also zurück zu meiner Idee, welche DeddyH auch schon gut als Code gezeigt hat. Warum nicht einfach EINMAL die Daten ermitteln und anschließend einfach als fertige Liste zur Verfügung stellen. Gerne auch als TStrings - die Nach-/Vorteile sind ja bekannt. Dann würdest Du auch keine CS in multi-threading benötigen (mit der Einschränkung keiner ändert jemals die Werte nach dem initialen Befüllen).

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:...

Delbor 3. Mär 2017 13:23

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:
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;
Stimmt, so wird die Abfrage nur einmal durchgeführt, vorausgesetzt die Tabelle ändert sich nicht. Was sie allerdings zumindest zur Laufzeit nie tun sollte.

Gruss
Delbor

PS: Oder meinst du etwa dies aus Beitrag 18:
Zitat:

constructor TDingens.Create;
begin
FTablenames := TStringList.Create;
FillTablenamesFormSomewhere(FTablenames);
end;
Das gefällt nun mir nicht wirklich. Und DeddyH selbst wohl auch nicht.
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.

DeddyH 3. Mär 2017 18:31

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;

Delbor 4. Mär 2017 10:37

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:
'SELECT Variable_Value FROM performance_schema.SESSION_VARIABLES WHERE VARIABLE_NAME = ''MAX_ALLOWED_PACKET''';
Den Wert kann ich dann so ändern:

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