AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Datenbanken Delphi Datenbankanwendung sauber strukturieren und programmieren
Thema durchsuchen
Ansicht
Themen-Optionen

Datenbankanwendung sauber strukturieren und programmieren

Ein Thema von guidok · begonnen am 3. Nov 2008 · letzter Beitrag vom 19. Nov 2008
Antwort Antwort
Seite 1 von 2  1 2      
guidok

Registriert seit: 28. Jun 2007
417 Beiträge
 
#1

Datenbankanwendung sauber strukturieren und programmieren

  Alt 3. Nov 2008, 07:54
Datenbank: MSSQL • Zugriff über: ADO
Hallo zusammen,

zunächst eine kleine Warnung: Da ich nicht weiß, wie ich direkt auf den Punkt kommen soll, beginne ich mal mit einer etwas ausführlicheren Einleitung.

Es geht, wie der Titel bereits aussagt, darum, wie man eine Anwendung sauber programmiert, so dass sie übersichtlich und einfach wartbar wird. Ich bin ja kein professioneller Programmierer und habe auch keine entsprechende Ausbildung, aber auch als "Hobbyist" möchte ich versuchen meine Sache so gut wie möglich zu lösen und ich bin mir leider nicht ganz sicher, ob ich mich auf dem richtigen Weg befinde, oder mir das Leben selbst unnötig schwer mache. So beginne ich mal einige Möglichkeiten auf zu listen, die auch weitestgehend dem entsprechen, wie ich bisher vor gegangen bin.

Zunächst noch der Hinweis, dass ich hier von einer DB als SQL-Server spreche, da die Anwendung im Netzwerk und ggf. mit mehreren Benutzern betrieben wird. Die eingesetzte DB ist jetzt mal MSSQL mit ADO, das kann aber auch mal MySQL mit Zeos sein. In der Regel benutze ich keine Komponenten, wie DBGrid o.ä. sondern greife über eine Query auf die DB zu und fülle "normale" Grids und Edits mit den Daten.

1. Die "alles in einem" Lösung: Ein Formular, eine Connection, eine Query. Das wurde sehr schnell sehr unübersichtlich und machte keinen Spaß.

2. Ein globales Datenmodul mit einer Connection und einer Query. Die Anwendung wurde auf mehrere Formulare aufgeteilt, die diese Query verwendeten. Wesentlich übersichtlicher, ich musste allerdings manchmal mehrere Querys verwenden, wenn parallel Daten bereitgestellt werden mussten und hatte die SQL-Anweisungen in der jeweiligen Formularunit. Zudem las ich irgendwann von der Trennung von GUI und Daten, was mich zum nächsten Versuch führte.

3. Globales Datenmodul mit Connection und Query(s), der Zugriff auf die Daten erfolgte jedoch nicht direkt über die Query sondern über extra dafür erstellte Methoden, in der Art wie "First" und "Next". Als Beispiel nenne ich mal "FirstArtikelübersicht", die mit einem Select eine Query öffnet und "NextArtikelübersicht" mit dem ich Datensatz für Datensatz aus der Query auslesen und in die GUI übertragen kann. Sinn war es, bei einer DB-Änderung (z.B. auf MySQL) nur das Datenmodul ändern zu müssen. So richtig befriedigt hat mich das allerdings auch nicht, weil jetzt das Datenmodul ziemlich unübersichtlich geworden ist.

Grundsätzlich bin ich auch nicht so sicher, ob es eine gute Idee ist, die Daten auf diese Weise in Anzeigekomponenten zu kopieren, weil diese ja auch (teilweise) in irgend einer Weise bearbeitet und geändert werden und dementprechend wieder in die DB geschrieben werden müssen.
----

Jetzt stehe ich wieder davor eine kleine Anwendung machen zu wollen und möchte mich gerne verbessern und mir das Leben einfacher machen. Deshalb die Frage:

Wie soll ich das am sinnvollsten angehen?
  Mit Zitat antworten Zitat
Blup

Registriert seit: 7. Aug 2008
Ort: Brandenburg
1.429 Beiträge
 
Delphi 10.4 Sydney
 
#2

Re: Datenbankanwendung sauber strukturieren und programmiere

  Alt 3. Nov 2008, 08:19
Hallo, such mal nach Object Persistence Framework.

z.B.: http://tiopf.sourceforge.net/
  Mit Zitat antworten Zitat
Tyrael Y.

Registriert seit: 28. Jul 2003
Ort: Stuttgart
1.093 Beiträge
 
Delphi 2007 Professional
 
#3

Re: Datenbankanwendung sauber strukturieren und programmiere

  Alt 3. Nov 2008, 08:22
Der Weg über Trennung von GUI und Funktionalität ist schon mal der Richtige.

Ich würde folgendermassen vorgehen.

- eine Datenbank Klasse
Hier sind alle Funktionalitäten im Zusammenhang mit der DB untergebracht
DB erzeugen, Tabelle erzeugen, Daten schrieben/lesen/löschen. Diese Klasse "kennt" ihre Aussenwelt nicht.

- eine oder mehrere reine Datenklasse(n), diese Klasse(n) können im Prinzip nix, sie sollen nur die Daten bereit halten. Diese Klassen werden mit den Daten aus der DB-Klasse gefüllt.

- die GUI - sie zeigt die Daten aus den Datenklassen an
Levent Yildirim
Erzeugung von Icons aus Bildern:IconLev
  Mit Zitat antworten Zitat
SvB

Registriert seit: 21. Okt 2004
Ort: Eckenroth
426 Beiträge
 
Delphi 10.1 Berlin Enterprise
 
#4

Re: Datenbankanwendung sauber strukturieren und programmiere

  Alt 3. Nov 2008, 08:57
Hallo, das interessiert mich auch.
Wenn man jetzt eine Datenklasse erstellt, dann ist das doch fast das selbe wie ein DateSet, oder. Alle benötigten Funktionen müssen da dann ja wieder implementiert werden, wie z.B. Next, RecordCount.....?
Sehe ich das richtig?

Grüße Sven
  Mit Zitat antworten Zitat
guidok

Registriert seit: 28. Jun 2007
417 Beiträge
 
#5

Re: Datenbankanwendung sauber strukturieren und programmiere

  Alt 3. Nov 2008, 10:19
Zitat von Tyrael Y.:
- eine Datenbank Klasse
Hier sind alle Funktionalitäten im Zusammenhang mit der DB untergebracht
DB erzeugen, Tabelle erzeugen, Daten schrieben/lesen/löschen. Diese Klasse "kennt" ihre Aussenwelt nicht.

- eine oder mehrere reine Datenklasse(n), diese Klasse(n) können im Prinzip nix, sie sollen nur die Daten bereit halten. Diese Klassen werden mit den Daten aus der DB-Klasse gefüllt.

- die GUI - sie zeigt die Daten aus den Datenklassen an
Im Prinzip ist ja eine Query eine "Datenklasse", d.h. sie hält (solange sie geöffnet ist) die Daten bereit. Ist es an dieser Stelle sinnvoll diese einzusetzen? Wobei dann die Funktionalität von "Daten schreiben/lesen/löschen" ja wieder aus der Datenbank-Klasse heraus verlagert werden würde.

Also eher, eine DB-Klasse, die eine Connection und eine Query enthält und die entsprechenden Methoden die Daten zu schreiben/lesen/löschen und Datenklassen, die beispielsweise in einer Objectlist die Daten aus der DB-Klasse hält. Habe ich das richtig verstanden? Wie würde ich dann Datenänderungen handeln können?

Guido
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#6

Re: Datenbankanwendung sauber strukturieren und programmiere

  Alt 3. Nov 2008, 10:53
Hi,

eine entsprechende Abstraktion gelingt über TDataSet.
Dazu hier ein kleines Beispiel:

Die Datenklasse (mit ADO auf Access)
Delphi-Quellcode:
unit datADO;

interface

uses
  SysUtils, Classes, DB, ADODB;

type
  TADOmod = class(TDataModule)
    ADOConnection1: TADOConnection;
    ADOQuery1: TADOQuery;
    procedure DataModuleCreate(Sender: TObject);
    procedure DataModuleDestroy(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    function GetTable( const ATableName : string; var ADataSet : TDataSet ) : boolean;
    function GetQuery( const ASqlQuery : string; var ADataSet : TDataSet ) : boolean;
    function ExecSql( const ASqlStatement : string; var ARecordsAffected : integer ) : boolean;
  end;

var
  ADOmod: TADOmod;

implementation

const
  DBconn = 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s;Persist Security Info=False';
  DBpath = '..\db\DBabstract.mdb';

{$R *.dfm}

procedure TADOmod.DataModuleCreate(Sender: TObject);
begin
  if ADOConnection1.Connected then
    ADOConnection1.Close;
  ADOConnection1.ConnectionString :=
    Format( DBconn, [ DBpath ] );
end;

procedure TADOmod.DataModuleDestroy(Sender: TObject);
begin
  if ADOConnection1.Connected then
    ADOConnection1.Close;
end;

function TADOmod.ExecSql(const ASqlStatement: string; var ARecordsAffected: integer): boolean;
begin
  try
    ADOConnection1.Execute( ASqlStatement, ARecordsAffected );
    Result := true;
  except
    Result := false;
  end;
end;

function TADOmod.GetQuery(const ASqlQuery: string;
  var ADataSet: TDataSet): boolean;
begin
  ADataSet := TADOTable.Create( Self );
  try
    with TADOQuery( ADataSet )
    do
      begin
        Connection := ADOConnection1;
        SQL.Text := ASqlQuery;
      end;
    Result := true;
  except
    Result := false;
    FreeAndNil( ADataSet );
  end;
end;

function TADOmod.GetTable(const ATableName: string; var ADataSet: TDataSet) : boolean;
begin
  ADataSet := TADOTable.Create( Self );
  try
    with TADOTable( ADataSet )
    do
      begin
        Connection := ADOConnection1;
        TableName := ATableName;
      end;
    Result := true;
  except
    Result := false;
    FreeAndNil( ADataSet );
  end;
end;

end.
Und so wird die verwendet (ein Button, ein Datengrid und eine damit verknüpfte DataSource)
Delphi-Quellcode:
unit frmMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, DB, Grids, DBGrids;

type
  TForm1 = class(TForm)
    DBGrid1: TDBGrid;
    DataSource1: TDataSource;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

uses datADO;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  MyDataSet : TDataSet;
begin
  if ADOmod.GetTable( 'Personen', MyDataSet )
  then
    begin
      MyDataSet.Open;
      DataSource1.DataSet := MyDataSet;
    end;
end;

end.
Als weiterer Hinweis seien noch die StoredProcedures genannt, die z.B. bei MSSQL, MySQL, etc. möglich sind.
Hier kann eine Schnittstellen-Ebene geschaffen werden, die die DB-Entwicklung von der Programm-Entwicklung trennt.
Änderungen an der DB-Struktur ziehen nur Änderungen an den StoredProcedures nach sich (natürlich nur, wenn die Ein- und Ausgabe-Parameter sich nicht ändern). Aber wenn man aus einer Tabelle nun jetzt 2 machen möchte, das grundsätzliche Ein- und Ausgabeverhalten hat sich aber nicht geändert, so habe ich nur die Änderungen auf der DB-Seite und das Prog bleibt unangetastet.

cu

Oliver
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
guidok

Registriert seit: 28. Jun 2007
417 Beiträge
 
#7

Re: Datenbankanwendung sauber strukturieren und programmiere

  Alt 12. Nov 2008, 11:03
So da bin ich wieder, habe mich noch etwas belesen und leider festgestellt, dass es zu dem Thema leider nicht sehr viel zu lesen gibt.

Meine Idee geht jetzt in folgende Richtung:

Eine Klasse TDatensatz, die genau einen Datensatz enthält und auch die Methode diesen wieder in der DB zu speichern (laden auch?) oder zu löschen.

Eine Klasse TTabelle, die in einer TObjectList die gelesenen Datensätze hält. Aus dieser Klasse würde ich die GUI bedienen. Es gibt ebenfalls Methoden zum Löschen, Einfügen, Laden (Select) und Speichern.

Diese Klassen würde ich für jede benötigte Datenstruktur (z.B. Kunden, Bestellungen, Lieferungen usw.) einmal erstellen (z.B. Kunden: TKundentabelle und das Hinzufügen eines Kunden mit Kunden.Add...).

Ihr seht, so ganz durchdacht habe ich das noch nicht (Ganz nebenbei, dies wäre mal ein echt gutes Thema für ein Tutorial, wie man so etwas geschickt anstellt) und mir fehlt vor allem noch die Stelle an der die Datenbank und der Zugriff darauf stattfindet und zwar so, dass ein Wechsel des DBMS auf minimalinvasive Art und Weise möglich ist. Zudem muss man überlegen, dass es sicher nicht sinnvoll ist, die kompletten Daten aus einer DB in die TTabelle zu holen, sondern nur eine Teilmenge davon evtl. auch nur einen bestimmten Datensatz...

Hilfe! Ich brauche noch Denkanstöße!
  Mit Zitat antworten Zitat
mquadrat

Registriert seit: 13. Feb 2004
1.113 Beiträge
 
Delphi XE2 Professional
 
#8

Re: Datenbankanwendung sauber strukturieren und programmiere

  Alt 12. Nov 2008, 12:43
Bei einem unserer Projekte (~500.000 Zeilen) machen wir das genau so wie du als letztes beschrieben hast.

Je eine Klasse pro Entität. Diese enthalt drei Konstruktoren. Einen für einen neuen Datensatz, einen dem eine Datensatz-ID übergeben wird und einen, dem ein DataSet übergeben wird. Den dritten benötigen wir für unsere Listenklassen. Diese fahren eine beliebige Query und erzeugen für jeden Satz im Resultset ein Datenobjekt, dass sie in eine interne Objektlist packen.

Die GUI greift (meist ) ausschließlich auf die Datenklassen zurück. Ausnahmen bilden bei uns eigentlich nur Suchabfragen, da diese durch den zusätzlichen Umwandlungsschritt nicht gerade schneller werden.

Den Datenbankzugriff machen wir derzeit direkt. Du müsstest wenn du generisch bleiben möchtest, halt nur Datasets weitergeben und die Queries beispielsweise über eine eigene Query laufen lassen, die von einer konkreten erbt. Ich würde allerdings immer bei Dataset kompatiblen DB Zugriffsarten bleiben.

Bei einem neueren Projekt gehen wir einen leicht modifizierten Weg, indem die Datenklassen eine Collection der Felder enthalten und somit die SQL Statements für die Std-Aktionen selbst erzeugen kann. Im Grunde arbeiten wir mit einer Art selbstentwickelten OR Mapper.

Aber egal wie du es auch machst, hinterher fallen dir immer hundert Sachen ein, die du beim nächsten Projekt anders machen möchtest.
  Mit Zitat antworten Zitat
guidok

Registriert seit: 28. Jun 2007
417 Beiträge
 
#9

Re: Datenbankanwendung sauber strukturieren und programmiere

  Alt 14. Nov 2008, 13:36
Ich probiere gerade etwas herum und habe nun den vorhin beschriebenen Weg eingeschlagen. Momentan existiert nur ein unvollständiges Grundgerüst, das derzeit nur die Funktionalität bietet Daten(objekte) der Liste hinzuzufügen.

Das Lesen und Schreiben der Daten in eine DBMS fehlt noch komplett, aber ich will mal langsam beginnen.

Delphi-Quellcode:
type
  TDAORec = class(TObject)
  private
    FModified: Boolean;
    FPrimaryKey: Integer;
  public
    procedure Assign(ARec: TDAORec); virtual;
  end;

  TDAORecType = class of TDAORec;

  TDAO = class(TObject)
  private
    FRecType: TDAORecType;
    FRecList: TObjectList;
    function GetRec(AIndex: Integer): TDAORec;
    function GetCount: Integer;
  public
    constructor Create(ADAORecType: TDAORecType);
    destructor Destroy; override;
    function AddRec(var ARec: TDAORec): Integer;
    property Rec[AIndex: Integer]: TDAORec read GetRec; default;
    property Count: Integer read GetCount;
  end;

//==============================================================================
// T D A O R e c
//==============================================================================

procedure TDAORec.Assign(ARec: TDAORec);
begin

end;

//==============================================================================
// T D A O
//==============================================================================

constructor TDAO.Create(ADAORecType: TDAORecType);
begin
  inherited Create;
  FRecType := ADAORecType;
  FRecList := TObjectList.Create;
end;

destructor TDAO.Destroy;
begin
  FRecList.Free;
  inherited;
end;

function TDAO.AddRec(var ARec: TDAORec): Integer;
begin
  ARec.FModified := True;
  ARec.Assign(ARec);
  result := FRecList.Add(ARec);
end;

function TDAO.GetRec(AIndex: Integer): TDAORec;
begin
  result := FRecList[AIndex] as TDAORec;
end;

function TDAO.GetCount: Integer;
begin
  result := FRecList.Count;
end;
Um das ganze etwas universeller zu haben, ist die Idee dabei, dass ich für jede konkrete Implementation eine neue Klasse für die Datenfelder von TDAORec ableite, in der ich die benötigten Felder anlege und die Methode Assign entsprechend anpasse.

In TDAO sind dann die entsprechenden Methoden, um diese Struktur zu verwalten.

Delphi-Quellcode:
  TTestRec = class(TDAORec)
    Feld1: String;
    Feld2: String;
    procedure Assign(ARec: TDAORec); override;
  end;

  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Button1: TButton;
    Label1: TLabel;
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private-Deklarationen }
    DAO: TDAO;
  public
    { Public-Deklarationen }
  end;

procedure TTestRec.Assign(ARec: TDAORec);
begin
  if ARec is TTestRec then
    begin
    Feld1 := TTestRec(ARec).Feld1;
    Feld2 := TTestRec(ARec).Feld2;
    end;
  inherited;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  Rec: TTestRec;
begin
  Rec := TTestRec.Create; //Die erzeugten Objekte werden von TDAO wieder freigegeben
  Rec.Feld1 := Edit1.Text;
  Rec.Feld2 := Edit2.Text;
  //Datensatz hinzufügen
  DAO.AddRec(TDAORec(Rec)); //Der Typecast gefällt mir nicht

  //Nur zum Testen:
  //Anzahl der Datensätze
  Label1.Caption := IntToStr(DAO.Count);
  //Inhalt der Datensätze
  Listbox1.Clear;
  for i := 0 to DAO.Count - 1 do
  begin
    Rec := TTestRec(DAO[i]); //Der Typecast gefällt mir nicht
    Listbox1.Items.Add(Rec.Feld1 + ' ' + Rec.Feld2);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  DAO := TDAO.Create(TTestRec); //Übergabe des Types für einen Datensatz
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DAO.Free;
end;
Leider sind hier noch einige Typecasts notwendig, die ich gerne weg haben möchte (dafür wollte ich irgendwie die Klassenreferenz TDAORecType verwenden, aber ich komm damit nicht weiter). Kann sich mal ein Profi meiner annehmen und mich auf den rechten Weg bringen? Evtl. ist das ja auch alles Quatsch, was ich hier fabriziere und ich sollte wie immer rumwursteln...
  Mit Zitat antworten Zitat
webcss

Registriert seit: 10. Feb 2006
255 Beiträge
 
Delphi XE2 Professional
 
#10

Re: Datenbankanwendung sauber strukturieren und programmiere

  Alt 14. Nov 2008, 14:57
Schau Dir doch mal das hier an: PressObjects
ausserdem Sammlung von OPF's
und das hier (ganz wichtig!) design of a robust persistance layer.

Das hier ist auch ganz interessant, wenn auch etwas rudimentär Tabdee persistance layer.

Oder warte noch einen Moment, ich habe genau das, was Du vorhast in Arbeit, ist fast fertig
"Wer seinem Computer Mist erzählt, muss immer damit rechnen..." (unbekannt)
"Der Computer rechnet damit, dass der Mensch denkt..." (auch unbekannt)
mein blog
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 16: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