Überblick

Bevor das vorliegende Werk den Einsatz von Entity Framework anhand von Beispielen veranschaulicht, beschreibt dieses Kapitel, wo die Vorteile dieses umfangreichen Datenzugriffsframeworks liegen. Darüber hinaus geht dieses Kapitel auf grundlegende Konzepte von Entity Framework sowie auf die beiden zur Verfügung stehenden Spielarten ein. Eine Diskussion über den Einsatz von Entity Framework mit verschiedenen RDBMS rundet das Kapitel ab.

Motivation

Um die Vorzüge von O/R-Mappern im Allgemeinen und Entity Framework im Speziellen aufzuzeigen, soll zunächst ein Blick auf das Listing geworfen werden. Es greift auf „klassische Weise“ mit ADO.NET, dem Datenzugriffsframework, welches seit den ersten Tagen Bestandteil von .NET ist, auf eine Datenbank zu. Dabei handelt es sich um eine Codestrecke, die die meisten .NET-Entwickler inklusive der Autoren wohl schon zig mal auf die eine oder andere Weise geschrieben haben: Die DbConnection repräsentiert die Verbinung zur Datenbank und das DbCommand eine SQL-Anweisung die zur Datenbank gesendet wird. Über den DbDataReader greift das Beispiel auf die abgefragten Daten zu.

 1 using (DbConnection conn = new SqlConnection(
 2               "Data Source=(LocalDb)\v11.0;Initial Catalog=HotelDb;[…]"))
 3 {   
 4     conn.Open();   
 5     using (DbCommand cmd = conn.CreateCommand())
 6     {     
 7         cmd.CommandText = "select * from Hotel where RegionId = 3";      
 8         using (DbDataReader r = cmd.ExecuteReader())    
 9         {
10             while (r.Read())
11             {
12                 Console.WriteLine(r["Bezeichnung"]);
13             }
14         }
15     }
16 }

Obwohl die betrachtete Implementierug ihre Aufgabe erfüllt, bringt diese direkte Art des Datenzugriffs auch zahlreiche Nachteile mit sich:

  • Die SQL-Anweisungen werden in Form von Strings in den Quellcode eingebettet. Bei der Entwicklung und Wartung dieser SQL-Anweisungen kann Visual Studio nicht mit Intellisense unterstützen und Fehler können nicht vom Compiler sondern erst zur Laufzeit entdeckt werden.
  • Auch die abzurufenden Spaltenbezeichnungen werden in Form von Strings angeführt. Auch hierbei gibt es weder Unterstützung durch die Entwicklungsumgebung oder durch den Compiler.
  • Der Entwickler hat sich mit C# für eine objektorientiete Sprache entschieden. Dieses Paradigma sieht vor, dass die Anwendung den betroffenen Problembereich in Form von untereinander interagierenden Objekten, die jenen aus der „richtigen Welt“ entsprechen, widerspiegelt. Das soll die Komplexität senken und den Quellcode selbstbeschreibend gestalten. Im betroffenen Beispiel würde das bedeuten, dass der Entwickler auf Hotel-Objekte zugreifen kann. Stattdessen erhält er jedoch nur einen DbDataReader zum Zugriff auf eine Ergebnismenge, welche auf relationale Weise Hotels widerspiegelt. Der Entwickler könnte nun zwar mit den Daten aus dem DbDataReader Hotel-Objekte erzeugen, aber das geht mit einer Menge monotonem und fehleranfälligem Quellcode einher.

Diese Nachteile werden durch einen O/R-Mapper, wie Entity Framework, kompensiert.

Entity Framework und das Entity Data Model

Entity Framework bildet Elemente aus der relationalen Welt auf Elemente der objektorientierten Welt ab: Tabellen werden auf Klassen abgebildet, Spalten auf Eigenschaften, Datensätze auf Objekte und Fremdschlüsselbeziehungen auf Objektreferenzen. Der Entwickler muss sich somit nicht mehr selbst mit dem Brückenschlag zwischen der objektorientieren und relationalen Welt belasten. Stattdessen übergibt er Entity Framework die zu persistierenden Objekte oder fordert bei Entity Framework gespeicherte Objekte an. Entity Framework kümmert sich dabei um das Anlegen von Datensätzen für Objekte sowie um das Laden von Datensätzen und deren Transformation in Objekte.

Als O/R-Mapper erleichtert Entity Framework nicht nur den Zugriff auf relationale Datenbanken, sondern bietet darüber hinaus auch Datenbankunabhängigkeit. Dies wird unter anderem durch eine eigene Abfragesprache, die sich Entity SQL nennt und stark an SQL angelehnt ist, erreicht. Allerdings verwenden die meisten Entwickler Entity SQL nicht direkt, sondern nehmen mit der in C# und VB.NET integrierten Abfragesprache LINQ vorlieb. Die Ausprägung von LINQ, die bei der Arbeit mit dem Entity Framework zum Einsatz kommt, nennt sich LINQ to Entities und wird ohne Zutun des Entwicklers nach Entity SQL kompiliert. Dieses wird wiederum unter Verwendung des jeweiligen Datenbanktreibers in das native SQL der jeweils verwendeten Datenbank umgewandelt.

Um Elemente aus der objektorientierten Welt auf Konzepte der relationalen Welt abzubilden, bedient sich Entity Framework eines sogenannten Entity Data Models (EDM). Dabei handelt es sich um ein Modell, welches im Hauptspeicher und/oder in Form einer XML-Datei vorliegt und aus drei Submodellen besteht. Diese Submodelle nennen sich:

  • Conceptual Model (Konzeptmodell)
  • Store Model (Speichermodell)
  • Mapping Model

Das Konzeptmodell beschreibt das zu verwendende Objektmodell, bestehend aus Klassen, sogenannten Entitäten oder Entitätsklasse, die miteinander in Beziehung stehen (können). Das Speichermodell beschreibt hingegen das einzusetzende Datenbankschema. Für den Brückenschlag zwischen der objektorientierten und der relationalen Welt ist die Map verantwortlich: Sie bildet Elemente aus dem Conceptional Model auf Elemente des Speichermodell ab. Beispielsweise beschreibt sie, dass eine Klasse Person auf eine Tabelle Personen abgebildet wird, dass ein Attribut Id einer Spalte PersonId entspricht oder dass ein bestimmter Fremdschlüssel zum Auflösen einer Objektbeziehung heranzuziehen ist. Neben den einzelnen Entitätsklassen hat der Entwicklker auch Zugriff auf einen sogenannten Kontext. Dabei handelt es sich um eine Klasse, welche von DbContext erbt, und den Dreh- und Angelpunkt bei der Arbeit mit der Datenbank darstellt. Beim Datenbankkontext kann der Entwickler das Laden von Objekten anstoßen und an den Datenbankkontext übergibt er auch die zu speichernden Objekte. Ob der Entwickler selbst die Entitätsklassen und den Kontext implementieren muss oder diese Artefakte generieren lassen kann, hängt von der gewählten Spielart ab. Der nächste Absatz geht darauf genauer ein.

Spielarten

Von Entity Framework existieren seit Version 4.1 zwei Geschmacksrichtungen, die hier als Spielarten bezeichnet werden. Da die Wahl der Spielart eine wichtige grundlegende Entscheidung darstellt, geht dieser Abschnitt darauf ein, bevor in den nachfolgenden Kapiteln die Arbeit mit Entity Framework anhand von Beispielen veranschaulicht wird.

Modell-basierte Vorgehensweise

Bei der ursprünglichen Spielart von Entity Framework liegt das Entity Data Model explizit in Form einer XML-Datei vor. In Ermangelung eines offiziellen Begriffs für diese Variante verwenden die Autoren die Bezeichnung Modell-basierte Vorgehensweise hierfür. Bei dieser Spielart generiert Visual Studio aus dem Entity Data Model Quellcode. Pro Entität erhält der Entwickler auf diesem Weg eine generierte Klasse. Diese Klassen sind mit partial gekennzeichnet und können somit über weitere partielle Klassen, die in anderen Dateien definiert werden, erweitert werden. Somit kann der Entwickler sicherstellen, dass seine Erweiterungen bei einer erneuten Generierung der Entitätsklassen nicht verloren gehen. Für das Modell generiert Visual Studio darüber hinaus einen Kontext. Besteht die zu verwendete Datenbank bereits, kann der Entwickler mit den von Visual Studio gebotenen Werkzeugen daraus ein XML-basiertes Entity Data Model generieren lassen (siehe Abbildung). Hierbei spricht man von Database First. Der Entwickler kann hierbei das generierte Entity Data Model mit den graphischen Werkzeugen in Visual Studio einsehen und auch bearbeiten. Letzteres ist nötig, wenn er die bestehende Datenbank nicht 1:1 auf ein Objektmodell abbilden möchte. Ein Beispiel dafür ist das Abbilden von Tabellenstrukturen auf Vererbungsbeziehungen oder das Abbilden einer Tabelle auf mehrere Klassen. Wer nicht gerne mit graphischen Designern sondern lieber mit Quellcode arbeitet, kann auch die im nächsten Abschnitt beschriebene Spielart Code First gemeinsam mit einer bestehenden Datenbank einsetzen. Alternativ zur Vorgehensweise Database First kann der Entwickler auch mit dem von Visual Studio gebotenen graphischen Designer ein Entity Data Model „zeichnen“ und daraus ein Datenbankschema generieren lassen (siehe übernächste Abbildung). Somit muss sich der Entwickler – zumindest theoretisch – gar nicht mit der relationalen Welt belasten. Diese Variante, welche man als Model First bezeichnet, ist jedoch nicht sonderlich komfortabel. Dies liegt zum einen am recht schwerfälligen Designer sowie zum anderen daran, dass Visual Studio keine Möglichkeit bietet, eine bereits bestehende Datenbank nach der Änderung des Models zu aktualisieren. Das ist auch der Grund, warum die Autoren den Einsatz von Model First nicht empfehlen. Als leichtgewichtige Alternative hierzu bietet sich die jüngere Spielart Code First, auf die der nächste Abschnitt eingeht, an.

Modell-basierte Vorgehensweise mit Database First
Modell-basierte Vorgehensweise mit Database First
Modell-basierte Vorgehensweise mit Model First
Modell-basierte Vorgehensweise mit Model First

Code First

Die jüngere Spielart Code First, welche mit Version 4.1 eingeführt wurde, kommt ohne explizites Entity Data Model aus. Sie sieht vor, dass der Entwickler die gewünschten Entitäten und den Kontext selbst erstellt oder aus einer Datenbank generiert. Zur Laufzeit leitet Entity Framework aus den Entitäten ein Entity Data Model ab. Dazu nutzt Entity Framework sogenannte Konventionen. Eine dieser Konventionen sieht vor, dass der Primärschlüssel einer Entität daran erkannt werden kann, dass er den Namen Id oder den Namen der Entität gefolgt von der Endung Id - z. B. HotelId für die Entität Hotel - aufweist. Möchte der Entwickler mit diesen Konventionen nicht vorlieb nehmen, kann er durch das Bereitstellen von Attributen oder Konfigurationscode daraus ausbrechen. Auch die Definition eigener Konventionen ist mittlerweile möglich.

Existiert noch keine Datenbank, kann der Entwickler aus dem abgeleiteten Entity Data Model eine generieren lassen. Dies kann entweder zur Laufzeit passieren oder im Zuge des Entwickelns (siehe nachfolgende Abbildung). Letzteres erlaubt auch das Generieren von SQL-Skripten, die schlussendlich von einem Administrator im Produktivsystem eingespielt werden können. Im Gegensatz zu Model First erlaubt Code First auch das Aktualisieren von bestehenden Datenbanken anhand von Änderungen, die der Entwickler an den Entitäten oder am Konfigurationscode vorgenommen hat.

Code First
Code First

In Fällen, in denen bereits eine Datenbank existiert, kann der Entwickler Entitäten und Konfigurationscode generieren lassen (siehe Abbildung).

Code First mit bestehender Datenbank
Code First mit bestehender Datenbank

Im Gegensatz zur modellbasierten Vorgehensweise bietet Code First mehr Flexibilität: Der Ent-wickler kann die Entitäten nach eigenem Gutdünken aufbauen sowie auf verschiedene Projekte verteilen. Darüber hinaus ist Code First gerade für Entwickler, die das Arbeiten mit Quellcode angenehmer als das Arbeiten mit einem graphischen Designer empfinden, von Vorteil. Dabei ist zu berücksichtigen, dass der graphische Designer gerade bei Datenbanken mit vielen Tabellen schnell unübersichtlich wird und sich generell ein wenig unhandsam anfühlt. Auch das Mergen von Änderungen, die parallel durch verschiedene Entwickler vorgenommen wurden, gestaltet sich bei Code First einfacher. Der wohl größte Vorteil von Code First liegt darin, dass das verwendete RDBMS besonders einfach gewechselt werden kann: Während dies bei der modellbasierten Vorgehensweise das Überarbeiten des gesamten Store-Models notwendig macht, ist hierfür bei Code First lediglich der Datenbanktreiber auszutauschen. Ein weiterer Aspekt, der für Code First steht, ist, dass Code First ab der komplett überarbeiteten Version 7 von Entity Framework die einzige unterstützte Spielart sein wird. Aus den genannten Gründen ist der Einsatz von Code First in den meisten Fällen zu bevorzugen. Da jedoch mittlerweile eine Menge Code, der auf der ursprünglichen modellbasierten Vorgehensweise basiert, vorliegt und der Einsatz dieser Vorgehensweise in bestimmten Fällen nach wie vor gerechtfertigt scheint, geht das vorliegende Werk auf beide Spielarten ein.

Datenbankunterstützung

Im Lieferumfang von .NET befindet sich lediglich Unterstützung für Microsoft SQL Server in seinen verschiedenen Ausprägungen. Allerdings stellen mittlerweile Datenbankanbieter, wie ORACLE, auch Treiber (Datenanbieter) zur Verfügung, die mit dem Entity Framework umgehen können. Daneben gibt es auch Unternehmen, wie DevArt oder DataDirect, die sich auf die Entwicklung von Datenbankwerkzeugen spezialisiert haben und Entity Framework-fähige Treiber für verschiedenste Datenbanken anbieten. Gerade im Umfeld von ORACLE haben die Autoren mit den kostenpflichtigen Datenanbietern von DevArt sehr gute Erfahrungen gemacht, wohingegen jener von ORACLE selbst immer wieder zu Problemen geführt hat. Mittlerweile sollte man für jedes gängige RDBMS einen Entity-Framework-kompatiblen Treiber finden. Die Autoren haben in der Vergangenheit Entity Framework erfolgreich gemeinsam mit SQL Server, ORACLE, DB/2, Informix, MySQL und Sybase Anywhere eingesetzt. Entscheidet sich der Entwickler für die modellbasierte Vorgehensweise, kann es empfehlenswert sein, Visual Studio mit Tools von Drittanbietern, wie DevArt, zu erweitern. Gerade bei der Arbeit mit ORACLE-Datenbanken hat sich das Produkt DevArt Entity Developer (siehe den dazugehörigen Abschnitt “Devart Entity Developer” im hinteren Teil des Buches ) bewährt, da es Werkzeuge bietet, die besser mit ORACLE-Datenbanken umgehen können als jene, die mit Visual Studio ausgeliefert werden und in erster Linie für SQL Server optimiert sind. Beim Einsatz von Code First muss der Entwickler hingegen weniger auf die Werkzeugunterstützung achten, da diese aufgrund seines Code-zentrierten Charakters naturgemäß keinen besonderen Stellenwert einnimmt. Code First ist auch zu bevorzugen, wenn ein Wechsel des verwendeten RDBMS zu unterstützen ist. Hierbei ist an und für sich lediglich der Datenbanktreiber auszutauschen. Manche Treiber müssen darüber hinaus mit ein paar Zeilen Code konfiguriert werden. Beim Einsatz der modellbasierten Vorgehensweise müsste der Entwickler hingegen das gesamte Store Model für die neue Datenbank austauschen, was mit erheblichem Aufwand verbunden wäre.

Wofür Entity Framework nicht geeignet ist

Entity Framework kann mit riesigen Datenbanken ebenso wunderbar umgehen wie mit komplexen Abfragen. Wie bei allen O/R-Mappern sollte man jedoch den Einsatz von Entity Framework bei zeitkritischen Szenarien, bei denen große Datenmengen in den Hauptspeicher geladen werden müssen, kritisch hinterfragen. Beispiele hierfür sind Massenaktualisierungen oder Massenimporte. Entity Framework wird damit zwar fertig und der Quellcode wird (hoffentlich) aufgrund der Verwendung von Objekten besser lesbar sein, allerdings gibt es hierfür Lösungsansätze, die eine bessere Performance bieten. Der Grund dafür ist, dass beim Laden und Speichern riesiger Datenmengen der Overhead für die Objekt-Serialisierung häufig zu hoch ist. Möchte der Entwickler beispielsweise eine große Datenmenge mit optimaler Performance importieren, sollte er zu Werkzeugen wie Bulk Insert unter SQL Server oder Database Loader unter ORACLE greifen. Möchte der Entwickler mit optimaler Performance sämtliche Datensätze einer Tabelle nach einer bestimmten Regel aktualisieren, wird er auch mit einem direkten UPDATE-Befehl besser beraten sein. Ist der durch die Objekt-Serialisierung hervorgerufene Overhead zu verkraften und steht die Lesbarkeit und Wartbarkeit des Quellcodes im Vordergrund, spricht hingegen nichts gegen den Einsatz von Entity Framework. Daneben hat Entity Framework mit einem Datanbank-Schema, das sich zur Laufzeit ändert, Probleme, zumal der Entwickler das Datenbankschema im Zuge der Entwicklung auf Entitätsklassen abbildet. Auf solche Bereiche der Datenbank muss der Entwickler nach wie vor auf klassische Art und Weise, zum Beispiel unter Verwendung von ADO.NET, zugreifen.