Inspiriert durch diese
Frage im Forum, möchte ich die Gelegenheit nutzen, einige mORMot Funktionen an einem Beispiel zu demonstrieren. Ziel ist es, so viel Funktionalität wie möglich mit nur wenigen Zeilen Quelltext zu präsentieren. Es geht hier um Konzepte, nicht um eine fertige Copy-Paste Lösung. Die enthaltenen Verweise zur Dokumentation verlinken zur aktuell verfügbaren
mORMot1 Hilfe. Für das Beispiel wird mORMot2 verwendet. Die Namen für Klassen und Funktionen können sich leicht unterscheiden.
Im Anhang befindet sich der Sourcecode und das ausführbare Programm.
Disclaimer: Der Sourcecode ist weder getestet noch optimiert. Er sollte mit Delphi ab Version 10.2 funktionieren. Die Benutzung der zur Verfügung gestellten Materialien erfolgt auf eigene Gefahr.
Die erstellte Klasse
TImageResourceDB verwaltet Bilder und verwendet als Datengrab eine SQLite Datenbank. Das Interface ist sehr einfach gehalten und umfasst nur wenige Funktionen:
Delphi-Quellcode:
TImageResourceDB = class(TObject)
private
FRestServer: TRestServerDB;
function CreateModel: TOrmModel;
protected
function LoadData(pmStream: TStream; const pmcRowID: TID): Boolean;
function SaveData(const pmcImageData: RawBlob; const pmcTitle, pmcComment: String; const pmcMetaData: Variant; var pmvRowID: TID): Boolean;
public
constructor Create(const pmcFileName: TFileName; const pmcPassword: RawUtf8 = '');
destructor Destroy; override;
class function InitDefaultMetaData(const pmcCreator, pmcLocation: RawUtf8; pmLatitude, pmLongitude: Double; pmDate: TDate; pmTime: TTime): Variant;
function LoadImage(pmImage: TImage; const pmcRowID: TID): Boolean; overload;
function LoadImage(pmImage: TImage; const pmcSearchPhrase: String; pmResultIDs: PIDDynArray = Nil): Boolean; overload;
function LoadImage(pmImage: TImage; const pmcMetaFieldName: String; const pmcMetaFieldValue: Variant; pmResultIDs: PIDDynArray = Nil): Boolean; overload;
function SaveImage(pmImage: TImage; const pmcTitle, pmcComment: String; const pmcMetaData: Variant; pmRowID: PID = Nil): Boolean; overload;
function SaveImage(pmStream: TStream; const pmcTitle, pmcComment: String; const pmcMetaData: Variant; pmRowID: PID = Nil): Boolean; overload;
end;
Nichts Besonderes, mag man meinen. Mit den wenigen Zeile bekommt man:
- Eine eingebettet (embedded) SQLite Datenbank, die optional AES verschlüsselt werden kann.
- Mit dem integrierten ORM Aufgaben wie das Speichern und Lesen von Datensätzen erledigen.
- Eine Volltextsuche über die Felder Title und Comment.
- Ein Feld für Meta-Daten, das für jeden Datensatz verschiedene Felder enthalten und über SQL abgefragt werden kann.
- Beschleunigung beim Speichern und Lesen der Grafik-Formate JPEG, PNG, GIF, TIFF.
Beschleunigung beim Speichern und Lesen von Bildern
Um den letzten Punkt gleich abzuhandeln, hierzu ein paar Benchmark-Wert aus der Anwendung ermittelt mit einem 2MB großen PNG-Bild:
Funktion / Unit Name | Vcl.Imaging.pngimage | mormot.ui.gdiplus |
SaveImage() | 900 ms | 25 ms |
LoadImage() | 70 ms | 10 ms |
Die Zahlen sprechen für sich. Bei einem Problem mit der Geschwindigkeit Folgendes probieren:
Unit Vcl.Imaging.pngimage entfernen, danach
Unit mormot.ui.gdiplus einbinden und die Funktion
RegisterSynPictures aufrufen.
Embedded SQLite Datenbank
Um eine SQLite Datenbank statisch ins Programm einzubinden, muss
nur die
Unit mormot.db.raw.sqlite3.static hinzugefügt werden. Danach kann der Zugriff auf die SQLite3 Engine low-level, oder besser über eine TRest* Klasse erfolgen. Über Connection Klassen lassen sich auch andere Datenbanken anbinden. Es gibt Connections für die Frameworks ZEOS, FireDac/AnyDac, UniDac,
ODBC, OleDB
API und direkte Anbindungen für SQLite, PostgreSQL, Oracle OCI und MongoDB. Die Anbindung erfolgt über die schnellst mögliche Variante. Zum Beispiel verwendet ZEOS direkt ZDBC, anstatt über die Delphi
DB Klassen zu gehen. Daraus ergibt sich eine deutliche Beschleunigung.
ORM initialisieren
Alle Klassen für das ORM müssen Nachfahre(n) der Klasse
TOrm sein. Der Tabellenname in der Datenbank ergibt sich aus dem Klassennamen. Alle öffentlichen (
published) Eigenschaften einer ORM Klasse werden als Feld in der Datenbank repräsentiert. Zusätzlich wird das Feld ID/RowID angelegt. Welche Feld-Typen möglich sind, ist in der
Hilfe beschrieben. Die ORM Klassen werden in einem Model zusammengefasst. Das Model wird einer TRest* Klasse für den Zugriff übergeben. Eine Übersicht der vorhandenen TRest* Klassen und ihr Verwendungszweck ist in der
Hilfe aufgelistet. Der eigentliche Quelltext ist nicht viel länger als die Beschreibung:
Delphi-Quellcode:
type
TOrmFile = class(TOrm)
...
published
property Title: RawUTF8
read FTitle write FTitle;
property Comment: RawUtf8
read FComment write FComment;
property MetaData: Variant
read FMetaData write FMetaData;
...
end;
...
FRestServer := TRestServerDB.Create(TOrmModel.Create([TOrmFile, ...]), DBFileName, False, DBPassword);
FRestServer.Model.Owner := FRestServer;
FRestServer.DB.Synchronous := smFull;
FRestServer.DB.LockingMode := lmExclusive;
FRestServer.Server.CreateMissingTables(0, [itoNoAutoCreateGroups, itoNoAutoCreateUsers]);
Anmerkung:
smFull ist mit Abstand der langsamste Modus, aber gewährleistet ein 100%iges ACID-Verhalten. In der Praxis ist
smNormal ein guter Kompromiss aus Sicherheit und Geschwindigkeit. Im Beispiel besteht kein Grund für eine Authentifizierung, daher wird die automatische Erstellung der hierfür notwendigen Tabellen unterdrückt.
Aufgaben über das ORM erledigen
Eine Übersicht aller ORM Funktionen erhält man beim Blick in das
IRestOrm Interface. Es steht eine Vielzahl von Möglichkeiten zur Auswahl. Die Anwendung ist sehr einfach. Das Hinzufügen eines Datensatzes geschieht wie folgt:
Delphi-Quellcode:
var
ormFile: TOrmFile;
begin
ormFile := TOrmFile.Create;
try
ormFile.Title := 'my first one';
ormFile.Comment := 'Arnaud is the best';
...
FRestServer.Server.Add(ormFile, True);
finally
ormFile.Free;
end;
SQLite Volltextsuche
Um die Volltextsuche zu aktivieren, erstellt man eine eigene ORM Klasse, die von einer in mORMot vorhanden, spezialisierten Basisklassen (TOrmFts5/TOrmFts5Porter/TOrmFts5Unicode61) abstammt. In dieser Klasse werden die Felder der Datenklasse, die zum Suchen vorgesehen sind, wiederholt. Die Suche ist eine einfache
SQL Abfrage:
Delphi-Quellcode:
var
sqlWhere: RawUtf8;
searchIDs: TIDDynArray;
begin
sqlWhere := FormatUtf8('% MATCH ? ORDER BY rank DESC', ['SearchTable'], [SearchPhrase]);
if FRestServer.Server.FTSMatch(TOrmFileSearch, sqlWhere, searchIDs) then
Anmerkung: Die Funktion FormatUtf8() ist der Delphi Format() Funktion ähnlich. Eine Zusatzfunktion ist, dass Argumente mit
:(): umschlossen werden. Diese Markierung wird im ORM zur Optimierung verwendet.
Feld mit Meta-Daten
Ein Eigenschaftsfeld der Klasse vom Typ DocVariant wird im ORM als JSON gespeichert. Ein DocVariant ist eine beliebig komplexe Datenstruktur aus Objekt(en) und/oder Arrays, oder aus Kombinationen von beiden. WOW. Die DocVariant Syntax sieht für Pascal Entwickler etwas gewöhnungsbedürftig aus, weil es eher an eine Scriptsprache erinnert. Mehr dazu in der
Hilfe nachlesen. Ein DocVariant lässt sich auf mehrere Arten erstellen. Eine Möglichkeit ist folgende:
Delphi-Quellcode:
var
metaData: Variant;
begin
TDocVariant.New(metaData);
metaData.Number := 10;
metaData.Creator := 'Thomas';
metaData.Birthday := EncodeDate('Top Secret!');
In SQLite ab Version 3.38.0 lässt sich das mit folgender
SQL Syntax abfragen:
Code:
Schema: SELECT * FROM File WHERE MetaData->>'$.Creator'='Thomas' ORDER BY ...
Zusammenfassung
mORMot ist gut dokumentiert. Die Hilfe umfasst mehr als 2500 Seiten. Davon enthalten die ersten ca. 650 Seiten einen sehr lesenswerten allgemeinen Teil, der Rest ist
API Dokumentation. mORMot muss nicht in der
IDE installierten werden! Es reicht aus, die entsprechenden Bibliothekspfade einzufügen. Bei neuen Anwendungen ist mORMot2 zu empfehlen. Hier der Link zum
GitHub Repro. Es stehen viele Beispiele und ein freundliches
Forum zur Verfügung. Wenn mehr Interesse an mORMot besteht, kann ich auch andere Teile in ähnlicher Weise kurz vorstellen.
Bis bald...
Thomas