hat jemand ein vollständiges Codebeispiel für eine zuverlässig funktionierende Master-Detail-Beziehung zweier dBase-Tabellen (TDbf)? Keines der wenigen Codebeispiele im Netz führt mich zum Erfolg.
1) Ich weiß, dass TDbf veraltet ist. Auf dem Stand, zu dem die Entwicklung eingestellt wurde, soll es aber lt. Lazarus Tdbf Tutorial zwar keine referenzielle Integrität, aber eine vollständige Master-Detail-Funktionalität gegeben haben.
2) Es gibt Gründe, warum ich nicht SQLite (oder eine Serverlösung wie MariaDB) benutze(n kann).
3) Ich benutze LazarusPortable 1.0.0.0 mit FPC 2.6.0 (aus denselben Gründen).
3) Vollständige Master-Detail-Funktionalität: Darunter verstehe ich, dass ich einen existierenden Datensatz in der Mastertabelle markiere und in der Detail-Tabelle
a) neue Datensätze beim Speichern automatisch die ID dieses Master-Datensatzes zugeordnet bekommen, ohne dass ich diesen Vorgang programmieren muss,
b) nur diejenigen bereits existierenden Detail-Datensätze angezeigt werden, welche die ID des Master-Datensatzes haben, ohne dass ich diesen Filtervorgang programmieren muss.
4) Referenzielle Integrität: Darunter verstehe ich, dass, wenn ich einen Datensatz in der Master-Tabelle lösche, alle mit ihm verbundenen Datensätze in der Detail-Tabelle ebenfalls gelöscht werden. Diese Funktionalität benötige ich hier nicht.
Mein eigener Code
Site = Krankenhaus, Patient selbsterklärend, Urine = Urinausscheidung in Millilitern, Creatinine = ein Laborwert zur Nierenfunktion
Jede Site hat einige Patienten (1:n).
Jeder Patient hat ein stündliches Urinausscheidungsvolumen für die Stunden nach Schließung der Operationsnaht (n:m).
Jeder Patient hat einige Kreatinin-Blutlaborwerte vor und nach der Operation (ebenfalls n:m).
Hier soll also sogar eine 1:n:m-Beziehung abgebildet werden, und zwar gleich zweimal:
Sites:Patients:Urine
Sites:Patients:Creatinine
Ich gebe mich aber gern mit einem einzigen 1:n-Beispiel zufrieden, um die Dinge einfach zu halten:
Sites:Patients
In der aktuellen Fassung habe ich in sites.dbf drei Krankenhäuser angelegt und in patients.dbf zwei Patienten für das dritte Krankenhaus (SiteID = 2). Die SiteID in patients.dbf habe ich selbst von Hand eintragen müssen (urine.dbf und creatinine.dbf sind noch leer). Fun fact: Obwohl die Master-Tabelle sites.dbf drei Datensätze hat, wird mir nur derjenige Datensatz angezeigt, der in patients.dbf Detail-Datensätze hat.
Code: Alles auswählen
unit FormMain;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
DBGrids, DbCtrls, Dbf, db {FieldTypes ft...};
type
{ TfrmMain }
TfrmMain = class(TForm)
dsCreatinine: TDatasource;
dsUrine: TDatasource;
dsPatients: TDatasource;
dsSites: TDatasource;
DBGridUrine: TDBGrid;
DBGridCreatinine: TDBGrid;
DBGridSites: TDBGrid;
DBGridPatients: TDBGrid;
DBNavUrine: TDBNavigator;
DBNavCreatinine: TDBNavigator;
DBNavSites: TDBNavigator;
DBNavPatients: TDBNavigator;
PageControlMain: TPageControl;
StatusBarMain: TStatusBar;
TabSheetCreatinine: TTabSheet;
TabSheetUrine: TTabSheet;
TabSheetPatients: TTabSheet;
TabSheetSites: TTabSheet;
ToolBarMain: TToolBar;
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
frmMain: TfrmMain;
DbfSites, DbfPatients, DbfUrine, DbfCreatinine: TDbf;
dsSites, dsPatients, dsUrine, dsCreatinine: TDataSource;
implementation
{$R *.lfm}
{ TfrmMain }
procedure TfrmMain.FormCreate(Sender: TObject);
var
path: String;
begin
{DataSets in jedem Fall anlegen}
DbfSites := TDbf.Create(self);
DbfPatients := TDbf.Create(self);
DbfUrine := TDbf.Create(self);
DbfCreatinine := TDbf.Create(self);
{absoluten Pfad der .exe ermitteln}
path := ExtractFilePath(Application.EXEName);
{Prüfen, ob es das relative Verzeichnis 'database' gibt}
if not DirectoryExists(path + 'database') then
begin
CreateDir(path + 'database');
end;
{Prüfen, ob es die dBase-Tabelle 'database\sites.dbf' gibt}
if not FileExists(path + 'database\' + 'sites.dbf') then
begin
with DbfSites do begin
TableLevel := 7;
FilePathFull := Path + 'database\';
TableName := 'sites.dbf';
with FieldDefs do begin
Add('SiteID', ftAutoInc, 0, True);
Add('SiteNo', ftString, 4, True);
Add('SiteName', ftString, 25);
end;
CreateTable;
Exclusive := True;
Open;
AddIndex('idxSiteID', 'SiteID', [ixPrimary, ixUnique]);
AddIndex('idxSiteNo','SiteNo', [ixCaseInsensitive]);
AddIndex('idxSiteName','SiteName', [ixCaseInsensitive]);
Close;
end;
end;
{Prüfen, ob es die dBase-Tabelle 'database\patients.dbf' gibt}
if not FileExists(path + 'database' + DirectorySeparator + 'patients.dbf') then
begin
with DbfPatients do begin
TableLevel := 7;
FilePathFull := Path + 'database';
TableName := 'patients.dbf';
// Feldstruktur definieren
with FieldDefs do begin
Add('PatID', ftAutoInc, 0, True);
Add('SiteID', ftInteger, 0, True);
Add('PatNo', ftString, 8, True);
Add('PatBodyWeight', ftFloat, 0, True);
Add('PatDateTimeSuture', ftDateTime, 0, True);
end;
CreateTable;
// Indizes hinzufügen
Exclusive := True;
Open;
AddIndex('idxPatID', 'PatID', [ixPrimary, ixUnique]);
AddIndex('idxSiteID', 'SiteID', [ixCaseInsensitive]);
AddIndex('idxPatNo', 'PatNo', [ixCaseInsensitive]);
Close;
end;
end;
{Prüfen, ob es die dBase-Tabelle 'database\urine.dbf' gibt}
if not FileExists(path + 'database' + DirectorySeparator + 'urine.dbf') then
begin
with DbfUrine do begin
TableLevel := 7;
FilePathFull := Path + 'database';
TableName := 'urine.dbf';
with FieldDefs do begin
Add('UrineID', ftAutoInc, 0, True);
Add('PatID', ftInteger, 0, True);
Add('Hour', ftInteger);
Add('Excretion', ftInteger);
Add('Stage', ftInteger);
end;
CreateTable;
Exclusive := True;
Open;
AddIndex('idxUrineID', 'UrineID', [ixPrimary, ixUnique]);
AddIndex('idxPatID', 'PatID', [ixCaseInsensitive]);
Close;
end;
end;
{Prüfen, ob es die dBase-Tabelle 'database\creatinine.dbf' gibt}
if not FileExists(path + 'database' + DirectorySeparator + 'creatinine.dbf') then
begin
with DbfCreatinine do begin
TableLevel := 7;
FilePathFull := Path + 'database';
TableName := 'creatinine.dbf';
with FieldDefs do begin
Add('CreatinineID', ftAutoInc, 0, True);
Add('PatID', ftInteger, 0, True);
Add('TestDateTime', ftDateTime, 0, True);
Add('Value', ftFloat, 0, True);
Add('Unit', ftString, 6, True);
Add('HoursAfterSuture', ftInteger, 0, True);
Add('Stage', ftInteger);
end;
CreateTable;
Exclusive := True;
Open;
AddIndex('idxCreatinineID', 'CreatinineID', [ixPrimary, ixUnique]);
AddIndex('idxPatID', 'PatID', [ixCaseInsensitive]);
Close;
end;
end;
{DataSources mit den zur Laufzeit erstellten DataSets verbinden}
dsSites.DataSet := DbfSites;
dsPatients.DataSet := DbfPatients;
dsUrine.DataSet := DbfUrine;
dsCreatinine.DataSet := DbfCreatinine;
{Master-Detail-Beziehungen definieren und DataSets öffnen}
with DbfSites do begin
FilePathFull := Application.Location + 'database';
TableName := 'sites.dbf';
Open;
{'sites.dbf' Master-Tabelle, 1:n-Beziehung zu 'patients.dbf'}
IndexName := 'idxSiteID'; // Index des Master-Felds
end;
with DbfPatients do begin
FilePathFull := Application.Location + 'database';
TableName := 'patients.dbf';
Open;
{'patients.dbf' Master-Tabelle, n:m-Beziehung zu 'urine/creatinine.dbf'}
IndexName := 'idxPatID'; // Index des Master-Felds
{'patients.dbf' Detail-Tabelle, 1:n Beziehung zu 'sites.dbf'}
MasterSource := dsSites; // DataSet der Master-Tabelle
MasterFields := 'SiteID'; // Master-Feld der Master-Tabelle
IndexName := 'idxSiteID'; // Index des Master-Felds
end;
with DbfUrine do begin
FilePathFull := Application.Location + 'database';
TableName := 'urine.dbf';
Open;
{'urine.dbf' Detail-Tabelle, n:m-Beziehung zu 'patients.dbf'}
MasterSource := dsPatients; // DataSet der Master-Tabelle
MasterFields := 'PatID'; // Master-Feld der Master-Tabelle
IndexName := 'idxPatID'; // Index des Master-Felds
end;
with DbfCreatinine do begin
FilePathFull := Application.Location + 'database';
TableName := 'creatinine.dbf';
Open;
{'creatinine.dbf' auch Detail-Tabelle, n:m-Beziehung zu 'patients.dbf'}
MasterSource := dsPatients; // DataSet der Master-Tabelle
MasterFields := 'PatID'; // Master-Feld der Master-Tabelle
IndexName := 'idxPatID'; // Index des Master-Felds
end;
end;
end.
René