Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi [RTTI] RttiProperty aus Property von Instanz erzeugen (https://www.delphipraxis.net/149628-%5Brtti%5D-rttiproperty-aus-property-von-instanz-erzeugen.html)

s.h.a.r.k 25. Mär 2010 15:13


[RTTI] RttiProperty aus Property von Instanz erzeugen
 
Hallo,

hänge gerade mal wieder an einem neuen Problem mit Rtti und ich weiß nicht, wie ich das machen sollte. Ich habe mehrere Properties, die mit Werten aus der Datenbank befüllt werden. Die Setter-Methoden dieser Properties sollen auf eine generische Methode verweisen, die das Setzen der Werte für alle relevanten Properties übernehmen kann. Wichtig hierbei ist die Prüfung auf das Attribut NotNull, d.h. beim Setzen eine Wertes wird geprüft, ob ein Null-Wert gesetzt werden darf oder nicht.

Hier ein vereinfachtes Szenario, sodass das verständlicher wird:

Delphi-Quellcode:
TBlub = class(TObject)
private
  FNotNullProperties : TStringList; // wird im Konstruktor mit
  FPropA : Variant;
  FPropB : Variant;
  procedure GenericSetter(const AValue: Variant; const ADestVar: PVariant; const AProperty: TRttiProperty);
  procedure SetPropA(const AValue: Variant);
  procedure SetPropB(const AValue: Variant);
public
  [NotNull]
  property PropA : Variant read FPropA write SetPropA;
  property PropB : Variant read FPropB write SetPropB;
end;

procedure TBlub.GenericSetter(const AValue: Variant; const ADestVar: PVariant; const AProperty: TRttiProperty);
var
  rContext : TRttiContext;
  rType : TRttiType;
begin
  if (AValue <> ADestVar^) then
  begin
    if (AValue <> Null) then
    begin
      ADestVar^ := AValue;
    end
    else begin
      // Prüfen, ob Property überhaupt Null werden darf, denn wenn nicht dann muss eine Exception
      // geworfen werden
      if (FNotNullProperties.IndexOf(AProperty.Name) <> -1) then
        raise Exception.Create('...');
      ADestVar^ := Null;
    end;
  end;
end;

procedure TBlub.SetPropA(const AValue: Variant);
begin
  GenericSetter(AValue, FPropA, GetRttiProperty(PropA)); // GetRttiPropertyist eine fiktive Funktion
end;

procedure TBlub.SetPropB(const AValue: Variant);
begin
  GenericSetter(AValue, FPropB, GetRttiProperty(PropB)); // GetRttiPropertyist eine fiktive Funktion
end;
So, nun ist es ja so, dass, wie im Quelltext schon geschrieben, GetRttiProperty eine fiktive Funktion ist, die ich suche. Ich will eben aus der Property PropA bzw. PropB ein TRttiProperty-Objekt machen, ohne dabei auf einen String zurückgreifen zu müssen. Sollte sich nämlich mal etwas an den Properties ändern, so kann ich nicht einfach das Refactoring anwerfen, sondern darf selbst nach Strings suchen, was alles andere als sauber wäre.

Ich hoffe, dass mir hier jemand weiter helfen kann. Bin gerade echt am verzweifeln :roll:

Khabarakh 25. Mär 2010 16:59

Re: [RTTI] RttiProperty aus Property von Instanz erzeugen
 
So funktioniert aber kein mir bekannter O/R-Mapper (wenn es auch nur annähernd in diese Richtung gehen soll). Die Datenobjekte sollten reine PODOs (Plain Old Java Delphi Objects) sein, die ganz gewöhnliche Properties besitzen. Erst von außen reflektiert ein Helferlein über diese Klassen und füllt sie mit den Daten aus der DB - den Klassen selbst sieht man das überhaupt nicht an.

chaosben 26. Mär 2010 06:35

Re: [RTTI] RttiProperty aus Property von Instanz erzeugen
 
Vielleicht hilft dir unsere RTTIHelper-Unit weiter.

Stevie 26. Mär 2010 07:00

Re: [RTTI] RttiProperty aus Property von Instanz erzeugen
 
Zitat:

Zitat von Khabarakh
So funktioniert aber kein mir bekannter O/R-Mapper (wenn es auch nur annähernd in diese Richtung gehen soll). Die Datenobjekte sollten reine PODOs (Plain Old Java Delphi Objects) sein, die ganz gewöhnliche Properties besitzen. Erst von außen reflektiert ein Helferlein über diese Klassen und füllt sie mit den Daten aus der DB - den Klassen selbst sieht man das überhaupt nicht an.

Sehe ich genauso. Das Datenobjekt sollte keinen Code enthalten, der für das O/R-Mapping zuständig ist. Einzig die mit Delphi 2010 eingeführten Attribute halte ich für eine Möglichkeit einer Klasse Mapping Infos mitzugeben (obwohl auch hier teilweise die Meinungen auseinander gehen).
Man sieht an deinem Beispiel schon, welche Nachteile du hast. Man muss bei neuen Properties speziellen Code schreiben und kann nicht einfach eine simple neue Property in die Klasse einbauen.

Zu deiner eigentlichen Frage: ich kenne keine Möglichkeit über eine Property-"Referenz" an die RTTI Information dieser zu kommen.

s.h.a.r.k 26. Mär 2010 12:31

Re: [RTTI] RttiProperty aus Property von Instanz erzeugen
 
Jo, es soll im Endeffekt ein O/R-Mapping statt finden. Ich fand das Attribut-Prinzip ganz hilfreich für genau so etwas. Ich habe auch für jede Property ein Attribut, welchem ich einen String übergebe. Dieser entspricht einer Spalte aus der Datenbank. Somit baue ich im Moment meine Abbildung zusammen, d.h. die Properties können einen anderen Namen tragen als die Spalten aus der DB. Ich habe mir schon überlegt, das in einer XML zu definieren oder über ein Array zu lösen, aber da habe ich *immer* das Problem, dass kein Refactoring sinnvoll greift, wenn ich denn einmal eine Property umbenennen will. Daher hielt ich das für die beste Lösung.

Und genau das wollte ich nun eigentlich für genau das oben beschriebene Problem auch habe. Änderungen sollen einfach via Refactoring möglich sein, ohne, dass ich nicht x-beliebig viele andere Stellen anspringen muss und Strings ändern sollte. Nur ich seh schon, ich muss das irgendwie anders gestalten.

Abseits davon: Wie machen das "normale" O/R-Mapper denn? Es wird ja ein Abbildungs-Verzeichnis benötigt. Wo wird das gespeichert? Wie werden zusätzliche Dinge beachtet? Not Null z.B.? Wird das alles fix in einen Controller implementiert?

Khabarakh 26. Mär 2010 14:24

Re: [RTTI] RttiProperty aus Property von Instanz erzeugen
 
Gegen Attribute haben wir doch gar nichts eingewendet :D . Es ging lediglich um den Setter-Code.

Im einfachsten Fall wird für alle Public Properties jeweils eine gleichnamige DB-Spalte gesucht. Durch Attribute können sie zusätzlich umbenannt oder ignoriert werden, auch Validation-Informationen wie NotNull kannst du damit durchaus hinzufügen.

s.h.a.r.k 26. Mär 2010 17:31

Re: [RTTI] RttiProperty aus Property von Instanz erzeugen
 
Zitat:

Zitat von Khabarakh
Im einfachsten Fall wird für alle Public Properties jeweils eine gleichnamige DB-Spalte gesucht. Durch Attribute können sie zusätzlich umbenannt oder ignoriert werden, auch Validation-Informationen wie NotNull kannst du damit durchaus hinzufügen.

Ja, klar, aber ich muss diesen Code dann immer in die Setter-Methoden packen und kann dies ja nicht auf eine Methode reduzieren. Sollte sich dann mal etwas an dem System ändern muss ich es in *allen* Setter-Methoden ändern und nicht nur in einer. Das finde ich nicht wirklich zufriedenstellend.

Khabarakh 27. Mär 2010 14:25

Re: [RTTI] RttiProperty aus Property von Instanz erzeugen
 
Ah, ich habe mich auf das Einfügen der DB-Daten in die Entities konzentriert, eben der erste und wahrscheinlich leichteste Schritt auf dem Weg zu einem O/RM :) . Für solche Sachen wie Change Tracking und Validation muss tatsächlich etwas im Setter geschehen, da hast du schon recht. Wenn man also ein Validate('ThisProperty') vermeiden will, sehe ich bei statisch typisierten Sprachen nur zwei Möglichkeiten:
  • Runtime Proxies: Alle Properties werden als virtual markiert und zur Laufzeit vom Mapper-System überschrieben. So machen das zum Beispiel Hibernate in Java und NHibernate & Entity Framework in .NET. In Delphi dürftest du da aber relativ chancenlos sein.
  • Code Generation: Wenn man den Code selbst nicht schreiben will, muss er eben automatisch aus dem Designer/der XML-Config/... erstellt werden :) . Damit sind die Property-Namen garantiert aktuell, von der generierten Klasse kann dann abgeleitet und entsprechende virtuelle Methoden wie "PropAChanged" überschrieben werden. So macht es LINQ To SQL.

s.h.a.r.k 30. Mär 2010 14:38

Re: [RTTI] RttiProperty aus Property von Instanz erzeugen
 
Irgendwie ist das aber immer noch nicht so ganz das was ich mir darunter vorgestellt habe. Selbst wenn ich intern die Daten nicht wie eine Property handle (z.B. in einem passenden indexierten Container), führt das früher oder später zu ungewollten Problemen, da ich dann wieder keine Attribute für die einzelnen Werte anwenden kann. Ich stoße dabei immer wieder an Ecken, die nicht so toll sind.

Es wäre echt wünschenswert aus einer Property eine TRttiProperty-Referenz zu erhalten. Mal schauen, was das neue Delphi so bringen wird.

s.h.a.r.k 31. Mär 2010 21:58

Re: [RTTI] RttiProperty aus Property von Instanz erzeugen
 
Ich habe nun eine Lösung, die über die Setter-Methode einer Property läuft. Diese Idee funktioniert allerdings nur unter der Voraussetzung, dass jede Property eine eindeutige und einzigartige Setter-Methode hat, denn wenn für mehrere Properties ein und die selbe Setter-Methode verwendet wird (was ja unter Umständen der Fall sein kann), so wäre dies keine eindeutige Abbildung und so käme auch keine korrekt Lösung heraus.

Delphi-Quellcode:
// Prozeduraler Typ für Parameter
TMyProc = procedure (const Value: Variant) of object;

// Test-Klasse ;)
TTestObject = class(TObject)
private
  FPropA : String;
  FPropB : String;
  function GetPropA(): String;
  procedure SetPropA(const Value: String);
  function GetPropB(): String;
  procedure SetPropB(const Value: String);
public
  property PropA : Stringread GetPropA write SetPropA;
  property PropB : Stringread GetPropB write SetPropB;
  function GetRttiPropertyBySetter(const AMethod: TMyProc): TRttiProperty;
end;

implementation

{ TTestObject }

function TTestObject.GetPropA(): String;
begin
  Result := FPropA;
end;

procedure TTestObject.SetPropA(const Value: String);
var
  pt : Pointer;
begin
  { ... }
end;

function TTestObject.GetPropB(): String;
begin
  Result := FPropA;
end;

procedure TTestObject.SetPropB(const Value: String);
begin
  { ... }
end;

function TTestObject.GetRttiPropertyBySetter(const AMethod: TMyProc): TRttiProperty;
var
  pt : Pointer;
  rContext : TRttiContext;
  rType : TRttiType;
  rProperty : TRttiProperty;
  rPropInfo : PPropInfo;
begin
  Result := nil;

  // Pointer auf Setter-Methode holen
  pt := @AMethod;

  rContext := TRttiContext.Create();
  try
    rType := rContext.GetType(Self.ClassType);

    // Property für Property durchgehen, bis eine gefunden wurde
    // die eine passende Setter-Methode (Erkennung via Pointer) hat.
    for rProperty in rType.GetDeclaredProperties do
    begin
      rPropInfo := TRttiInstanceProperty(rProperty).PropInfo;
      if (rPropInfo.SetProc = pt) then
      begin
        Result := rProperty;
        exit;
      end;
    end;
  finally
    rContext.Free();
  end;
end;
Vielleicht hilft das ja mal jemandem. Sinnvoll ist diese Methode im Moment nur, wenn diese intern (private) verwendet wird, oder man setzt die Setter-Methoden public, sodass man darauf auch von außen zugreifen kann.


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