Schneller, stabiler, besser

Neuerungen im Entity Framework 5.0
Kommentare

Auf uns rollt eine große Welle an Neuerungen zu. Nicht nur, dass das neue .NET Framework 4.5 an unsere Tür klopft, es gibt auch gleich noch ein neues Betriebssystem und eine neue Entwicklungsumgebung oben drauf. In diesem Artikel greifen wir uns das Entity Framework heraus, das in der Version 5 mit dem neuen .NET Framework 4.5 ausgerollt wurde, und betrachten einmal die Neuerungen dieses O/R-Mappers.

Nahezu nichts wird im .NET-Umfeld so rasant weiterentwickelt wie das Entity Framework. Der Hype um den O/R-Mapper scheint vorerst kein Ende zu finden, und so rasen die Versionsnummern weiter in schwindelerregendem Tempo in die Höhe. Mit dem im August dieses Jahres veröffentlichten .NET Framework 4.5 wurde auch das Entity Framework 5 ausgerollt. Das neue Release dieser Bibliothek bringt einige Neuerungen mit sich, die in diesem Artikel erläutert und anhand von Beispielen verdeutlicht werden sollen. Der Vollständigkeit halber werden auch Themen behandelt, die bereits ab der Version 4.1 zur Verfügung stehen.

Bevor es ans Eingemachte geht, hier noch ein Blick in die Zukunft: Microsoft hat angekündigt, das Entity Framework ab der Version 6 als Open-Source-Projekt unter der Apache-Lizenz v2 zu veröffentlichen, und folgt damit dem Vorbild des ASP.NET MVC Frameworks, das ebenfalls auf CodePlex als offener Quellcode veröffentlicht wurde. Microsoft verspricht, das Framework auch künftig weiter zu entwickeln und bietet weiterhin vollständige Unterstützung für den O/R Mapper.

DbContext vs. ObjectContext

Neu hinzugekommen seit der Version 4.1 des Entity Frameworks ist der DbContext. Im Prinzip kann man diesen als die direkte Konkurrenz des ObjectContext und als dessen Ablösung betrachten. Der neue Kontext kommt schlanker daher als sein großer Bruder und wird mit dem T4 Code-Template „ADO.NET DbContext Generator“ erzeugt, das kostenlos über Visual Studio heruntergeladen werden kann. Ausdrücklich empfohlen wird an dieser Stelle, nur neue Datenmodelle auf dem DbContext basieren zu lassen. Bestehende Modelle sind auch nach wie vor noch mit dem ObjectContext lauffähig und keinesfalls als „veraltet“ anzusehen. Der DbContext arbeitet mit simplen POCO-Klassen, die nicht so eng an den Kontext gebunden sind, wie es beim großen Bruder der Fall ist. Einzig auf Compiled Queries muss man noch verzichten, was unter Umständen ein kleiner Performancenachteil sein kann. Weitere Informationen hierzu finden sich im Abschnitt „Mapping“.

Ein Vorteil in Sachen Performance ist jedoch die Möglichkeit des bedingten Nachladens. Es können nun nicht länger nur alle verknüpften Entitäten eines Objekts nachgeladen werden, sondern es besteht die Möglichkeit, explizit zu definieren, welche Objekte man gezielt nachladen möchte:

this.Context.Entry(user).Collection(f => f.Orders).Query().Where(x => x.Date == DateTime.Now).Load();

Erwähnenswert ist auch die neue Methode Find(), die das Suchen nach Objekten über den Primärschlüssel deutlich beschleunigt, da die Ergebnisse im Cache gehalten werden und somit für weitere Abfragen nach dem gleichen Schlüssel keine erneute Abfrage an die Datenbank mehr gestartet werden muss:

var user = context.Users.Find(1000);

Zudem ist der Kontext um eine Methode GetValidationErrors() erweitert worden. Diese bietet eine Vielzahl an Möglichkeiten, getrackte Objekte zu validieren. Tiefergreifende Informationen zu den Möglichkeiten dieser Methode und praktische Beispiele gibt es im MSDN-Blog von Julie Lerman [1]. Es soll jedoch nicht unerwähnt bleiben, dass die Methode auch mit DataAnnotations kompatibel ist, was das Validieren von Entitäten schlank und elegant macht. Näheres hierzu im Abschnitt „Validierung“.

Zum Schluss noch einige erläuternde Worte zum Database-Unterobjekt des DbContext und dessen Methoden. Mittels des Database-Objekts kann die Datenbank verwaltet und teilweise konfiguriert werden. Die Methoden Create() und CreateIfNotExists() bieten die Möglichkeit, eine Datenbank direkt aus dem Code heraus mit dem dazugehörigen Schema zu erzeugen. Mit den Methoden ExecuteSqlCommand() und SqlQuery () wird dem Entwickler die Möglichkeit geboten SQL-Anweisungen direkt an die Datenbank weiterzugeben.

Vereinfachtes NoTracking

Wir müssen uns eingestehen, dass das Änderungs-Tracking einer Entität nicht gerade eine Performancerakete ist. Das Tracking wird verwendet, um Änderungen an einer Entität zu tracken und an den Context zu melden, damit er die geänderten Eigenschaften persistieren kann. Es ist jedoch oft nicht nötig, Änderungen an einer Entität zu tracken, da diese nicht wieder zurück in die Datenbank geschrieben werden muss, sondern lediglich zur Anzeige verwendet wird. Dann ist es ratsam, das Änderungs-Tracking abzuschalten, um keine wertvollen Ressourcen zu verschwenden. Das Tracking kann auch nachträglich aktiviert werden. Das ist z. B. in einem DataGrid sinnvoll, wenn ein Datensatz zur Bearbeitung geöffnet wird. Abbildung 1 vergleicht eine normale Entity-Framework-Abfrage und eine Abfrage mit NoTracking sowie eine herkömmliche SQL-Abfrage hinsichtlich der gebrauchten Zeit (ms) und macht den Unterschied in Sachen Performance noch einmal deutlich. In den vorherigen Versionen des Entity Framework konnte man das Tracking nicht wirklich elegant abschalten. Listing 1 zeigt die bisherige Vorgehensweise und wie die Änderungsverfolgung mit der neuen Version des O/R Mappers deaktiviert werden kann.

Abb. 1: Performancevergleich Entity Framework

Listing 1
// Bisheriges Abschalten des Tracking DatabaseContext context = new DatabaseContext(); var users = context.Users.Where(x => x.Age < 18); (users as ObjectQuery).MergeOption = MergeOption.NoTracking;  // Neues Abschalten des Trackings var users = context.Users.AsNoTracking().Where(x => x.Age < 18);
Typsicheres Eager Loading

Die Einen brauchten es nicht, die Anderen vermissten es: typsicheres Eager Loading im Entity Framework. Bisher konnte man nur mit einfachen Strings ausdrücken, welche Entitäten man gern mit der eigentlichen Abfrage mitladen möchte. Es bedarf keiner großen Vorstellungskraft, um zu sehen, dass die Identifizierung einer Entität bzw. deren POCO-Klasse auf diesem Weg sehr fehleranfällig und unflexibel war und ist. Nun haben die Entwickler aus Redmond der Methode Include aber eine weitere Überladung spendiert, die es möglich macht, auch typsicher eine Entität zu bestimmen, die mitgeladen werden soll. Das erfolgt über einen regulären Ausdruck (Listing 2). Wichtig ist dabei, den Namespace System.Data.Entity einzubinden, da die Methode bzw. die neue Überladung sonst nicht zur Verfügung steht.

Listing 2
DatabaseContext context = new DatabaseContext();  // Bisheriges mitladen von Entitäten var users = context.Users.Include("Orders").ToList();  // Neue Möglichkeit des Mitladens var users = context.Users.Include(u => u.Orders).ToList();

Nicht ganz passend zum Thema, aber praktisch für die tägliche Arbeit: Wie kann man dynamisch Entitäten im Zusammenspiel mit einem Repository-Pattern nachladen? Listing 3 zeigt eine Methode, die aus einem Repository für Funktionalitäten rund um Benutzer stammt und als Parameter Lambda-Expressions entgegennimmt, die die nachzuladenden Entitäten qualifizieren. Damit diese Methode nun funktioniert, muss noch eine weitere Implementierung der Include-Methode geschrieben werden, die mehrere Lambda-Ausdrücke entgegen nimmt (Listing 4). Die Extension-Methode ist denkbar simpel und hängt einfach die einzelnen nachzuladenden Entitäten in einer Foreach-Schleife an die bestehende IQueryable-Collection an.

Listing 3
public IEnumerable GetAll(params Expression>[] includes) { return this.Context.Users .Include(includes) .AsEnumerable(); }
Listing 4
public static IQueryable Include(this IQueryable source, Expression>[] includes) where T : class { foreach (var item in includes) { source = source.Include(item); }  return source; }

[ header = Neuerungen im Entity Framework 5.0 - Teil 2 ]

Validierung

Das Thema Validierung, immer wieder wird es in Fachartikeln und „Getting Started“-Guides vernachlässigt oder gar vergessen. Eine Erklärung hierfür könnte sein, dass die Validierung mit den bisherigen Versionen des Entity Frameworks nur sehr eingeschränkt und umständlich möglich war. Endlich ist Land in Sicht, und die nun aktuelle Version 5 des O/R Mappers hat in dieser Kategorie eine große Verbesserung erfahren. Der DbContext, der in der neuen Version eingeführt und weiter oben schon erklärt wurde, ist mit der Methode GetValidationErrors() ausgestattet, die es erlaubt, aktuelle Validierungsfehler komfortabel auszulesen. Der wohl größte Vorteil ist, dass die Methode auch DataAnnotations berücksichtigt und man seine Entitäten nicht nur schlank, sondern auch einfach mit Validierungslogik ausstatten kann. Ein Beispiel ist in Listing 5 zu finden.

Listing 5
DatabaseContext context = new DatabaseContext(); var user = context.Users.Find(1); user.Email = "kontakt(at)timm-bremus.de"; // Ungültige E-Mail Adresse var validList = context.GetValidationErrors(); foreach(var item in validList) { foreach(var error in item.ValidationErrors) { Response.Write(string.Format("{0}: {1}", error.PropertyName, error.ErrorMessage); } }

Der DbContext kann auch so konfiguriert werden, dass er vor dem Speichern von Änderungen durch die Methode SaveChanges() geladene und evtl. modifizierte Entitäten auf Validierungsfehler prüft. Das hat den charmanten Vorteil, dass Fehler nicht auf Datenbankebene provoziert, sondern vor dem Übertragen auf nicht valide Eingaben überprüft werden und diese gezielt in der Benutzeroberfläche abgefangen werden können. Das spart nicht zuletzt auch Performance, da das Öffnen der Datenbankverbindung ebenfalls seine Zeit braucht, die man sich mit einer vorherigen Validierung einsparen kann. Diese Funktionalität muss explizit am DbContext-Objekt eingestellt bzw. aktiviert werden. Das erreicht man durch folgende Anweisung:

this.Context.Configuration.ValidateOnSaveEnabled = true;
Datenbindung

Verbessert wurde im neuen Entity Framework auch die Möglichkeit der Datenbindung an eine WPF- oder Windows-Forms-Oberfläche. Die beiden neu hinzu gekommenen Methoden AddObject() und DeleteObject() ersparen dem Entwickler die Implementierung einer eigenen ObserveableCollection. Zudem kann über das Property Local eines DbSet eine ObserveableCollection-Instanz vom DbContext angefordert werden. Veranschaulicht wird das in Listing 6. Besonders zu achten ist auf das Laden der Daten in den Context. Hier fällt auf, dass das Ergebnis in keiner Variablen gespeichert, sondern nur im Cache eingelagert wird.

Listing 6
DatabaseContext context = new DatabaseContext();  // Daten einmalig in den Context laden var user = context.Users.Where(u => u.Surname.StartsWith("B")).ToList();  // Datenbindung this.userDataGrid.ItemsSource = context.Users.Local;
Entity Framework Designer

Der Designer des Entity Frameworks, der mit Visual Studio standardmäßig ausgeliefert wird, hat zahlreiche Neuerungen erfahren. Neuerdings wird das Schema beim Datenbankimport in den Designer übernommen. Es besteht ebenso die Möglichkeit, einzelne Tabellen in den Datenbank zu aktualisieren und nicht länger der Zwang, alle Tabellen in der Datenbank vom Designer aus neu zu schreiben.

Die wohl größte Änderung ist die Möglichkeit, mehrere Diagramme im Designer erzeugen und verwalten zu können. Entitäten können zwischen den Diagrammen verschoben und auch mehrmals verwendet werden. In den Entitäten selbst können Properties nach oben und unten verschoben werden. Leider geht das nur über das Kontextmenü, jedoch noch nicht per Maus-Drag-and-Drop. Löscht man eine Entität aus dem Diagramm, wird sie nicht automatisch auch aus der Datenbank entfernt. Sie kann nach wie vor über den Model-Browser (Abb. 2) in einem anderen Diagramm verwendet werden. Um eine Entität endgültig zu löschen, muss sie nunmehr direkt im Model-Browser entfernt werden.

Abb. 2: Model Explorer des Entity Frameworks 5

Das Importieren von Stored Procedures ist schon länger möglich, die Fähigkeit hingegen, auch Table-valued Functions [2] zu importieren, ist in Version 5 neu hinzugekommen. Damit hat man ein weiteres starkes Werkzeug an der Hand, um Abfragen über den O/R Mapper an die Datenbank maßgeblich zu beschleunigen.

Nicht unerwähnt sollte bleiben, dass an einer Beziehung beteiligte Properties im Designer nun hervorgehoben werden, wenn die jeweilige Beziehung mit der Maus ausgewählt wurde. Das verschafft dem Entwickler bei komplexen Datenmodellen einen besseren Überblick und hilft dabei, große, vielverzweigte Diagramme besser zu verstehen und zu verwalten.

Code First

Der Ansatz des Code First ist zwar schon seit Version 4.1 des Entity Frameworks integriert, sollte hier jedoch nicht unerwähnt bleiben. Der Code-First-Ansatz ermöglicht es dem Entwickler, so genannte „Plain Old CLR Objects“ (POCOs) zu schreiben und den DbContext, der ansonsten vom Designer im Visual Studio erzeugt wird. Der Vorteil ist hier ganz klar, dass die Entitäten, die über die POCOs abgebildet werden, frei von Entity-Framework-spezifischem Code sind und es hier ein Leichtes ist, Validierung und Globalisierung mithilfe von DataAnnotations umzusetzen. Zwar können DataAnnotations den über den Designer erzeugten Entity Properties auch über so genannte Metaklassen angefügt werden, das ist aber deutlich mehr Schreibaufwand als bei einer POCO-Klasse, die im Zuge des Code-First-Ansatzes implementiert wurde.

Der große Vorteil bei Code First ist, dass die Entitätenklassen nicht in einem einzigen Namespace verankert sein müssen, sondern verstreut in der Solution angelegt werden und darüber hinaus in verschiedenen Assemblies existieren können. Zudem ist es sehr einfach, das Datenbanksystem einer Anwendung auszutauschen, da die POCO-Klassen keinerlei spezifischen Code in Bezug auf Datenbank und Datenbankmodell beinhalten. Bei großen Modellen könnte jedoch die Übersicht verloren gehen, da es (anders als im Designer) kein Diagramm zur Übersicht über die Entitäten gibt. Das Reverse Engineering, also das Erzeugen eines Modells aus einer bestehenden Datenbankstruktur, ist natürlich bei diesem Ansatz ausgeschlossen und nur unter Zuhilfenahme des Designers möglich.

Mapping

Lange wurde sie gefordert, in der neuen Version ist sie nun endlich Realität: die Unterstützung von Enums. Was man in der Vorgängerversion noch umständlich durch die Hintertür realisieren musste, geht nun ganz einfach im Entity Designer und auch bei Code First. Ein Property mit einem Enum-Typ wird intern in der Datenbank als Integer angelegt und dann vom Entity Framework in den jeweiligen Enum-Typ gemappt. Sowohl geografische als auch geometrische Datentypen werden zur Verfügung gestellt, um diese Arten von Informationen besser in der Datenbank persistieren und natürlich später über das Entity Framework wieder auslesen zu können.

Microsoft hat in Sachen Performanceoptimierung dem Entwickler einiges an zuvor nötiger Eigenleistung abgenommen und die automatische Kompilierung von LINQ-Queries eingeführt. In den Vorgängerversionen war es noch dem Entwickler überlassen, seinen Code dahingehend zu erweitern, dass seine LINQ-Abfragen mit kompiliert werden. Die meisten Entwickler haben sich diesen Schritt entweder aus Unwissenheit oder aus zeitlichen Gründen gespart. Das führte dazu, dass Abfragen rund dreimal so viel Zeit in Anspruch nahmen als vorkompilierte Abfragen.

Fazit und Ausblick

Die neuen Funktionen des Entity Frameworks 5 versetzen den Entwickler in die Lage, noch einfacher und effizienter mit einer Datenbank zu kommunizieren und die Entwicklungszeit für die Datenzugriffsschicht zu verkürzen. Das Argument, dass die Performance bei Microsofts O/R Mapper nicht ausreichend für hochfrequent genutzte Geschäftsanwendungen ist, ist ein alter Hut und nur noch schwer zu vertreten. Es gibt wohl kaum ein vergleichbares Projekt, das bei Microsoft so sehr forciert vorangetrieben wird wie das Entity Framework. Schon jetzt hat der Redmonder Softwareriese die wichtigsten Features und Neuerungen für die kommende Version 6 verraten. Die bei WinRT hochgelobte Möglichkeit, Tasks einfach und übersichtlich asynchron auszuführen, soll auch für Abfragen des Entity Frameworks unterstützt werden. Damit wären lange Wartezeiten und einfriedende Benutzeroberflächen bei großen Abfragen passé. Weiterhin wird es eine Unterstützung von Stored Procedures und Functions bei Code First geben und nicht nur länger über den Designer. Wir dürfen gespannt sein auf die weitere Entwicklung des Microsoft Entity Frameworks.

Im Vergleich: Stored Procedure und User-defined Function

Stored Procedure (SP) Eine Stored Procedure ist ein Programm bzw. eine Prozedur, die physikalisch in der Datenbank gespeichert wird. Der Vorteil bei einer solchen Prozedur ist, dass sie bei ihrer Ausführung direkt auf dem Datenbankserver und dessen Engine läuft und man so die Performance seiner Anwendung steigern kann, da man die Last auf mehrere Systeme verteilt. Da eine Stored Procedure direkt an der Datenbank hängt, ist das Manipulieren von Daten in einer darin befindlichen Tabelle deutlich schneller, als wenn die Modifizierung der Daten direkt in der Anwendung geschehen würde. Man spart sich hier den Transportweg der Daten zwischen Datenbank und Anwendung.

User-defined Function (UDF) Eine User-defined Function ist eine Routine, die eine bestimmte Logik kapselt und die dann in verschiedenen Abfragen, z. B. WHERE oder SELECT, wiederverwertet werden kann. User-defined Functions haben neben den Views deshalb ihre Daseinsberechtigung, da Views z. B. nur ein SELECT-Statement beinhalten können, die Function hingegen mehrere. Damit sind hier deutlich mächtigere Abfragen und Logiken möglich. Eine UDF erwartet keinen, einen oder mehrere Parameter und gibt ein Ergebnis zurück, das ein wesentlicher Unterschied zur Stored Procedure ist, denn diese muss keinen Rückgabewert haben.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -