Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Getter wird übergangen (https://www.delphipraxis.net/152302-getter-wird-uebergangen.html)

Medium 17. Jun 2010 17:07

Getter wird übergangen
 
Ahoi!

Ich habe hier folgende Struktur (eine Objekt-Relationale Abbildung zu einer DB, Delphi 2010):
Delphi-Quellcode:
  TDB = class
  private
    type
      TDBUser = class
      private
        class var FID: Integer;
        class function GetName: string; static;
        class procedure SetName(const Value: string); static;
        // für jedes DB feld eben...
      public
        class property ID: Integer read FID;
        class property Name: string read GetName write SetName;
      end;
    class var FQuery: TUniQuery;
    class var FUser: TDBUser;
    class function GetDBUserByLogin(aLogin: string): TDBUser; static;
  public
    class property UsersByLogin[aLogin: string]: TDBUser read GetDBUserByLogin;
  end;

implementation

class function TDB.GetDBUserByLogin(aLogin: string): TDBUser;
begin
  Query.SQL.Text := 'SELECT id FROM users WHERE login=:lg';
  Query.ParamByName('lg').AsString := aLogin;
  Query.Open;
  if Query.RecordCount=0 then
  begin
    Query.Close;
    raise EUserDoesNotExistException.Create();
  end;
  FUser.FID := Query.FieldByName('id').AsInteger;
  result := FUser;
  Query.Close;
end;
Um die ID eines Users anhand seines Logins zu erhalten, tue ich in einer anderen Unit dies:
Delphi-Quellcode:
constructor TUser.Create(aLogin, aPassword: string);
var
  id: Integer;
begin
  if FInstanceCounter > 0 then
    raise Exception.Create('Es ist bereits ein User angemeldet. Melden Sie diesen ab, bevor Sie sich einloggen.');

  try
    id := TDB.UsersByLogin[aLogin].ID; // !!! Hier wichtig !!! :)
  except
    raise Exception.Create('Benutzer '''+aLogin+''' existiert nicht.');
  end;

  if TDB.Users[id].Password <> aPassword then
    raise Exception.Create('Ungültiges Passwort für Benutzer '''+aLogin+'''.');

  FId     := id;
  FLogin  := aLogin;
  FName   := TDB.Users[id].Name;
  FSurname := TDB.Users[id].Surname;
  FRights := TDB.Users[id].Rights;

  inc(FInstanceCounter);
end;
An der markierten Stelle passiert es dann: NICHTS!
Es wird niemals die Methode GetDBUserByLogin() durchlaufen, und "id" hat nachher immer den Wert "0" (diese ID gibt es nicht in der DB). Ich erhalte keine Hinweise/Warnungen vom Compiler, kann einen Haltepunkt auf die oben markierte Zeile setzen, ein solcher wird in der Methode GetDBUserByLogin() jedoch deaktiviert. (Das ist die einzige Überladung davon.)
Löschen der DCUs hat leider nichts gebracht, sowie auch das testweise entfernen des try-Blocks. Auch die Optimierung abzuschalten hat an diesem Verhalten nichts geändert.

Bin es nun ich, der einen Denkfehler hat und ihn nicht findet, oder wäre das es wert dem Support vorzutragen? :gruebel:

Besten Dank schon mal!
Medium

shmia 17. Jun 2010 17:27

AW: Getter wird übergangen
 
Wieso hast du denn ausschliesslich statische class-Methoden?
Das ist sehr unschön und damit verbaust du Dir alle Möglichkeiten für die Zukunft.
Die Klasse TDBUser sollte auf jeden Fall eine ganz normale Klasse sein; ungefähr so:
Delphi-Quellcode:
TDBUser = class
private
 FID: Integer;
 FName : string;
public
  property ID: Integer read FID;
  property Name: string read GetName write SetName;
  property LastLogon:TDateTime read FLastLogon;
end;
Die Funktion GetDBUserByLogin() sieht dann ungefähr so aus:
Delphi-Quellcode:
class function TDB.GetDBUserByLogin(aLogin: string): TDBUser;
begin
  Query.SQL.Text := 'SELECT id FROM users WHERE login=:lg';
  Query.ParamByName('lg').AsString := aLogin;
  Query.Open;
  if Query.IsEmpty {besser als RecordCount=0} then
  begin
    Query.Close;
    raise EUserDoesNotExistException.Create(aLogin {welcher user nicht existiert ist wichtig zu wissen});
  end;
  result := TDBUser.Create; // neues Obj erzeugen !
  result.ID  := Query.FieldByName('id').AsInteger;
  result.Name := Query.FieldByName('Name').AsString;
  ....
  Query.Close;
end;
Der Aufrufer GetDBUserByLogin() muss dann das TDBUser-Objekt freigeben.
Falls Dir das nicht gefällt, kann man auch auf Interfaces ausweichen.
Damit werden die TDBUser-Objekte automatisch freigegeben (über die Referenzzählung).

Medium 17. Jun 2010 17:35

AW: Getter wird übergangen
 
Eben genau die Freigabefrage war der Grund für die Klassenmethoden. Es würde ja praktisch für jeden Zugriff auf einen User, wie klein auch immer, eine neue Instanz erstellt, die dann irgendwer freigeben müsste. Da die Instanz selber aber niemanden interessiert, sondern nur das, was die Properties zurück geben (das sind intern SQL Abfragen), wird auch nirgends eine Referenz auf diese behalten. Das möchte ich auch keinesfalls!

Da mir Interfaces recht unsympathisch sind, muss ich mal fragen, was ich mir so verbaue? Ableiten soll da ohnehin nie mehr jemand von, deswegen ist es ja eine private Klasse, und ich sollte sie ggf. auch noch sealed machen. Das ist auf eine ganz spezielle DB zugeschnitten, und wer auch immer darin etwas ändert hat a) auch Zugang zum Source, und bekommt b) was auffe Löffel :).

Das seltsame Verhalten erklärt das allerdings leider noch nicht :?

Edit: IsEmpty ist schöner, danke! Welcher User da nicht gefunden wurde, wird im Moment eine Aufruftiefe höher behandelt. Da weiss ich ja auch mit welchem Login ich nachgefragt habe, und on EUserDoesNotExistException wird jetzt explizit mit einem MessageDlg behandelt. Man könnte das noch als Parameter an die Exception geben, Sinn würd das auch machen. Denken :) Macht viel Sinn, habs angepasst.

himitsu 17. Jun 2010 17:37

AW: Getter wird übergangen
 
OK, statt dem Interface würde ein Record das selbe Ergebnis bieten.
- mehrere solcher Container gleichzeitig möglich
- Delphi kümmert sich automatisch um die Freigabe
Delphi-Quellcode:
TDBUser = record
private
  FID: Integer;
  FName: string;
  FLastLogon: TDateTime;
public
  property ID: Integer read FID;
  property Name: string read FName;
  property LastLogon: TDateTime read FLastLogon;
end;
[edit]
in Delphi 7 dann ein normaler Record ohne Property (die waren eh nur für ein ReadOnly gedacht)

Medium 17. Jun 2010 17:50

AW: Getter wird übergangen
 
Das könnte sicherlich auch gut klappen, und wäre ggf. eine Überlegung wert. Ich hatte beim Basteln .NET im Hinterkopf, wo sowas dank GC ja usus ist.

Allerdings hilft mir das bei meinem eigentlichen Problem kein Stück weiter, da der Getter von TDB, nicht von TDB.TDBUser betroffen ist, also eine Ebene höher. Welchen Typ TDBUser dann hat, ist wohl relativ unerheblich hier, auch wenn ich die Ideen gerne annehme! Das hat nur leider nix mit dem Problem an sich zu tun :?

himitsu 17. Jun 2010 18:08

AW: Getter wird übergangen
 
Erstmal wäre es gut, daß du erwähnst, wenn du eine andere Delphiversion, als die im Profil erwähnte, verwendest.
(inline-Typen gingen in D7 doch noch garnicht)

ups, übersehn :oops:

Dann kann es sein, daß der Compiler, Aufgrund deiner komischen Sichtbarkeitsreglungen, durchdreht.
Dein TDBUser ist als Private deklariert, aber du willst es in Public verwenden, als Result für eine Public-Methode/Property.

DeddyH 17. Jun 2010 18:10

AW: Getter wird übergangen
 
Delphi 2010 steht aber ganz klar im Ausgangspost :zwinker:

Medium 17. Jun 2010 18:19

AW: Getter wird übergangen
 
Zitat:

Zitat von himitsu (Beitrag 1029743)
Dann kann es sein, daß der Compiler, Aufgrund deiner komischen Sichtbarkeitsreglungen, durchdreht.
Dein TDBUser ist als Private deklariert, aber du willst es in Public verwenden, als Result für eine Public-Methode/Property.

So komisch sind die eigentlich garnicht. In C# hab ich solche Konstrukte schon haufenweise verwendet. Ohne Probleme.

Ich habe aber testweise die TDBUsers als ganz eigenständige Klasse gesetzt, was leider noch immer nichts an dem Problem geändert hat. GetDBUserByLogin() wird noch immer geflissentlich gemieden, obwohl explizit auf die zugehörige Property lesend zugegriffen wird :?. Daran lag es leider nicht. (Was heisst leider... das wäre recht dümmlich, wenn sowas nicht in Delphi ginge :))


EDIT: Okay, lustig...
Ich habe noch eine weitere Property "Users", die wie "UsersByLogin" arbeitet, aber eben eine ID statt eines Logins nimmt:
Delphi-Quellcode:
  TDB = class
  private
    class var FQuery: TUniQuery;
    class var FUser: TDBUser;
    class function GetDBUser(aUserID: Integer): TDBUser; static;
    class function GetDBUserByLogin(aLogin: string): TDBUser; static;
  public
    class property Users[aUserID: Integer]: TDBUser read GetDBUser;
    class property UsersByLogin[aLogin: string]: TDBUser read GetDBUserByLogin;
  end;

implementation

class function TDB.GetDBUser(aUserID: Integer): TDBUser;
begin
  FUser.FID := aUserID;
  result := FUser;
end;

Folgenden Methode:
Delphi-Quellcode:
procedure TframeUsers.btnResetPasswordClick(Sender: TObject);
var
  id: Integer;
begin
  id := qryUsers.FieldByName('id').AsInteger; // <----  !!!
  TDB.Users[id].Password := '';
end;
Hier wird mir vom Compiler gemeldet, dass der auf "id" zugewiesene Wert nicht mehr verwendet wird! :shock:

implementation 17. Jun 2010 18:29

AW: Getter wird übergangen
 
Zitat:

Zitat von Medium (Beitrag 1029749)
So komisch sind die eigentlich garnicht. In C# hab ich solche Konstrukte schon haufenweise verwendet. Ohne Probleme.

:shock: Welche VS-Version hast du? Also 2010 lässt es ganz sicher nicht zu, dass man in einer Public-Methode einen Private-Typ zurückliefert. Hab ich selber schon mehrmals versucht.

Ist ja auch total sinnlos. So verhinderst du ja, dass man den Rückgabewert in einer Variable speichern kann... Wat'n Schwachsinn...

himitsu 17. Jun 2010 18:31

AW: Getter wird übergangen
 
Eventuell schlägt sich auch hier wieder ein Fehler im Compiler nieder?

Versuch es mal so:
Delphi-Quellcode:
TDB = class
public
  type
    TDBUser = record
    private
      FID: Integer;
      // für jedes DB feld eben...
    public
      property ID: Integer read FID;
      property Name: string read FName;
    end;
private
  class var FQuery: TUniQuery;
  class var FUser: TDBUser;
  class function GetDBUserByLogin(aLogin: string): TDBUser; static;
public
  abc: Integer; // dummies
  xyz: String;  //
  class property UsersByLogin[aLogin: string]: TDBUser read GetDBUserByLogin;
end;
Ansonsten wüßte ich auch nicht weiter (jedenfalls nicht ohne mal ein Testprojekt zum Testen zu haben).


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:00 Uhr.
Seite 1 von 2  1 2      

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