Software
20.08.2024 13:34

Mit anderen teilen:

Aktie

Objektrelationales Mapping mit Entity Framework

Heute ist das World Wide Web der beliebteste Dienst des größten Computernetzwerks, des Internets. Täglich kommen über 100.000 neue Webseiten hinzu, von statischen mit minimaler Interaktionsunterstützung bis hin zu dynamischen, die für die Ausführung ihrer Funktionen eine große Menge aktueller Daten benötigen.
Das Foto dient der Veranschaulichung. (Foto: Pixabay)
Das Foto dient der Veranschaulichung. (Foto: Pixabay)

Autor: Denis Balant, Enej Hudobreznik


Trotz der rasanten technologischen Entwicklung ist SQL immer noch die Hauptsprache für die Verwaltung relationaler Datenbanken. strukturierte Abfragesprache, die mit vielen angepassten Derivaten (PostgreSQL, MySQL...) ihre Wurzeln in den 70er Jahren des letzten Jahrhunderts hat.

Gerade bei komplexeren Datenbankstrukturen erweist sich das Schreiben von SQL-Abfragen oft als recht zeitaufwändige Arbeit. Anstatt manuell Abfragen und Konvertierungen für Objekte zu schreiben, wie sie für objektorientierte Programmiersprachen typisch sind, ist es viel natürlicher und oft einfacher, Klassen vom Quellcode zum Datenschema zuzuordnen und nicht umgekehrt. Dadurch wird Entwicklungszeit gespart und gleichzeitig ist es viel einfacher, die Beziehungen zwischen Entitäten zu erkennen. Diese Methode wird als objektrelationales Mapping (ORM) bezeichnet.

Für die .NET-Umgebung bietet Microsoft zu diesem Zweck das Open-Source-Framework Entity Framework Core (kurz EF Core) an, das eine datenorientierte Entwicklung ermöglicht (Code First-Ansatz), bei dem miteinander verbundene Daten einfach in Klassen gesammelt werden, die Tabellen darstellen, und das Framework baut auf dieser Grundlage auf, versteht Zusammenhänge und erstellt ein Datenbankschema in seinem eigenen Format. Der größte Vorteil eines solchen Ansatzes ist die Möglichkeit, das Datenschema ohne SQL-Kenntnisse auf der gewünschten Datenbank zu installieren.

EF Core kommuniziert mit Datenbanken über Plug-in-Bibliotheken, sogenannte Datenbankanbieter, sodass der Wechsel zu einer anderen Datenbank einfach ist. Diese können vom Benutzer über den Paketmanager (NuGet für C#) installiert werden. Offizielle Bibliotheken sind nur für Microsofts SQL Server- und Azure Cosmos DB-Lösungen sowie das SQLite-Projekt verfügbar und dank der starken Open-Source-Community unterstützt das Framework praktisch alle wichtigen relationalen Datenbanken (MySQL, PostgreSQL...).

Schauen wir uns als Beispiel für die Datenmodellierung die Tabelle „Student“ an, die über die Attribute Vorname (String mit bis zu zwanzig Zeichen), Nachname (String mit bis zu zwanzig Zeichen) und eindeutige Kennung (Ganzzahl) verfügt.

Tabelle Student (eigene Quelle)

In PostgreSQL definieren wir diese Tabelle wie folgt:

Student-Tabellendefinition in PostgreSQL (eigene Quelle)

Zusätzlich zu den Definitionen der Grundtypen für Vor- und Nachname verlangen wir auch, dass diese niemals leer sind (NOT NULL). Der Typ SERIAL stellt eine eindeutige Ganzzahl dar, die in unserem Fall als Primärschlüssel der Id dient. Für jede hinzugefügte Entität wird sie automatisch anhand der ID der zuvor hinzugefügten Identität ermittelt, die einfach inkrementiert wird.

Es ist jedoch viel einfacher, die Tabelle als Klasse im Quellcode der Anwendungslogik zu definieren, indem ein ORM verwendet wird, der das Objekt und die entsprechenden Datenbankanforderungen selbst erstellt. Das Key-Tag über dem Klassenattribut gibt den Primärschlüssel an und Required gibt an, dass das Attribut nicht leer sein darf.

Nach der Entwicklung können wir mit dem EF Core-Framework automatisch ein Datenschema aus dem Code generieren und es über die im Framework enthaltenen Befehlszeilentools in der ausgewählten Datenbank bereitstellen. Durch diese Migrationen werden schrittweise Änderungen am Datenbankschema verwaltet, die sicherstellen, dass die Datenbank mit dem Datenmodell der Anwendung synchronisiert bleibt. Durch den Vergleich des aktuellen Datenmodells mit dem aktuellen Datenbankschema (dem Stand der letzten Migration) werden neue Migrationen erstellt, die in Form spezieller Klassen in EF Core gespeichert werden.

Definition der Schülertabelle mit EF Core in der .NET-Umgebung (eigene Quelle)

Die Datenbankverbindung wird von einer Klasse abstrahiert, die von der DbContext-Klasse erbt. Seine Attribute sind Sammlungen von Entitäten vom Typ DbSet, die Tabellen zugeordnet werden.

Datenbankverbindungsklasse und Studententabelle definiert (eigene Quelle)

Das oben genannte Framework zum Schreiben von Abfragen verwendet die LINQ-Syntax, die eine einheitliche Möglichkeit zum Abrufen und Verarbeiten von Daten aus verschiedenen Quellen darstellt. Die Abfrage wird dann in SQL übersetzt und das Ergebnis selbst wird zurück in ein Objekt, sein Attribut oder eine Objekttabelle übersetzt.

Das folgende Beispiel zeigt eine Abfrage für einen Studenten mit bekannter Immatrikulationsnummer. Es ist lediglich ein Aufruf der Find-Methode mit dem Primärschlüsselwert (ID) erforderlich.

Beispiel für eine Schülerabfrage mit EF Core (eigene Quelle)

Wenn wir das gleiche Ergebnis ohne den Einsatz eines ORM-Tools erzielen wollen, ist deutlich mehr Code erforderlich. Es wird ein Beispiel mit der NpgSql-Bibliothek gezeigt. Zuerst müssen wir Objekte erstellen, die die SQL-Abfrage darstellen und die Datenbank lesen, wobei wir darauf achten müssen, die Parameter korrekt einzubinden, um mögliche Schwachstellen zu vermeiden, wie z.B. SQL-Injection. Dieses Mal müssen wir das Studentenobjekt selbst erstellen, müssen jedoch auf die Reihenfolge der Attribute in der Abfrage achten und auf die Möglichkeit eines Fehlers in der Abfrage achten.

Beispiel für eine Studentenabfrage mit der NpgSql-Bibliothek (eigene Quelle)
Antwort auf obige Frage (eigene Quelle)

Das objektrelationale Mapping bietet uns somit eine Abstraktionsebene, die die Softwareentwicklung aufgrund der Abbildung aus einem objektorientierten Entwicklungsplan erheblich beschleunigt, da SQL-Code nicht getrennt vom Quellcode (häufig objektorientiert) geschrieben werden muss. Ein gut geschriebenes ORM unterstützt gute Entwicklungsmuster und -praktiken für das Anwendungsdesign und ermöglicht es Nicht-SQL-Entwicklern gleichzeitig, eine relationale Datenbank einfacher in ihre Anwendung zu integrieren.

Es ist jedoch wichtig zu betonen, dass ORM keine perfekte Lösung ist. Die Schwäche liegt gerade in der Abstraktion, die es uns bietet. Es generiert deutlich mehr SQL-Code, als ein Entwickler schreiben würde, was sich stark auf die Anwendungsgeschwindigkeit auswirken kann, fehlerhafte Datenbankzugriffspraktiken verbergen kann und ein Problem der Abwärtskompatibilität darstellt. Obwohl der generierte Code in den meisten Fällen korrekt ist, empfiehlt es sich dennoch, ihn manuell zu überprüfen und zu testen.

Die objektrelationale Zuordnung ist eine Funktionalität, die nicht in der .NET-Umgebung enthalten ist. Funktionalität wird von den meisten Frameworks und Bibliotheken für verschiedene Sprachen bereitgestellt. Beispiele sind Django für Python, Gorm für Go, Spring für Java, Prisma für JavaScript (oder Node.js) ...




Was lesen andere?