AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials [Tutorial] Laden / Speichern von Objekten in einer normalisierten Datenbank

[Tutorial] Laden / Speichern von Objekten in einer normalisierten Datenbank

Ein Tutorial von haentschman · begonnen am 27. Jul 2017 · letzter Beitrag vom 24. Mai 2018
Antwort Antwort
Seite 2 von 4     12 34   
Benutzerbild von haentschman
haentschman
Registriert seit: 24. Okt 2006
Hallo...

Das ist mein erstes Tutorial. Bitte seid gnädig... Es soll als Anregung dienen wie man auch ohne datensensitive Controls auskommt.

Ich möchte Euch zeigen, wie man programmintern mit Objekten arbeitet und diese Objekte in einer normalisierten Datenbank speichert. Im Prinzip ist es
ein Mini-ORM ohne externes Framework. Der Kreativität sind keine Grenzen gesetzt.
Ich habe versucht das einfach zu halten. Mancher Code könnte mit unterschiedlichen Methoden realisiert werden. (JOIN statt separater procedure) Welche Variante
man nimmt, ist jedem selbst überlassen.

Hinweise:

* Weil die Units zu lang sind... siehe Projekt.
* Weil nicht alle das compilieren können ist die EXE zum Ausprobieren dabei... siehe Projekt
* Die Zwischenvariablen (wie var Customer: TCustomer; in Main) sind nicht immer notwendig aber es macht es für den Anfang imho übersichtlicher.

Anhänge:
* Projekt mit Datenbank ausführbar.
* kompletter Quelltext D10.1

Voraussetzungen:
Delphi XE und höher wegen Generics im Beispiel

Aufbau 3 Schicht Anwendung:
(Unit: FormMain) -> (Unit: Logic) -> (Unit: Database) -> (Database: z.B.Firebird)
<- (Event) <- (Event)

Database (beliebiebiges DBMS)
Code:
CREATE TABLE T_CUSTOMER (
    ID               INTEGER NOT NULL,
    F_NAME           VARCHAR(50),
    F_FIRST_NAME     VARCHAR(50),
    F_ADDRESS_ID     INTEGER
);


CREATE TABLE T_ADDRESS (
    ID                INTEGER NOT NULL,
    F_POSTCODE        VARCHAR(30),
    F_TOWN            VARCHAR(50),
    F_STREET          VARCHAR(50),
    F_HOUSE_NUMBER    VARCHAR(10)
);
Unit: Database
In dieser Unit ist die Schnittstelle zur Datenbank definiert. In diesem Falle als Interface. Die Logik kennt nur das Interface welches in der Logik instanziert wird. (siehe Unit: Logik)
Die Querys werden nicht mehr auf die Form "geklatscht" und dort die SQL eingetragen. Das Interface kennt alleinig die SQL Statements.
Das macht es einfacher mehrere Datenbanken anzubinden. Für jede Datenbank gibt es dann ein eigenes Interface wegen der Unterschiede der möglichen Datenbanken.
Als Datenbankkomponenten kommen hier die UniDAC mit der Schnittstelle zu Firebird zum Einsatz. Andere Komponenten sind natürlich auch möglich.
Diese eine Unit kennt allein die Datenbank. Die Kommunikation mit der Logik, welche die Unit nicht kennt, kann sowohl über Events oder als Rückgabe der Funktion
aus der Logik erfolgen.

Interface:
Delphi-Quellcode:
unit Database.Interfaces;

interface

uses
  Logic.DataClasses,
  Database.Events;

type
  IDatabaseCommon = interface(IInterface) // ggf. in seperate Unit bei mehreren DBMS
  ['{E41ADEE8-56F9-4223-8238-61B6C033DFF1}']
    function GetAfterConnect: TOnAfterConnectEvent;
    procedure SetAfterConnect(const Value: TOnAfterConnectEvent);
    function GetDatabaseError: TOnDatabaseErrorEvent;
    procedure SetDatabaseError(const Value: TOnDatabaseErrorEvent);
    function GetAfterDisconnect: TOnAfterDisconnectEvent;
    procedure SetAfterDisconnect(const Value: TOnAfterDisconnectEvent);

    property OnAfterConnect: TOnAfterConnectEvent read GetAfterConnect write SetAfterConnect;
    property OnAfterDisconnect: TOnAfterDisconnectEvent read GetAfterDisconnect write SetAfterDisconnect;
    property OnDatabaseError: TOnDatabaseErrorEvent read GetDatabaseError write SetDatabaseError;

    function Connect: Boolean;
    procedure Disconnect;
    procedure StartTransaction;
    procedure Commit;
    procedure Rollback;

    function GetSQLByName(SQLName: string): string; // ggf. bei Laden des SQL Statements aus Ressource
  end;

  IDatabase = interface(IDatabaseCommon)
    ['{C1BC6FE3-9586-4D92-8221-A3DD030E80B5}']
    // Entweder overload oder als einzelne Prozeduren, der Creativität sind keine Grenzen gesetzt.
    procedure FillList(List: TCustomerList); overload;

    function Save(Customer: TCustomer): Integer; overload;
    function Save(Address: TAddress): Integer; overload;

    procedure Get(Customer: TCustomer; ID: Integer); overload;
    procedure Get(Address: TAddress; ID: Integer); overload; // kann auch separat genutzt werden...oder auch nicht
  end;


implementation

end.
Database gekürzt:
Delphi-Quellcode:
unit Database.Firebird;

interface

uses
  System.Classes, System.SysUtils, System.Variants, System.Generics.Collections, System.Generics.Defaults, System.DateUtils,
  Uni, DBAccess, InterBaseUniProvider,
  Database.Interfaces, Database.Events,
  Logic.DataClasses;

type
  TDatabaseFirebird = class(TInterfacedObject, IDatabase)
  strict private
    // Properties Connection
    FConnection: TUniConnection;
    FOnAfterConnect: TOnAfterConnectEvent;
    FOnAfterDisconnect: TOnAfterDisconnectEvent;
    FOnDatabaseError: TOnDatabaseErrorEvent;
    // Getter / Setter
    function GetAfterConnect: TOnAfterConnectEvent;
    procedure SetAfterConnect(const Value: TOnAfterConnectEvent);
    function GetDatabaseError: TOnDatabaseErrorEvent;
    procedure SetDatabaseError(const Value: TOnDatabaseErrorEvent);
    function GetAfterDisconnect: TOnAfterDisconnectEvent;
    procedure SetAfterDisconnect(const Value: TOnAfterDisconnectEvent);
    // Eventhandler
    procedure DoAfterConnect(Sender: TObject);
    procedure DoAfterDisconnect(Sender: TObject);
    procedure DoError(Sender: TObject; E: EDAError; var Fail: Boolean);
    // Funktionen
    function CreateQuery: TUniQuery;
  public
    constructor Create;
    destructor Destroy; override;
    // Events
    property OnAfterConnect: TOnAfterConnectEvent read GetAfterConnect write SetAfterConnect;
    property OnAfterDisconnect: TOnAfterDisconnectEvent read GetAfterDisconnect write SetAfterDisconnect;
    property OnDatabaseError: TOnDatabaseErrorEvent read GetDatabaseError write SetDatabaseError;
    // Funktionen aus Interface
    function Connect: Boolean;
    procedure Disconnect;
    procedure StartTransaction;
    procedure Commit;
    procedure Rollback;

    function GetSQLByName(SQLName: string): string; // ggf. bei Laden des SQL Statements aus Ressource

    procedure FillList(List: TCustomerList); overload;

    function Save(Customer: TCustomer): Integer; overload;
    function Save(Address: TAddress): Integer; overload;

    procedure Get(Customer: TCustomer; ID: Integer); overload;
    procedure Get(Address: TAddress; ID: Integer); overload; // kann auch separat genutzt werden...oder auch nicht
  end;
  
...
Unit: Logic
In dieser Unit ist die Logik definiert. Die Logik nimmt die Befehle der Form entgegen und führt diese aus. Desweiteren hällt die Logik die Daten der Anwendung. In diesem
Falle die CustomerList. Die Kommunikation mit der Form, welche die Unit nicht kennt, kann sowohl über Events oder als Rückgabe der Function aus der Logik erfolgen.

Klassendefinition gekürzt:
Delphi-Quellcode:
unit Logic.DataClasses;

interface

uses
  System.Generics.Collections, System.Generics.Defaults;

type
  TDataState = (ddsNormal, ddsNew, ddsModified, ddsDeleted);

  TBaseClass = class
  strict protected
    FID: Integer;
    FState: TDataState; // jedes Objekt kennt seinen Status
  public
    property ID: Integer read FID write FID;
    property State: TDataState read FState write FState;
  end;

  TAddress = class(TBaseClass)
  strict private
    FTown: string;
    FStreet: string;
    FPostCode: string;
    FHouseNumber: string;
  public
    constructor Create;
    destructor Destroy; override;
    property PostCode: string read FPostCode write FPostCode;
    property Town: string read FTown write FTown;
    property Street: string read FStreet write FStreet;
    property HouseNumber: string read FHouseNumber write FHouseNumber;
  end;

  TCustomer = class(TBaseClass)
  strict private
    FName: string;
    FAddress: TAddress;
    FFirstName: string;
  public
    constructor Create;
    destructor Destroy; override;
    property Name: string read FName write FName;
    property FirstName: string read FFirstName write FFirstName;
    property Address: TAddress read FAddress write FAddress;
  end;

  TCustomerList = TObjectList<TCustomer>;
...
Logic gekürzt:
Delphi-Quellcode:
unit Logic.Base;

interface

uses
  Database.Interfaces, Database.Firebird, Database.Events,
  Logic.DataClasses;

type
  TOnFillCustomerListEvent = procedure(Sender: TObject; List: TCustomerList) of object;
  TOnGetCustomerEvent = procedure(Sender: TObject; Customer: TCustomer) of object;
  TOnDataChangedEvent = procedure(Sender: TObject; State: Boolean) of object;

  TLogic = class
  strict private
    FDatabase: IDatabase;
    FCustomerList: TCustomerList;

    FOnConnectDatabase: TOnAfterConnectEvent;
    FOnDisconnectDatabase: TOnAfterDisconnectEvent;
    FOnDatabaseError: TOnDatabaseErrorEvent;
    FOnFillCustomerList: TOnFillCustomerListEvent;
    FOnGetCustomer: TOnGetCustomerEvent;

    procedure DoOnDatabaseError(Sender: TObject; ErrorNumber: Integer; ErrorMessage: string);
    procedure DoOnAfterConnect(Sender: TObject);
    procedure DoOnAfterDisconnect(Sender: TObject);
  private
    FDataChanged: Boolean;
    FOnDataChanged: TOnDataChangedEvent;
    procedure SetDataChanged(const Value: Boolean);
  public
    constructor Create;
    destructor Destroy; override;

    property OnConnectDatabase: TOnAfterConnectEvent read FOnConnectDatabase write FOnConnectDatabase;
    property OnDisconnectDatabase: TOnAfterDisconnectEvent read FOnDisconnectDatabase write FOnDisconnectDatabase;
    property OnDatabaseError: TOnDatabaseErrorEvent read FOnDatabaseError write FOnDatabaseError;
    property OnFillCustomerList: TOnFillCustomerListEvent read FOnFillCustomerList write FOnFillCustomerList;
    property OnGetCustomer: TOnGetCustomerEvent read FOnGetCustomer write FOnGetCustomer;
    property OnDataChanged: TOnDataChangedEvent read FOnDataChanged write FOnDataChanged;

    property DataChanged: Boolean read FDataChanged write SetDataChanged;

    property CustomerList: TCustomerList read FCustomerList;

    procedure GetCustomerList;
    procedure GetCustomer(ID: Integer);
    procedure SaveCustomer(Customer: TCustomer);
    procedure RefreshCustomerList;
  end;
...
Unit: FormMain gekürzt
In dieser Unit ist die Form mit den Controls definiert. Die Form gibt der Logic Befehle was sie an Informationen haben möchte. Über Events werden die Information aus der Logik
zurückgeliefert und verarbeitet.
Delphi-Quellcode:
unit FormMain;

interface

uses
  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants, System.Actions, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.ActnList, Vcl.ComCtrls,
  ImageList.Small,
  Logic.Base, Logic.DataClasses;

const
  conTextGroupboxNormal = 'Details (Normal)';
  conTextGroupboxEdit = 'Details (Editmodus)';

type
  TfoMain = class(TForm)
    pnlTop: TPanel;
    pnlContent: TPanel;
    lvCustomers: TListView;
    btnNew: TButton;
    btnCopy: TButton;
    btnDelete: TButton;
    grpDetails: TGroupBox;
    btnSave: TButton;
    edtName: TEdit;
    lblName: TLabel;
    edtFirstName: TEdit;
    lblFirstName: TLabel;
    edtPostCode: TEdit;
    lblPostCode: TLabel;
    edtTown: TEdit;
    lblTown: TLabel;
    edtStreet: TEdit;
    lblStreet: TLabel;
    edtHouseNumber: TEdit;
    lblHouseNumber: TLabel;
    actlstMain: TActionList;
    actNew: TAction;
    btnInfo: TButton;
    actCopy: TAction;
    actDelete: TAction;
    actInfo: TAction;
    actSave: TAction;
    actCancel: TAction;
    btnCancel: TButton;
    btnMessage: TButton;
    actMessage: TAction;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure actNewExecute(Sender: TObject);
    procedure actCopyExecute(Sender: TObject);
    procedure actDeleteExecute(Sender: TObject);
    procedure actInfoExecute(Sender: TObject);
    procedure actSaveExecute(Sender: TObject);
    procedure actCancelExecute(Sender: TObject);
    procedure lvCustomersChange(Sender: TObject; Item: TListItem; Change: TItemChange);
    procedure actMessageExecute(Sender: TObject);
  private
    FLogic: TLogic;
    procedure DoFillCustomerList(Sender: TObject; List: TCustomerList);
    procedure DoGetCustomer(Sender: TObject; Customer: TCustomer);
    procedure DoDataChanged(Sender: TObject; State: Boolean);

    procedure ShowCustomerList(List: TCustomerList);
    procedure ShowCustomer(Customer: TCustomer);
    procedure SetCustomerToEdit(Active: Boolean);
    procedure SetButtons(Active: Boolean);
  public

  end;

var
  foMain: TfoMain;
...

Prinzip des Datenholens:

Delphi-Quellcode:
procedure TDatabaseFirebird.Get(Customer: TCustomer; ID: Integer); // nur einen Customer holen
var
  Qry: TUniQuery;
begin
  Qry := CreateQuery; // Query incl. der Connection erzeugen
  try
    // SQL wie gehabt
    Qry.SQL.Text := 'SELECT * FROM T_CUSTOMER WHERE ID = :ID';
// Alternativ über Ressource: Qry.SQL.Text := GetSQLByName('xxx'); // SQL Name ergänzen
    Qry.ParamByName('ID').AsInteger := ID;
    Qry.Open;
    
    // das Objekt füllen
    Customer.ID := Qry.FieldByName('ID').AsInteger;
    Customer.Name := Qry.FieldByName('F_NAME').AsString;
    Customer.FirstName := Qry.FieldByName('F_FIRST_NAME').AsString;
    // Alternative für GET wäre ein JOIN im Statement und die Adresse hier zusammenbauen.
    // Der Vorteil der Trennung: Man kann auch die Adresse seperat lesen. Wie man es braucht... :-)
    Get(Customer.Address, Qry.FieldByName('F_ADDRESS_ID').AsInteger);
    Customer.State := ddsNormal; // Wichtig: Status setzen
  finally
    Qry.Free;
  end;
end;

...

procedure TDatabaseFirebird.FillList(List: TCustomerList); // komplette Liste füllen
var
  Qry: TUniQuery;
  Customer: TCustomer;
begin
  List.Clear;
  Qry := CreateQuery; // Query incl. der Connection erzeugen
  try
    Qry.SQL.Text := 'SELECT * FROM T_CUSTOMER';
// Alternativ über Ressource: Qry.SQL.Text := GetSQLByName('xxx'); // SQL Name ergänzen
    Qry.Open;
    while not Qry.Eof do
    begin
      Customer := TCustomer.Create; // Objekt erzeuggen
      Get(Customer, Qry.FieldByName('ID').AsInteger); // Objekt füllen
      List.Add(Customer); // Objekt in Liste legen
      Qry.Next;
    end;
  finally
    Qry.Free;
  end;
end;
Prinzip der Speicherung
Delphi-Quellcode:
function TDatabaseFirebird.Save(Customer: TCustomer): Integer; // Speichern
var
  Qry: TUniQuery;
begin
  Result := -1;
  Qry := CreateQuery;
  try
    StartTransaction;
    try
      case Customer.State of // entsprechend dem Status des Objektes
        ddsNew: // insert
          begin
            Customer.Address.ID := Save(Customer.Address); // Rückgabe der ID als Erstes wegen ID
            Qry.SQL.Text := 'INSERT INTO T_CUSTOMER (F_NAME, F_FIRST_NAME, F_ADDRESS_ID) VALUES (:NAM, :FIN, :ADD) returning ID';
            // Alternativ über Ressource: Qry.SQL.Text := GetSQLByName('xxx'); // SQL Name ergänzen
            // das Objekt dem SQL übergeben
            Qry.ParamByName('NAM').AsString := Customer.Name;
            Qry.ParamByName('FIN').AsString := Customer.FirstName;
            Qry.ParamByName('ADD').AsInteger := Customer.Address.ID;
            Qry.ExecSQL;
            Customer.ID := Qry.ParamByName('RET_ID').AsInteger;

            Customer.State := ddsNormal;
            Result := Customer.ID;
          end;
        ddsModified: // update
          begin
            Qry.SQL.Text := 'UPDATE T_CUSTOMER SET F_NAME = :NAM, F_FIRST_NAME = :FIN, F_ADDRESS_ID = :ADD WHERE ID = :ID';
            // Alternativ über Ressource: Qry.SQL.Text := GetSQLByName('xxx'); // SQL Name ergänzen
            // das Objekt dem SQL übergeben
            Qry.ParamByName('ID').AsInteger := Customer.ID;
            Qry.ParamByName('NAM').AsString := Customer.Name;
            Qry.ParamByName('FIN').AsString := Customer.FirstName;
            Qry.ParamByName('ADD').AsInteger := Customer.Address.ID;
            Qry.ExecSQL;

            Save(Customer.Address);

            Customer.State := ddsNormal;
            Customer.Address.State := ddsNormal;
            Result := Customer.ID;
          end;
        ddsDeleted: // deleted
          begin
            Qry.SQL.Text := 'DELETE FROM T_CUSTOMER WHERE ID = :ID';
            // Alternativ über Ressource: Qry.SQL.Text := GetSQLByName('xxx'); // SQL Name ergänzen
            Qry.ParamByName('ID').AsInteger := Customer.ID;
            Qry.ExecSQL;

            Customer.Address.State := ddsDeleted;
            Save(Customer.Address);

            Result := Customer.ID;
          end;
      end;

      Commit;
    except
      Rollback;
    end;
  finally
    Qry.Free;
  end;
end;
Prinzip der Datenanzeige
Delphi-Quellcode:
procedure TfoMain.DoFillCustomerList(Sender: TObject; List: TCustomerList); // Event nach dem Datenholen
begin
  ShowCustomerList(List);
  SetCustomerToEdit(False);
end;

...

procedure TfoMain.ShowCustomerList(List: TCustomerList);
var
  Item: TListItem;
  Customer: TCustomer;
begin
  lvCustomers.Items.Clear;
  for Customer in List do
  begin
    Item:= lvCustomers.Items.Add;
    Item.Data:= Customer; // Das Objekt (Pointer) hängt an dem Eintrag
    Item.SubItems.Add(Customer.Name);
    Item.SubItems.Add(Customer.FirstName);
    Item.SubItems.Add(Customer.Address.Town);
    Item.ImageIndex:= dmSmall.GetIconIndexDataState(Customer.State);
  end;
  lvCustomers.Items.Item[0].Selected := True; // ersten Eintrag markieren...oder so
end;
Erweiterungen:
Nach Bedarf können u.a. Funktionen hinzugefügt oder mit einander kombiniert werden. (DRY)

Vorteile:
* Keine datensensitiven Controls. Das bedeutet Unabhängigkeit von der Optik der DB sensitiven Controls.
* Eine Property des Objektes kann z.B. in einem TEdit einem TMemo oder mit einem TRotMitGelbenPunktenControl dargestellt werden.
* Alle SQL Statements auf einem Fleck. Das erleichtert das Suchen nach einem Statement. Die SQL sind nicht mehr auf den gesamten QT verteilt.
* Die Umbauten bei Datenbankwechsel beziehen sich nur auf das Interface.
* Durch die Objekte kann man sich die Informationen beliebig zustammenstellen. Auch wenn sie auch verschieden Tabelle stammen.
* Speichern mit einem Einzeiler
Delphi-Quellcode:
FDatabase.Save(Customer);
...
* Objekte sind besser debugbar.
* Objekte können wiederum Listen mit Objekten enthalten.
* Objekte sind mit einem Rutsch, über das Database Interface, speicherbar.

Bei diesem Tutorial geht es ums Prinzip bei der Arbeit mit Objekten ohne die üblichen Verdächtigen der großen Frameworks für Datenbanken. Meistens lohnt der Aufwand nicht
ein großes Framework zu installieren. Auch der Einarbeitungsaufwand bei diesen ist nicht ohne... Manchmal ist weniger mehr.


Diskussion eröffnet.

PS: Alle Fehler sind urheberrechtlich geschützt weil Unikate.
Miniaturansicht angehängter Grafiken
tutorial.png  
Angehängte Dateien
Dateityp: zip Tutorial_Projekt.zip (4,82 MB, 88x aufgerufen)

Geändert von haentschman (27. Jul 2017 um 15:01 Uhr)
 
Benutzerbild von haentschman
haentschman

 
Delphi 12 Athens
 
#11
  Alt 28. Jul 2017, 07:10
Moin...
Zitat:
Danke für das Tutorial.
Zitat:
Erst einmal vielen Dank!
...Dankeschön.

Zitat:
Der Spass fängt aber dann an wenn du ein Grid oder Listview scrollen und editieren möchtest,
Zitat:
Wie würdest du denn so etwas bei deinem Vorschlag lösen ?
Persönlich habe ich mich entschieden, daß niemals in der Listview editiert wird. Das hatte den Grund, daß mehr Informationen zu editieren waren als Spalten optisch zur Verfügung standen. Zum Editieren blende ich einfach eine modale Form über die Listview ein. Ich nenne sie InlineEditor. (siehe Bild 1) Damit kann ich beliebige Informationen editieren und die Controls im InlineEditor selbst, auch wegen der Optik, festlegen. Dem InlineEditor wird das Objekt, welches am Listvieweintrag hängt, übergeben...usw. (siehe Bild 2) Am Objekt kann man sehen, daß das mit dem Dataset nicht übersichtlich möglich ist. Das Objekt würde eigentlich aus mindestens 3 Datasets bestehen.
Zitat:
Das müsste ja meiner Meinung nach auch gekapselt werden, das will man nicht immer wieder neu schreiben müssen.
Der Kreativität sind keine Grenzen gesetzt. In meinen Produktivprojekten ist einiges als gemeinsamer Code ausgelagert um nicht alles neu schreiben zu müssen. Beispiel. Der InlineEditor. Den leite ich von der Basis, mit aller Logik für die Anzeige etc., ab.
...aber das würde zu weit führen und hat nichts mit dem Thema zu tun.
Zitat:
aber das wären nur Detailänderungen, die mir pers. besser in den Kram passen würden.
Der Kreativität sind keine Grenzen gesetzt. Du kannst das Prinzip für deine Bedürfnisse anpassen.
Zitat:
sollte immer die (formal) gleiche Liste liefern.
...das ist korrekt.
Zitat:
Mit einem TDataModule kommt man (ich) genauso weit, einziges Problem(?) für jede DB brauch ich ein eigenes Modul
1. Wenn eh keine Querykomponenten auf dem DataModule liegen kannst du dir das auch sparen. Prinzipiell kommst du auch mit einem DataModule für eine Datenbank zurecht. Wenn du ein neues DBMS dazunimmst, kommst du um eine Abstraktion nicht herum.
Delphi-Quellcode:
// Klassendefinition oder so ähnlich
TDataBaseAbstract = class
public
  procedure Blubb; abstract;
end;
...
TDataBase1 = class(TDataBaseAbstract)
public
  procedure Blubb; override; // die spezielle Implementierung für Blubb
end;
...
TDataBase2 = class(TDataBaseAbstract)
public
  procedure Blubb; override; // die spezielle Implementierung für Blubb
end;

// Instanz
FDatabase: TDataBaseAbstract;
...
FDatabase := TDataBase2.Create; // oder so...
...die Vorteile von Interfaces wurde schon an anderer Stelle diskutiert. http://www.delphipraxis.net/192364-t...nterfaces.html
Miniaturansicht angehängter Grafiken
inlineeditor.png   objekt.png  

Geändert von haentschman (28. Jul 2017 um 16:27 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von p80286
p80286

 
FreePascal / Lazarus
 
#12
  Alt 28. Jul 2017, 13:06
Prinzipiell kommst du auch mit einem DataModule für eine Datenbank zurecht. Wenn du ein neues DBMS dazunimmst, kommst du um eine Abstraktion nicht herum.
Danke, das hätte man mir ja auch gleich so erklären können, die ODenke hab ich eben immer noch nicht ganz verinnerlicht.

Gruß
K-H
  Mit Zitat antworten Zitat
Benutzerbild von haentschman
haentschman

 
Delphi 12 Athens
 
#13
  Alt 28. Jul 2017, 13:49
Hallöle...
Zitat:
Danke, das hätte man mir ja auch gleich so erklären können
Das wichtige ist nicht das Interface. Ein Datenmodul tut es auch. Sondern die Verlagerung aller SQL Statements in eine Unit ist Voraussetzung. Dann kann man "schnell" wechseln...
  Mit Zitat antworten Zitat
Benutzerbild von KodeZwerg
KodeZwerg

 
Delphi 11 Alexandria
 
#14
  Alt 24. Mai 2018, 00:41
Hallo, Vorweg: Das Tutorial ist gut gelungen auch wenn da mehr Code gezeigt wird als das Code erklärt wird.
Neugierig wie ich bin plus DB Neuling habe ich mir das Projekt geladen und beim Start der .exe erscheint das (gekürzt aufs wesentliche):
Zitat:
executable : Tutorial.exe
exec. date/time : 2017-07-27 14:01
version : 1.0.0.0
compiled with : Delphi 10.1 Berlin
madExcept version : 4.0.16
callstack crc : $0c5e2cf1, $23db6709, $23db6709
exception number : 1
exception class : EAccessViolation
exception message : Zugriffsverletzung bei Adresse 007FF967 in Modul 'Tutorial.exe'. Lesen von Adresse 00000008.

main thread ($11f8):
007ff967 +00b Tutorial.exe Logic.Base 143 +2 TLogic.GetCustomerList
00800a6e +026 Tutorial.exe FormMain 210 +2 TfoMain.FormShow
0065a34d +015 Tutorial.exe Vcl.Forms TCustomForm.DoShow
0065ebb9 +0a9 Tutorial.exe Vcl.Forms TCustomForm.CMShowingChanged
005a700e +2be Tutorial.exe Vcl.Controls TControl.WndProc
005abb59 +5e9 Tutorial.exe Vcl.Controls TWinControl.WndProc
0065ae0d +64d Tutorial.exe Vcl.Forms TCustomForm.WndProc
005a6c48 +024 Tutorial.exe Vcl.Controls TControl.Perform
005aaf21 +10d Tutorial.exe Vcl.Controls TWinControl.UpdateShowing
005ab030 +0bc Tutorial.exe Vcl.Controls TWinControl.UpdateControlState
005adc2a +026 Tutorial.exe Vcl.Controls TWinControl.CMVisibleChanged
005a700e +2be Tutorial.exe Vcl.Controls TControl.WndProc
005abb59 +5e9 Tutorial.exe Vcl.Controls TWinControl.WndProc
0065ae0d +64d Tutorial.exe Vcl.Forms TCustomForm.WndProc
005a6c48 +024 Tutorial.exe Vcl.Controls TControl.Perform
005a55fa +026 Tutorial.exe Vcl.Controls TControl.SetVisible
0065a629 +03d Tutorial.exe Vcl.Forms TCustomForm.SetVisible
00664897 +0b3 Tutorial.exe Vcl.Forms TApplication.Run
0080e9d9 +061 Tutorial.exe Tutorial 25 +5 initialization
77533675 +010 kernel32.dll BaseThreadInitThunk

thread $12f4:
77cd00f6 +0e ntdll.dll NtWaitForMultipleObjects
77533675 +10 kernel32.dll BaseThreadInitThunk

thread $1128:
77cd1edf +0b ntdll.dll NtWaitForWorkViaWorkerFactory
77533675 +10 kernel32.dll BaseThreadInitThunk

thread $104c:
77cd1edf +0b ntdll.dll NtWaitForWorkViaWorkerFactory
77533675 +10 kernel32.dll BaseThreadInitThunk

thread $afc:
77cd1edf +0b ntdll.dll NtWaitForWorkViaWorkerFactory
77533675 +10 kernel32.dll BaseThreadInitThunk

modules:
00400000 Tutorial.exe 1.0.0.0 \Win32
10000000 fbclient.dll 2.5.1.26351 \Win32
4a800000 icuuc30.dll 3.0.0.0 \Win32
4ad00000 icudt30.dll 3.0.0.0 \Win32

disassembling:
[...]
007ff95d mov ebp, esp
007ff95f push ecx
007ff960 push ebx
007ff961 mov [ebp-4], eax
007ff964 143 mov eax, [ebp-4]
007ff967 > mov edx, [eax+8]
007ff96a mov eax, [ebp-4]
007ff96d mov eax, [eax+4]
007ff970 mov ecx, [eax]
007ff972 call dword ptr [ecx+$3c]
007ff972
[...]
Müssen Voraussetzungen erfüllt werden um die .exe zu Starten?
  Mit Zitat antworten Zitat
Benutzerbild von haentschman
haentschman

 
Delphi 12 Athens
 
#15
  Alt 24. Mai 2018, 06:46
Moin...
Da ich auf der Arbeit kein UniDAC habe, kann ich es erst am Wochenende nachschauen. Aber normalerweise gibt es keine Voraussetzungen als die EXE im "out" Ordner zu starten.

Nachtrag:
Zitat:
da mehr Code gezeigt wird als das Code erklärt wird.
...mit Absicht. Nicht jede Zeile muß man erklären. Für tiefsinnige Fragen ist hier der Platz dafür.

Geändert von haentschman (24. Mai 2018 um 07:30 Uhr)
  Mit Zitat antworten Zitat
mkinzler

 
Delphi 11 Alexandria
 
#16
  Alt 24. Mai 2018, 07:54
Was steht den in der Zeile?
Markus Kinzler
  Mit Zitat antworten Zitat
Benutzerbild von haentschman
haentschman

 
Delphi 12 Athens
 
#17
  Alt 24. Mai 2018, 08:08
Zitat:
007ff967 +00b Tutorial.exe Logic.Base 143 +2 TLogic.GetCustomerList
Delphi-Quellcode:
procedure TLogic.GetCustomerList;
begin
  // Daten holen und in die übergebene Liste ablegen
  FDatabase.FillList(FCustomerList); // <- 143 FCustomer List ist instanziert
  DataChanged := False; // Setter
  // Liste per Event übergeben
  if Assigned(FOnFillCustomerList) then
  begin
    FOnFillCustomerList(Self, FCustomerList);
  end;
end;
Delphi-Quellcode:
constructor TLogic.Create;
begin
  FDataChanged := False;
  // Customer Liste erstellen
  FCustomerList := TCustomerList.Create(True); // Instanz
  // Datenbank Interface
  FDatabase := TDatabaseFirebird.Create; // An diesem Punkt wird das Interface gewechselt auf ein anderes DBMS z.b. entsprechend der Einstellungen in der INI/XML usw.
  // Eventhandler einhängen
  FDatabase.OnAfterConnect := DoOnAfterConnect;
  FDatabase.OnAfterDisconnect := DoOnAfterDisconnect;
  FDatabase.OnDatabaseError := DoOnDatabaseError;

  FDatabase.Connect; // ggf. Prüfung auf Rückgabewert (Connected)
end;

destructor TLogic.Destroy;
begin
  FCustomerList.Free; // die enthaltenen Objekte werden automatisch weggeräumt.
  // Keine Freigabe des Interfaces nötig. :-)
  inherited;
end;

Geändert von haentschman (24. Mai 2018 um 08:10 Uhr)
  Mit Zitat antworten Zitat
mkinzler

 
Delphi 11 Alexandria
 
#18
  Alt 24. Mai 2018, 08:15
Wenn man das Programm ausführt, kommt der Fehler bei mir auch. Selberkompiliert funktioniert es, nachdem ich die Datenbankverbindung auf embedded umgestellt habe.

FConnection.Server := ''; //'firma-server/3025'; // Embedded
Angehängte Dateien
Dateityp: zip Exe.zip (1,72 MB, 3x aufgerufen)
Markus Kinzler
  Mit Zitat antworten Zitat
Benutzerbild von haentschman
haentschman

 
Delphi 12 Athens
 
#19
  Alt 24. Mai 2018, 08:29
Echt jetzt? Zu meiner Verteidigung...ich arbeite nie mit Embedded.
Zitat:
'firma-server/3025'; // Embedded
...das muß logischerweise raus! Ich hatte das noch "markiert" um es nicht zu vergessen. Getreu dem Motto: Wer nichts macht, macht nichts falsch...und wer nichts falsch macht, wird befördert.

@mkinzler: Danke.

Geändert von haentschman (24. Mai 2018 um 08:33 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von KodeZwerg
KodeZwerg

 
Delphi 11 Alexandria
 
#20
  Alt 24. Mai 2018, 09:13
Danke mkinzler, Deine .exe kann ich Starten!
Ich habe soeben Versucht das Projekt mit Tokyo zu öffnen = Package PngComponents fehlte mir, hab das von Uwe Raabe geladen und installiert also öffnen lässt es sich jetzt ohne Probleme.
Jetzt das nächste mir fehlende, Uni.pas. Ich vermute UniDAC ist damit gemeint und hier meine Frage:
Reicht da die Express oder muss ich die Pro Version nehmen? (ich = planlos da Neuling)

Vielen Dank für Hilfe!

Das MadExcept aus der .dpr habe ich auskommentiert, reicht das aus oder muss ich MadExcept dafür auch laden und installieren?
  Mit Zitat antworten Zitat
Themen-Optionen Tutorial durchsuchen
Tutorial durchsuchen:

Erweiterte Suche
Ansicht

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 23:12 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