Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   Delphi Datenstruktur clientseitig abbilden? (https://www.delphipraxis.net/139831-datenstruktur-clientseitig-abbilden.html)

Namenloser 6. Sep 2009 13:49

Datenbank: Firebird • Zugriff über: ZEOS

Datenstruktur clientseitig abbilden?
 
Hallo,

angenommen, ich habe verschiedene Tabellen, die über irgendein Feld miteinander verknüpft sind, wie stelle ich das clientseitig am besten dar? Die Beschreibung ist etwas hakelig, deshalb hier mal ein Minimalbeispiel einer imaginären Forensoftware:
Code:
Table "Entries":
  ID: int
  AuthorID: int
  Text: text;

Table "Users":
  ID: int
  Name: int
Hier referenziert Entries über das Feld AuthorID also die Tabelle Users. Mit JOIN könnte man hier z.B. sehr elegant für einen Beitrag den Namen gleich mit herausbekommen und direkt verarbeiten. Aber wie mache ich das am besten, wenn ich mein Programm objektorientiert aufbauen möchte? Hierbei würde es sich quasi aufdrängen, dass Beitrag und Autor jeweils eigenständige Objekt sind und das Beitragsobjekt eine Referenz auf den jeweiligen Nutzer enthält.

Das Problem wäre, dass für jeden Beitrag die gesamte Liste an Usern durchgegangen werden müsste, um über die ID die Referenz herauszubekommen. Somit würde ich dann doch wieder die Daten manuell durchsuchen und könnte ich mir die Datenbank eigentlich sparen...

Also kurz gesagt habe ich das Gefühl, auf dem Holzweg zu sein. Ich habe nicht wirklich viel Erfahrung mit Datenbanken, bzw. habe sie bisher eigentlich nur für meine Webseite verwendet. Daher meine Frage: Wie macht man sowas professionell?

Vielen Dank

DeddyH 6. Sep 2009 13:58

Re: Datenstruktur clientseitig abbilden?
 
Vielleicht hilft Dir dieser Thread (inkl. der Querverlinkungen) etwas weiter.

omata 6. Sep 2009 16:35

Re: Datenstruktur clientseitig abbilden?
 
Zitat:

Zitat von NamenLozer
Das Problem wäre, dass für jeden Beitrag die gesamte Liste an Usern durchgegangen werden müsste, um über die ID die Referenz herauszubekommen

Binäre Suche.

Namenloser 7. Sep 2009 23:12

Re: Datenstruktur clientseitig abbilden?
 
Tut mir Leid, dass ich mich noch nicht wieder gemeldet habe, ich bin zur Zeit immer noch am überlegen wie ich das am besten aufbaue.

Vielen Dank schon mal für deinen Link, Detlef. Ich habe mir den Thread und die Querverlinkungen durchgelesen, es tut gut, zu sehen, dass ich nicht der einzige mit diesem Problem bin, und bin jetzt denke ich auch ein Stückchen weiter. Also alle Lösungen, die ich bisher gesehen habe, waren auf eine eindeutige ID jedes Datensatzes angewiesen. Allerdings ist mir noch nicht ganz klar, wie das bei Abfragen funktionieren soll, die über mehrere Tabellen gehen....

Also wie gesagt, ein paar Eindrücke habe ich gesammelt, ganz schlüssig, wie ich es jetzt angehe, bin ich aber noch nicht. Ich wollte euch nur kurz wissen lassen, dass ich eure Beiträge gelesen habe und das Thema noch aktuell ist.

@omata:
Daran hatte ich zwar auch schon gedacht, aber das ist keine Lösung des eigentlichen Problems... es macht nur den Umweg schneller. Ich nutze ja gerade auch deshalb Datenbanken, damit ich mich um solchen Kram wie Suchalgorithmen nicht selbst kümmern muss.

omata 7. Sep 2009 23:32

Re: Datenstruktur clientseitig abbilden?
 
Naja, du musst dich entscheiden, entweder sucht du (das dauert, verbraucht aber weniger Speicher) oder du speichert die Information mit ab (dann bist du schneller, benötigst aber mehr Speicherplatz).
Das gilt es immer abzuwägen, du musst dir klar werden, was dir wichtig ist.

Eine Datenbank einzusetzen, ist doch eine gute Idee...

Angel4585 8. Sep 2009 07:16

Re: Datenstruktur clientseitig abbilden?
 
Suchst du sowas wie Hashmaps? Hier giobts ein Link zu ner Implementierung in Delphi: Hash-Tabellen

DeddyH 8. Sep 2009 08:39

Re: Datenstruktur clientseitig abbilden?
 
Um alle Daten sofort zur Verfügung zu haben, müsstest Du ja die komplette DB im Speicher halten. Das macht in meinen Augen wenig Sinn. Bei gescheiter Indexierung sollte auch eine Abfrage zu der Zeit, wo die Daten wirklich benötigt werden, kein großes Performanceproblem darstellen. Nehmen wir einmal Folgendes an: Du hast aus der Datendefinition der Tabelle Users eine Klasse TUser erstellt, die zunächst nur die Daten, die auch in dieser einen Tabelle definiert wurden, enthält. Nun wäre es doch kein Problem, diese Klasse um eine Methode zu erweitern, die die Einträge des aktuellen Benutzers ermittelt. Was nach meiner Erfahrung gut klappt ist, solche Auflistungen für die Darstellung in TStrings-Objekten abzulegen (das Gleiche gilt auch für die Users-Tabelle). Ein Beispiel aus dem Kopf:
Delphi-Quellcode:
procedure TUser.GetEntries(const sl: TStrings);
var Entry: TEntry;
begin
  sl.BeginUpdate;
  try
    sl.Clear;
    //dem Objekt sei ein TQuery o.ä. zugewiesen
    Query.Close;
    Query.SQL.Text := 'SELECT ID,Text FROM Entries WHERE AuthorID = :id';
    Query.ParamByName('id').Value := self.ID;
    Query.Open;
    while not Query.EOF do
      begin
        //Entry-Objekt anlegen, befüllen und in TStrings ablegen
        Entry := TEntry.Create;
        Entry.ID := Query.FieldByName('ID').AsInteger;
        Entry.AuthorID := self.ID;
        Entry.Text := Query.FieldByName('Text').AsString;
        sl.AddObject(IntToStr(Entry.ID),Entry);
      end;
  finally
    sl.EndUpdate;
  end;
end;
Man muss halt nur darauf achten, im Delete, Clear, Destroy usw. des TStrings-Objektes die enthaltenen TEntry-Objekte wieder freizugeben.

Namenloser 17. Sep 2009 17:11

Re: Datenstruktur clientseitig abbilden?
 
Nanu, hier gabs ja inzwischen neue Beiträge :shock: Wieso wurde ich darüebr nciht benachrichtigt? :gruebel:

Ich habe nicht vor, die gesamte Datenbank im Programmspeicher zu halten, das wäre ja absolut kontraproduktiv. Daher helfen mir hier auch Hashmaps nicht.

Eigentlich wollte ich jetzt aber mal über den aktuellen Stand schreiben. Ich bin schon bei der Implementierung, aber ich bin mir unsicher, ob ich nicht vielleicht gerade das Rad neu erfinde, und Klassen schreibe, die es schon standardmäßig gibt - denn wie gesagt hab ich unter Delphi noch nix mit Datenbaken zu tun gehabt.

Hier ist das Konzept:

Es gibt folgende (wichtige) Klassen:
  • Datenbank (TDatabase)
  • Tabelle (TTable)
  • Datensatz/Zeile in einer Tabelle (TTableRow)
  • Feld (TField)
  • Feld-Wert (TFieldValue)
  • Resultat einer Abfrage (Liste) (TQueryResult)
  • Datensatz/Zeile in einer Abfrage (TQueryResultRow)
Es gibt also die Basislasse TTableRow für einen Datensatz in einer Tabelle, quasi eine Zeile. Die Felder eines Datensatzes (TField) werden in einer Liste von TTableRow verwaltet. Die Werte dieser Felder (TFieldValue) sind selbst Objekte eines abstrakten Typs. Einer der Vorteile: Die Klasse TTableRow ist selbst von TFieldValue abgeleitet, d.h. mit Referenzen kann hier sehr elegant umgegangen werden. Zur Identifizierung kann jede Zeile eine eindeutige ID zugewiesen bekommen, das ist zwar nicht zwingend, aber bestimmte Funktionen lassen sich sonst eben nicht nutzen.

Die Datenbank wird eine Funktion haben, einen Query auszuführen. Das Ergebnis wird als TQueryResult ausgegeben. Statt die Felder direkt zu beinhalten, beinhaltet TQueryResult Referenzen auf die entsprechenden Tabellendatensätze, die die angeforderten Informationen enthalten. Das soll die Verwaltung vereinfachen und dafür sorgen, dass die Datenbankstruktur besser auf die Klassenstruktur übertragen wird.

Natürlich kann und wird es passieren, dass die gleichen Tabellendatensätze an verschiedenen Stellen verwendet werden. Damit die Daten synchron bleiben, speichert jede TTableRow Referenzen auf andere TTableRow-Objekte, die auf den gleichen Datensatz zugreifen. Hier kommt die Klasse TTable ins Spiel, die eine Liste mit Datensätzen entählt, die im Progamm in Verwendung sind. Dazu kennt jedes TTableRow-Objekt die Tabelle, zu der es gehört. Wenn nun ein neues TTableRow-Objekt durch eine Abfrage angelegt wird, prüft es zunächst ob in seiner Tabelle dieser Datensatz schon vorhanden ist (dazu nutzt es die optionale ID), wenn nein, fügt es sich zur Referenzliste der Tabelle hinzu, sonst pickt es sich das dort vorhandene Objekt (ich nenn es jetzt mal den Verwalter) heraus, und meldet sich dort an. Es wird dann zur Referenzliste dieses Verwalters, sowie aller Referenzen, die der Verwalter kennt, hinzugefügt. Ebenso kopiert es alle "Bekannten" dieses Verwalters, sowie den Verwalter, in seine eigene Referenzliste ein. Wird ein Objekt zerstört, entfernt es sich automatisch aus den Referenzlisten der anderen Objekte. Falls das Objekt der Verwalter ist, wird das erste Objekt in seiner Referenzliste sein Nachfolger. Falls kein Nachfolger vorhanden ist, wird das Objekt aus der Referenzliste der Tabelle entfernt.

Statt der ganzen Listen hatte ich auch schon über Interfaces nachgedacht (Referenzzähler), aber da ich von Interfaces keine Ahnung habe, möchte ich lieber die Finger davon lassen, statt mir eine weitere Fehlerquelle ins Programm zu holen

Es ist so gedacht, dass die oben genannten Klassen nicht direkt verwenden werden, sondern nur das Grundgerüst darstellen. Letztendlich sollen abgeleitete Klassen verwendet werden, die die Datenstruktur repräsentieren. Für jede Tabelle wird eine eigene Klasse von TTable abgeleitet etc. Das könnte von einem Generator automatisch erledigt werden.

Ich hoffe mein Ansatz ist deutlich geworden. Nun möchte ich gerne hören, was ihr davon haltet. Zu komplex? Rad neu erfunden? Oder vielleicht total genial? :mrgreen:

Vielen Dank schonmal fürs Lesen und eure Antworten!

sx2008 18. Sep 2009 00:06

Re: Datenstruktur clientseitig abbilden?
 
Zitat:

Zitat von NamenLozer
Zu komplex? Rad neu erfunden? Oder vielleicht total genial? :mrgreen:

Sowohl als auch :-)
Du gehst hier in Richtung persistent Framework.
Das Problem ist die Zuordnung (Mapping) zwischen deinen Bussiness-Objekten und der relationalen Datenbank.
Es "klemmt" einfach irgendwie (Object-Relational Impedance Mismatch) und eine allgemeine Lösung passend für alle Anwendungfälle lässt sich schwer finden.

Je weiter du mit deiner Klassenbibliothek kommst umso schwieriger werden die Probleme.
Viele haben schon aufgegeben z.B. Jedi Obiwan
Aber lass dich nicht von mir entmutigen. :hi:
Weitere Ideen kannst du hier bekommen.

Oder vielleicht möchtest du auch das tech inside Open Persistent Framework anschauen.

Namenloser 22. Sep 2009 18:24

Re: Datenstruktur clientseitig abbilden?
 
Hallo,

wenn ich ein DB.TField von eienr TZQuery habe, wie kann ich dann herausfinden, aus welcher Tabelle dieses Feld stammt?

Ich habe eben noch mal die OH gewälzt, und dabei gesehen, dass Delphi von Haus aus auch schon Persistenz ermöglicht, z.B. für die datensensitiven Komponenten. Wie nutzt man dieses Feature? Gibt es irgendwas, das dagegen spricht, es zu benutzen?

Danke

Namenloser 23. Sep 2009 20:33

Re: Datenstruktur clientseitig abbilden?
 
Zitat:

Zitat von NamenLozer
wenn ich ein DB.TField von eienr TZQuery habe, wie kann ich dann herausfinden, aus welcher Tabelle dieses Feld stammt?

Also wenn ich z.B. zwei Tabelle habe, die ein gleichnamiges Feld haben, und diese joine, dann werden die Felder von ZEOS (oder von Firebird?) automatisch umbenannt in Feld_1, Feld_2 usw. Das ist aber für meine Zwecke nicht besonders geeignet, ich will ja flexibel bleiben. Wie kann ich herausfinden zu welcher Tabelle die jeweilgen Felder gehören und wie das Feld dort heißt?

Eine Dokumentation kann ich zu ZEOS leider nicht finden.

sx2008 23. Sep 2009 22:24

Re: Datenstruktur clientseitig abbilden?
 
Zitat:

Zitat von NamenLozer
.. wenn ich z.B. zwei Tabelle habe, die ein gleichnamiges Feld haben, und diese joine, dann werden die Felder von ZEOS (oder von Firebird?) automatisch umbenannt in Feld_1, Feld_2 usw.

Das darf man eben nicht tun:
SQL-Code:
SELECT * FROM Tabelle_A INNER JOIN Tabelle_B ON Tabelle_A.Keyfeld = Tabelle_B.Keyfeld
Jedes DBMS reagiert da anderst und man verbaut sich so die Möglichkeit auf eine andere Datenbank umzusteigen oder mehr als eine Datenbank zu unterstützen.
Man darf allerdings von einer Tabelle alle Felder nehmen (mit *) und gezielt Felder
von anderen Tabellen:
SQL-Code:
SELECT Tabelle_A.*, B.PLZ, B.Ort, B.Strasse FROM Tabelle_A INNER JOIN Tabelle_B B ON Tabelle_A.Keyfeld = B.Keyfeld
Das mag etwas mühsam sein, aber so werden gleichnamige Felder ausgeschlossen und holt nur die Felder,
die man wirklich braucht.

Namenloser 23. Sep 2009 22:48

Re: Datenstruktur clientseitig abbilden?
 
Hallo sx2008,
Danke für deine Antwort,

ich benutze testweise folgendes Statement:
SQL-Code:
SELECT * FROM people t1 LEFT JOIN people t2 ON t1.father_id=t2.id
Hier geht es nur um eine Tabelle, bei mehreren Tabellen ist das ganze mindestens genau so verzwickt.

Die SQL-Abfrage an sich ist aber nicht wirklich das Problem, sondern die Behandlung im Programm. Das Problem ist, dass die Daten eben so im Programm ankommen, dass sie schlecht automatisch verarbeitet werden können. Als Ergebnis bekomme ich NAME, NAME_1, ID, ID_1 usw. Das Ziel ist ja aber, die Daten automatisch in Klassen einzuordnen und mir fällt keine elegante Filterungsmethode ein, diese Feldnamen zuzuordnen. Es könnte ja auch das Feld im Original ID_1 heißen usw. Es wäre eben praktischer, wenn man das ganze direkt als Array oder Record bekäme, in etwa so:
Code:
Felder {
  Feld {
    Name: "ID"
    Table: "PEOPLE"
    Record: 0

    Value: "21"
  }
  Feld {
    Name: "NAME"
    Table: "PEOPLE"
    Record: 0

    Value: "Hans"
  }
  Feld {
    Name: "FATHER_ID"
    Table: "PEOPLE"
    Record: 0

    Value: "14"
  }

  Feld {
    Name: "ID"
    Table: "PEOPLE"
    Record: 1

    Value: "14"
  }
  Feld {
    Name: "NAME"
    Table: "PEOPLE"
    Record: 1

    Value: "Vater"
  }
  Feld {
    Name: "FATHER_ID"
    Table: "PEOPLE"
    Record: 1

    Value: NULL
  }
}
Was ich zur Zeit bekomme sieht aber eher so aus:
Code:
Felder {
  Feld {
    Name: "ID"
    Value: "21"
  }
  Feld {
    Name: "NAME"
    Value: "Hans"
  }
  Feld {
    Name: "FATHER_ID"
    Value: "14"
  }

  Feld {
    Name: "ID_1"
    Value: "14"
  }
  Feld {
    Name: "NAME_1"
    Value: "Vater"
  }
  Feld {
    Name: "FATHER_ID_1"
    Value: NULL
  }
}
Gibt es da eine Möglichkeit?

sx2008 23. Sep 2009 23:16

Re: Datenstruktur clientseitig abbilden?
 
Nachdem eine SQL-Abfrage gelaufen ist, lässt sich der Ursprung eines Feldes nicht mehr feststellen.
Die Treiberschichten geben diese Information nicht raus und falls doch, kommt der Name der Ursprungstabelle nicht durch die VCL.
Es müsste ja ein Property "TableName" oder so ähnlich in der Klasse TField geben; gibt's aber nicht.
Das Property TField.Origin liefert unter der BDE den ursprünglichen Feldnamen,aber das hilft hier auch nicht.

Ausserdem lässt sich der Ursprung eines Feldes nicht immer genau klären:
SQL-Code:
SELECT TabA.Feld1+'-'+TabB.Feld2 AS MeinFeld, 8*4 AS TestFeld FROM TabA INNER JOIN TabB ON....
Die Felder MeinFeld und TestFeld lassen sich keiner Tabelle eindeutig zuorden.

hoika 24. Sep 2009 09:40

Re: Datenstruktur clientseitig abbilden?
 
Hallo,

nun ja, man könnte als Lösung ja den Tabellen-Namen voranstellen.

SQL-Code:
SELECT ts.name as t1_name, t2.name as t2_name
FROM people t1 LEFT JOIN people t2 ON t1.father_id=t2.id
Der Delphi-Code muss dann per Copy/Explode aus dem Feld wieder Tabelle und Feld-Name machen.
Probleme gibt es aber dann mit

SQL-Code:
Select Sum(X) as T1_Sum From t1
Das Feld Summe gibt es ja bei T1 nicht.

Ein Select * fällt eh aus,
es sei denn, es handelt sich um genau eine Tabelle, also kein Join.


Heiko

fajac 24. Sep 2009 10:25

Re: Datenstruktur clientseitig abbilden?
 
Meiner Ansicht nach sollte man, wenn man seine Business-Objekte als Repräsentationen von Tabellen-Datensätzen erstellt, keine Abfragen mit JOIN verwenden.
Natürlich sind diese effizienter, aber das folgende Beispiel-Kostrukt dürfte auch genügend performant sein:

Delphi-Quellcode:
type
  TUser = class
    FUserId  : Integer;
    FUserName : string;
    procedure Assign (AData : TDataset);
  end;

  TEntry = class
    FEntryId  : Integer;
    FText     : string;
    FUser     : TUser;
    procedure Assign (AData : TDataset);
  end;

implementation
 
{ AData wurde mit "select * from Entries where ..." ermittelt }
procedure TEntry.Assign (AData : TDataset);
var
  data : TDataset;
begin
  { Eigenschaften setzen }
  FEntryId := AData.FieldByName('').AsInteger;
  { ... }
  FUser := TUser.Create;
  { data mit "select * from Users where id=:userId" füllen }
  FUser.Assign (data);
  { ... }
end;
Dann hast du zwar zwei Abfragen statt einer, aber alle Objekte enthalten nur Daten einer Tabelle.


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