![]() |
Datenbank: MSSQL • Zugriff über: ADO
Datenbankanwendung sauber strukturieren und programmieren
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? |
Re: Datenbankanwendung sauber strukturieren und programmiere
|
Re: Datenbankanwendung sauber strukturieren und programmiere
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 |
Re: Datenbankanwendung sauber strukturieren und programmiere
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 |
Re: Datenbankanwendung sauber strukturieren und programmiere
Zitat:
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 |
Re: Datenbankanwendung sauber strukturieren und programmiere
Hi,
eine entsprechende Abstraktion gelingt über TDataSet. Dazu hier ein kleines Beispiel: Die Datenklasse (mit ADO auf Access)
Delphi-Quellcode:
Und so wird die verwendet (ein Button, ein Datengrid und eine damit verknüpfte DataSource)
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.
Delphi-Quellcode:
Als weiterer Hinweis seien noch die StoredProcedures genannt, die z.B. bei MSSQL, MySQL, etc. möglich sind.
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. 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 |
Re: Datenbankanwendung sauber strukturieren und programmiere
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! |
Re: Datenbankanwendung sauber strukturieren und programmiere
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 :oops: ) 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. |
Re: Datenbankanwendung sauber strukturieren und programmiere
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:
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.
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; In TDAO sind dann die entsprechenden Methoden, um diese Struktur zu verwalten.
Delphi-Quellcode:
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...
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; |
Re: Datenbankanwendung sauber strukturieren und programmiere
Schau Dir doch mal das hier an:
![]() ausserdem ![]() und das hier (ganz wichtig!) ![]() Das hier ist auch ganz interessant, wenn auch etwas rudimentär ![]() Oder warte noch einen Moment, ich habe genau das, was Du vorhast in Arbeit, ist fast fertig :wink: |
Alle Zeitangaben in WEZ +1. Es ist jetzt 04:07 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-2025 by Thomas Breitkreuz