Einzelnen Beitrag anzeigen

mytbo

Registriert seit: 8. Jan 2007
461 Beiträge
 
#1

mORMot: ORM und DocVariant kurz vorgestellt

  Alt 20. Jun 2022, 23:32
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 NameVcl.Imaging.pngimagemormot.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
Angehängte Dateien
Dateityp: zip TestSQLiteSource.zip (1,02 MB, 60x aufgerufen)
Dateityp: zip TestSQLiteExe.zip (2,22 MB, 41x aufgerufen)
  Mit Zitat antworten Zitat