Entity Framework

Entity Framework 6: Die Ninja-Edition

Julie Lerman

Mit der neuesten Hauptversion von Entity Framework – EF6– hat das ORM(Object-Relational Mapping)-Tool von Microsoft eine neue Ebene der Professionalität erreicht. Folglich gilt dieses Tool nicht länger als der ländliche Verwandte der seit Langem etablierten .NET-ORM-Tools. EF ist erwachsen geworden und übertrumpft die vormaligen „Alteingesessenen“ bei Weitem.

Entity Framework hat das kabbelige Meer seiner Kindheit hinter sich gelassen, in der es schwerpunktmäßig als Tool für Datenbankentwickler galt und sich dann den Zorn von Agile-Entwicklern in der .NET-Community zuzog. Entity Framework lernte, der Anwendungsentwicklung nicht in die Quere zu kommen und wechselte zu einem POCOs(Plain Old CLR Objects)-Modell, sodass es Tests und domänenbasierte Softwareentwicklung ermöglicht, ohne datenorientierte Entwickler zu „entrechten“. Es löst Leistungsprobleme sowie zahlreiche Bedenken hinsichtlich der Qualität von generiertem Code und konnte im Verlauf der Zeit viele Datenbankadministratoren (DBAs) für sich gewinnen.

Ab EF 4.1 erkannte Microsoft die von EF benötigte Komplexität und vereinfachte durch die Einführung der DbContext-API den Zugriff auf die EF-Funktionalität. Da nicht jeder gerne mit einem Designer oder mit generiertem Code arbeitet, wurde gleichzeitig die Möglichkeit bereitgestellt, Modelle mit eigenem Code zu erstellen. Im weiteren Entwicklungsverlauf fand eine andere bedeutsame Änderung statt, bei der es nicht um Features, Syntax, Code oder Leistung ging. Das EF-Team wurde für die Benutzercommunity zunehmend transparenter und interaktiver und begann, Features flexibler bereitzustellen und nicht mehr an Microsoft .NET Framework zu binden. Das führte nach der Einführung von EF5 im Jahre 2012 zu zwei wichtigen Änderungen. Zum Ersten wurden alle Entity Framework-APIs aus .NET Framework extrahiert und mit den Out-of-Band-Feature-APIs kombiniert, an denen das Team ebenfalls arbeitete. Zum Zweiten verschob sich der gesamte Entwicklungsaufwand hin zu einem Open Source-Modell. EF6 ist öffentlich auf github.com/aspnet/EntityFramework6 entwickelt worden. Sie können also nicht nur anhand von Besprechungsnotizen, Eincheckvorgängen sowie herunterladbaren nächtlichen Builds die Aktivitäten des Teams nachverfolgen, sondern auch Quellcode für EF6 bereitstellen (mit vollständiger Überprüfung durch das EF-Team).

Bedenken Sie, dass EF6 eine Evolution, keine Revolution darstellt. Nahezu alles, was Sie bereits über EF wissen, bleibt unverändert (zum Beispiel, wie Sie Entity Framework-Modelle erstellen und EF in Ihren Anwendungen nutzen). EF6 stellt einen Fortschritt für ORM dar, ändert jedoch nicht dessen grundlegende Funktionsweise. Wenn Sie Investitionen getätigt haben, um EF zu erlernen, macht sich dies weiterhin bezahlt. EF6 weist durchaus bedeutende Veränderungen auf, die sich jedoch auf einige Namespaceänderungen beschränken und bei entsprechenden Kenntnissen einfach zu handhaben sind. Am Ende dieses Artikels nenne ich Ihnen Ressourcen für Anleitungen.

Ich unterteile die EF6-Features in mehrere Kategorien:

  1. Enthaltene Features: Diese Funktionen sind Bestandteil des Kerns. Sie müssen nicht einmal wissen, dass sie da sind, um ihre Vorteile zu nutzen. Und Sie müssen dafür keinen neuen Code erlernen. Diese Gruppe enthält Features wie beispielsweise Leistungsgewinne durch ein neu geschriebenes Modul für die Erstellung von Ansichten sowie eine geänderte Abfragekompilierung, und die Stabilität wird durch die Fähigkeit von „DbContext“ gewährleistet, eine bereits offene Verbindung nutzen zu können. Zudem ist eine Datenbankeinstellung für von Entity Framework erstellte SQL Server-Datenbanken geändert worden.
  2. Features auf Einstellungsebene: Eine wichtige Verbesserung ist, das Code First nun die Zuordnung zu gespeicherten Prozeduren unterstützt, wie es bereits bei im Designer erstellten Modellen der Fall war. Dieses Feature wird ausführlich in Channel 9-Videos (wie z. B. unter bit.ly/16wL8fz) und in einer detaillierten Spezifikation auf der CodePlex-Website behandelt, sodass ich diese Informationen in diesem Artikel nicht wiederhole.
  3. Eine andere Änderung ist deutlich interessanter. Wie bereits gesagt wurden in EF6 die EF-APIs aus .NET Framework extrahiert; diese sind nun vollständig im NuGet-Paket gekapselt. Das heißt, dass bestimmte, mit EF5 eingeführte Features – beispielsweise die Unterstützung von Aufzählungen und räumlichen Daten sowie Leistungsverbesserung – nicht mehr von .NET 4.5 abhängig sind. Wenn Sie also .NET 4 mit EF6 verwenden, können Sie diese Features nun endlich nutzen.
  4. In diese Kategorie würde ich auch den Entity Framework Designer einordnen. Der EF Designer wurde mit der Version 2013 aus Visual Studio ausgegliedert und wird nun als Erweiterung für Visual Studio bereitgestellt. Für EF6 stellt das Vorhandensein des Designers als Erweiterung einen großen Bonus dar. In Zukunft kann das Team die Features direkt für den EF Designer hinzufügen, und zwar auch solche, die derzeit über die Entity Framework Power Tools bereitgestellt werden. Durch die Trennung von Designer und Visual Studio kann Microsoft die EF6-Tools sowohl für Visual Studio 2012 als auch für Visual Studio 2013 liefern.
  5. Ninja-Features: Nach diesen Features haben Sie sich seit den ersten EF-Beispielanwendungen gesehnt. In EF6 sind viele davon vorhanden: Unterstützung für asynchrone Abfrage- und Speichervorgänge, Rückkehr zu benutzerdefinierten Code First-Konventionen, zusätzliche Erweiterbarkeit mit dem neuen DbConfiguration-Typ (der auf dem niedrigstufigen EF6-Element „IDbDependencyResolver“ basiert), Unterstützung von Modellen in Komponententests, konfigurierbare Wiederholungen bei unzuverlässigen Verbindungen und vieles mehr. Zur Nutzung dieser Features müssen Sie kein ausgewiesener Experte sein, aber Sie werden sich dabei wie einer fühlen!

Ich möchte noch eine spezielle Kategorie hervorheben: Die von Mitgliedern der Community stammenden EF6-Beiträge. Unai Zorrilla fügte „DbSet.AddRange“ und „RemoveRange“, die Möglichkeit der anpassbaren Pluralisierung sowie die nützliche DbChangeTracker.HasChanges-Methode hinzu. Er arbeitet zudem an weiteren tollen Features für eine zukünftige Iteration von Entity Framework. Erik Jensen, ein SQLCE(SQL Server Compact)-MVP, lieferte „SQLCeFunctions“, dies entspricht „SqlFunctions“ zur Verwendung von SQL Server-Funktionen in LINQ to Entities-Abfragen. Die deutlich verbesserte Geschwindigkeit bei der Erstellung von EF-Ansichten – kommt am meisten bei großen und komplexen Modellen zum Tragen – wurde von Alireza Haghshenas und einem CodePlex-Mitglied mit dem Namen VSavenkov erzielt. Dank Iñaki Elcoro (auf CodePlex auch als Iceclow bekannt) ist nun auch das Definieren von benutzerdefinierten Migrationsvorgängen möglich. (Rowan Miller vom EF-Team hat zu diesem Feature einige Blogbeiträge verfasst, den ersten finden Sie unter bit.ly/ZBU0w1.) Eine vollständige Liste aller Beitragenden finden Sie im Team-Blogbeitrag „RTM von EF6 verfügbar“ unter bit.ly/1gmDE6D.

In diesem Artikel beleuchte ich Themen, zu denen weniger Veröffentlichungen vorliegen, und zeige Ihnen vorhandene Ressourcen für die anderen auf.

Im MSDN-Datenentwicklercenter (bit.ly/1gCT0nz) finden Sie den Versionsverlauf mit allen Features, die in einem oder zwei Sätzen erläutert werden. Zudem sind Links zu weiteren Informationen vorhanden.

Es funktioniert einfach: Leistungsverbesserungen und Stabilität

Die Leistung ist das Rückgrat zahlreicher Softwareprojekte, und seit der Einführung von Entity Framework gab es viel Kritik an der Leistung von EF. Jedoch wies jede EF-Iteration umfangreiche Verbesserungen in diesem Bereich auf.

Eine der größten Leistungsanforderungen besteht in der Startzeit, die bei der ersten Verwendung eines Kontexts in einem Anwendungsprozess entsteht. Es gibt jedoch zahlreiche Möglichkeiten, um diese Startzeit zu verkürzen. Im besten Fall haben Sie diese Tipps schon meinen eigenen Beiträgen oder anderen Ressourcen entnommen, wie zum Beispiel dem MSDN-Artikel zum Thema der Leistungsaspekte bit.ly/3D6AiC.

Ein Schritt im Rahmen des Startvorgangs, der häufig die Leistung beeinträchtigt, ist die Erstellung von Zuordnungsansichten. Dabei erstellt EF das benötigte SQL, um die einzelnen Entitätssätze im Modell abzufragen. Diese Ansichten werden bei der App-Ausführung genutzt, sodass Entity Framework bei bestimmten Abfragen das SQL nicht dynamisch generieren muss. Die Ansichten werden unabhängig davon generiert, ob Sie das Modell mit dem EF Designer oder mit Code First erstellt haben. Um Zeit zu sparen, können Sie diese Ansichten vorab generieren und in die Anwendung kompilieren.

Bei großen und komplexen Modellen ist das Erstellen von Ansichten besonders zeitaufwendig gewesen. Dieser Prozess ist für EF6 überarbeitet worden und weist nun eine deutliche bessere Geschwindigkeit auf, und zwar unabhängig davon, ob Sie die Ansichten vorab erstellen oder dies zur Laufzeit erfolgt. In der EF 6.0.0-Version war ein Fehler vorhanden, der dieses Feature blockierte. Dieser ist aber bereits in der Version EF 6.0.1, die am gleichen Tag veröffentlicht wurde und derzeit (während ich diesen Artikel verfasse) das über NuGet ausgelieferte Standardpaket ist, korrigiert worden. Zusätzlich wurde noch die Art und Weise verbessert, auf die Entity Framework diese generierten Ansichten zur Laufzeit verwendet, sodass die Ausführungszeit der Abfragen verkürzt werden konnte. Bei kleinen oder einfachen Modellen war die Erstellung von Ansichten nie ein Problem. Aber viele Organisationen verfügen über Modelle mit Hunderten Entitäten, in die Vererbung, Beziehungen und andere erschwerende Faktoren einbezogen werden müssen. Für solche Organisationen stellt diese Änderung einen großen Vorteil dar.

Eine weitere Anmerkung zur Leistung finden Sie in der Anleitung für die NGen-Verwendung in der Entity Framework-Assembly, die im Blogbeitrag mit der Ankündigung der EF6-Veröffentlichung unter bit.ly/1gmDE6D enthalten ist.

Schnellere Kompilierung von „LINQ Contains“. Das EF-Team ist nach wie vor mit der Optimierung der Abfragenerstellung beschäftigt. Eine vom Team hervorgehobene Änderung besteht in der Kompilierung von Abfragen, die „LINQ Contains“ verwenden. Damit es kein Missverständnis gibt: Die Leistung des Kompilierungsprozesses ist verbessert worden. Das generierte SQL ist unverändert, sodass die Ausführung der Abfrage in der Datenbank nicht beeinträchtigt ist.

SQL Server-Datenbankerstellung. Eine der in EF6 vorgenommenen Verbesserungen im Bereich der Stabilität bezieht sich auf die Datenbankerstellung. Sowohl mit dem Model First- als auch dem Code First-Workflow kann eine Datenbank erstellt werden. Handelt es sich dabei um eine SQL Server-Datenbank, ist EF nun mit einer Best Practice für SQL Server-Datenbanken ausgestattet, die darin besteht, die READ_COMMITTED_SNAPSHOT-Einstellung der Datenbank auf „ON“ zu setzen. Das bedeutet, dass die Datenbank standardmäßig bei jeder Änderung eine Momentaufnahme von sich selbst erzeugt. Die Abfragen werden mit der Momentaufnahme ausgeführt, wohingegen Updates in der tatsächlichen Datenbank erfolgen. Zu diesem Feature habe ich kürzlich einen Blogbeitrag mit dem Namen „Worum geht es bei der Read_Committed_Snapshot-Transaktionsunterstützung für EF6 überhaupt?“ verfasst, den Sie unter bit.ly/14FDpZI finden.

Erneute Verwendung offener Verbindungen. Endlich ist ein frustrierendes Limit aufgehoben worden: Mit EF6 können Sie Kontextaufrufe mit einer offenen „DbConnection“ ausführen. Wenn Sie in der Vergangenheit explizit eine Verbindung geöffnet haben, bevor Sie den EF-Befehl, der diese Verbindung benötigte, ausführten, oder wenn Sie versuchten, eine bereits durch einen anderen Kontextaufruf geöffnete Verbindung erneut zu nutzen, trat eine Ausnahme mit der Meldung „EntityConnection kann nur mit einer geschlossenen DbConnection konstruiert werden“ auf. Nun können Sie mit EF6 eine bereits offene Verbindung erneut nutzen.

Ninja-Verbesserungen

Async-Unterstützung. In der Rubrik „Datenpunkte“ vom März 2013 habe ich in meinem Artikel „Spielen mit der EF6-Alphaversion“ einige der neues Features untersucht, darunter Async-Abfragen, „SaveChanges“ und benutzerdefinierte Konventionen (msdn.microsoft.com/magazine/jj991973).

Durch die Async-Unterstützung wird das Async/Await-Muster von .NET 4.5 für die LINQ-Abfrageausführungsmethoden in EF eingeführt, sodass Sie „FirstAsync“, „FirstOrDefaultAsync“, „SingleAsync“, „SingleOrDefaultAsync“, „ToListAsync“, „ForEachAsync“ und mehr nutzen können. Eine vollständige Liste finden Sie in „System.Data.Entity.QueryableExtensions“. Für „DbSet“ ist „FindAsync“ und für „DbContext“ ist „SaveChangesAsync“ hinzugekommen. Seit der Verfassung des Artikels hat es nicht viele Veränderungen gegeben, daher können Sie diesen für ausführlichere Informationen heranziehen. Außerdem hat Microsoft einige exemplarische Vorgehensweisen sowie eine interessante detaillierte Spezifikation erstellt, die Sie auf der bereits erwähnten Seite mit dem Versionsverlauf erhalten.

Benutzerdefinierte Konventionen. Im genannten Artikel erwähne ich auch die benutzerdefinierten Code First-Konventionen, bei denen es sich definitiv um ein Ninja-Feature handelt. Das EF-Team arbeitete bereits für die erste Code First-Version daran, jedoch verzögerte sich dadurch die Veröffentlichung und das Team war gezwungen, das Thema zur Seite zu legen – zur großen Enttäuschung vieler Entwickler.

Angenommen, Sie haben eine allgemeine Zuordnung, die Sie als generelle Regel auf Ihre Entitäten oder Eigenschaften anwenden möchten. Nun können Sie diese als Konvention definieren, anstatt die Zuordnung für jede Entität oder jede Eigenschaft im Modell einzeln angeben zu müssen. Diese Konvention wird dann allgemein angewendet. Wenn Sie beispielsweise möchten, dass anstelle des von Ihrem Datenbankanbieter verwendeten Standardwerts jede Zeichenfolgeneigenschaft in Ihrer Datenbank mit 50 Zeichen dargestellt wird, können Sie diese Regel als Konvention festlegen. Die Konventionen nutzen die Fluent-API von Code First. Sofern Sie also Zuordnungen auf die folgende Art und Weise konfiguriert haben, sollten Sie mit dem Erstellen von Konventionen vertraut sein:

modelBuilder.Properties<String>().Configure(p => p.HasMaxLength(50))

Jede Zeichenfolge in diesem Modell wird einer Datenbankspalte mit 50 Zeichen zugeordnet. Wie bei den Fluent- oder Annotations-Konfigurationen lassen sich Konventionen für Eigenschaften oder Entitäten und zur Steuerung der Vererbungszuordnungen angeben. Beziehungen über eine Konvention zu beeinflussen ist eine weniger gängige und deutliche komplexere Aufgabe, die von modellbasierten Konventionen übernommen wird. Weitere Informationen finden Sie unter bit.ly/1gAqcMq. Eine Konvention kann auch als eine Datenanmerkung verwendet werden. Bei der Modellerstellung mit Code First liegt für die Ausführung der Konventionen eine Hierarchie vor. Standardmäßig werden integrierte Konventionen zuerst ausgeführt, danach folgen die benutzerdefinierten Konventionen. Sie können jedoch erzwingen, dass die Ausführung einer benutzerdefinierte Konvention vor einer integrierten erfolgt. Beispiele dazu finden Sie unter „Benutzerdefinierte Code First-Konventionen“ unter bit.ly/14dg0CP.

Verbindungsstabilität. Falls eine Verbindung getrennt wird, während EF das Ausführen einer Abfrage oder das Speichern von Änderungen versucht, können Sie in EF nun eine Wiederholung veranlassen. Obwohl unterbrochene Verbindungen in unternehmenseigenen Intranets ein Problem darstellen können, hat sich die Verbindungsstabilität als sehr nützlich herausgestellt, da sie hilfreich für Apps ist, die eine Verbindung zur Cloud herstellen. Die Wiederholungen lassen sich mit „IDbConnectionStrategy“ konfigurieren. Der in EF integrierte SQL Server-Anbieter gibt „default:SqlServerExecutionStrategy“ vor, und in einer Fehlermeldung wird vorgeschlagen, die Strategie für Ausnahmen, die von vorübergehenden Verbindungsausfällen ausgelöst werden, zu optimieren. Für Verbindungen zur Windows Azure SQL-Datenbank ist „SqlAzureExecutionStrategy“ optimal geeignet.

Die einfachste Möglichkeit zum Spezifizieren einer Strategie ist mit der neuen DbConfiguration-Klasse gegeben, mit der sich das Verhalten eines bestimmten Datenbankanbieters einfach konfigurieren lässt. Mit folgender Codesequenz wird Entity Framework angewiesen, „SQLAzureExecutionStrategy“ für „SqlClient“ zu verwenden:

SetExecutionStrategy (SqlProviderServices.ProviderInvariantName,
  () => new SqlAzureExecutionStrategy());

Sie können nicht nur die Verbindungsstrategien konfigurieren, sondern auch eigene erstellen und diese bei Bedarf programmgesteuert unterbrechen. Das EF-Teammitglied Miller veranschaulicht in seinem Blogbeitrag unter bit.ly/14gPM1y, wie diese Unterbrechung funktioniert.

Ich habe „SqlAzureExecutionStrategy“ mit einem Trick von einem anderen EF-Teammitglied, Glenn Condron, getestet. Um die bestimmten Fehlercodes für die vorübergehende Verbindungsunterbrechung auszulösen, auf die diese Strategie abzielt, habe ich mit dem neuen Feature der EF6-Befehlsunterbrechung einen vorübergehenden Verbindungsausfall ausgelöst. Anschließend führte ich einen Test aus, dessen Ergebnisse ergaben, dass bei gesetzter Ausführungsstrategie die Abfrage nach dem ersten Fehlversuch fünfmal wiederholt wurde. Zu meinem Blogbeitrag für dieses Feature gibt es in einen tollen Kommentar von einem Entwickler. Laut seiner Aussage profitiert sein Unternehmen bereits von den Vorteilen dieses Features (bit.ly/HaqMA0).

Es gibt auch noch einen interessanten Algorithmus, der sicherstellt, dass die Wiederholungen für verschiedene Threads nicht alle zur gleichen Zeit ausgeführt werden.

„Share DbTransactions“ und „DbConnections“. Ich denke, Sie wissen inzwischen, dass EF standardmäßig immer „DbTransaction“ für Aufrufe verwendet, die an die Datenbank gesendet werden. Beispielsweise wird bei einem Aufruf von „SaveChanges“ ein DbTransaction-Objekt erstellt, bevor der erste Befehl an die Datenbank übermittelt wird. Anschließend sendet EF alle benötigten Befehle zum Einfügen, Aktualisieren und Löschen an die Datenbank. Zum Abschluss wird die Transaktion per Commit ausgeführt. Falls ein Befehl fehlschlägt, wird für alle zuvor ausgeführten Befehle ein Rollback ausgeführt.

Es besteht immer die Möglichkeit, dieses Standardverhalten außer Kraft zu setzen. Dazu verwenden Sie ein TransactionScope-Objekt, um den EF-Aufruf sowie alle anderen Aufrufe (nicht zwangsläufig EF- oder datenbankbezogen) zu umschließen, die in derselben Transaktion sein müssen. Zudem kann in EF6 ein einziges DbTransaction-Objekt für mehrere Datenbankaufrufe zuständig sein. Wenn Sie Logik, die nicht datenbankbezogen ist, in die Transaktion einbeziehen oder verteilte Transaktionen für Aufrufe verschiedener Datenbanken einbinden möchten, müssen Sie ebenfalls ein TransactionScope-Objekt verwenden.

Der Schlüssel für die gemeinsame Verwendung von „DbTransactions“ mit EF6 liegt in einer neuen BeginTransaction-Methode, die eine Referenz an das aktuelle DbTransaction-Objekt und eine UseTransaction-Methode zurückgibt.

Dieser Code veranschaulicht das Standardverhalten:

//code to create two new casinos, "casino1" & "casino2"
var context = new CasinoSlotsModel();
context.Casinos.AddRange(new[] { casino1, casino2 });
context.SaveChanges();
context.Database.ExecuteSqlCommand
  ("Update Casino.Casinos set rating= " +
 (int) casino.Rating)

In meinem Profiler wird für jeden Kontextaufruf eine Transaktion verwendet – eine für „SaveChanges“, die zwei Einfügevorgänge auslöst, und eine für „ExecuteSqlCommand“, die die Aktualisierung auslöst, wie in Abbildung 1 veranschaulicht.

Commands from Separate Context Calls Wrapped in Their Own Transactions
Abbildung 1: Befehle von unterschiedlichen Kontextaufrufen, die in ihren eigenen Transaktionen umschlossen werden

Nun werde ich den Code zur gemeinsamen Verwendung einer Transaktion ändern, indem ich die SaveChanges- und ExecuteSqlCommand-Aufrufe umschließe. Ich verwende das neue DbContext.Database.BeginTransaction-Objekt, um „System.Data.Entity.DbContextTransaction“ explizit zu instanziieren und ggf. eine Verbindung zu öffnen (beachten Sie hier, dass es einen ähnlichen Befehl für „DbContext.Database.Connection“ gibt, dieser aber ein System.Data.Common.DbTransaction-Objekt zurückgibt, das von den EF-Befehlen nicht gemeinsam verwendet werden kann):

using (var tx = context.Database.BeginTransaction()) {
  try  {
    context.SaveChanges();
    context.Database.ExecuteSqlCommand
      ("Update Casino.Casinos set rating= " +
      (int) casino.Rating);
    tx.Commit();
  }
  catch (Exception)  {
    tx.Rollback();
  }
}

In Abbildung 2 sehen Sie, dass alle Befehle in derselben Transaktion umschlossen sind.

Commands from All Context Calls in a Single Transaction
Abbildung 2: Befehle aus allen Kontextaufrufen in einer einzigen Transaktion

Ein weiteres neues Feature hat den Namen „UseTransaction“. Sie können ein DbTransaction-Objekt erstellen und dieses für ADO.NET-Aufrufe verwenden. Mit „DbContext.Database.UseTransaction“ lassen sich EF-Aufrufe sogar von getrennten Kontextinstanzen innerhalb derselben Transaktion ausführen. Ein Beispiel dafür finden Sie unter bit.ly/1aEMIuX.

Weiterhin ist es möglich, eine offene Verbindung explizit erneut zu verwenden, da EF nun ein EntityConnection-Objekt (dies wird von „ObjectContext“ im Hintergrund ausgeführt) mit einer bereits offenen Verbindung erstellen kann. Außerdem schließt ein Kontext keine Verbindung, die nicht durch ihn selbst geöffnet wurde. Ich habe einen einfachen Test geschrieben, bei dem ich die Verbindung eines Kontexts öffne, bevor ein Aufruf des Kontexts ausgeführt wird:

[TestMethod]
public void ContextCanCreateEntityConnectionWithOpenConnection()
{
  using (var context = new CasinoSlotsModel())
  {
    context.Database.Connection.Open();
    Assert.IsNotNull(context.Casinos.ToList());
  }
}

Bei der Ausführung des ToList-Aufrufs wird von „DbContext“ eine ObjectContext-Instanz erstellt, die wiederum ein EntityConnection-Objekt erstellt. Anschließend wird unter Verwendung des EntityConnection-Objekts „DbConnection“ geöffnet und wieder geschlossen, wenn der Aufruf beendet ist und alle Ergebnisse zurückgegeben sind. Eine solche Ausführung führt in EF5 zu einer Ausnahme („EntityConnection kann nur mit einer geschlossenen DbConnection konstruiert werden“), in EF6 ist sie jedoch aufgrund des geänderten Verhaltens erfolgreich. Durch diese Änderung lassen sich Verbindungen in Szenarien wiederverwenden, in denen Sie mehr Kontrolle über den Status der Verbindung erlangen möchten. Die Spezifikationen schlagen Szenarios „wie z. B. die gemeinsame Verwendung einer Verbindung zwischen Komponenten, für die Sie den Status der Verbindung gewährleisten können“ vor.

„AddRange“ und „RemoveRange“. Wie bereits gesagt stammen die Beiträge „AddRange“ und „RemoveRange“ vom Communitymitglied Zorrilla. Jede Methode nutzt eine Aufzählung eines einzelnen Entitätstyps als Parameter. Im ersten Codebeispiel habe ich im gemeinsam verwendeten DbTransactions-Abschnitt „AddRange“ zum Übergeben eines Arrays mit Casino-Instanzen verwendet:

context.Casinos.AddRange(new[] { casino1, casino2 });

Diese Methoden führen viel schneller aus, als pro Schritt ein einzelnes Objekt hinzuzufügen oder zu entfernen, da Entity Framework standardmäßig „DetectChanges“ in jeder Add- und Remove-Methode aufruft. Mit den Range-Methoden können mehrere Objekte verarbeitet werden, wohingegen „DetectChanges“ nur einmal ausgeführt wird, was zu einem erheblichen Leistungsgewinn führt. Ich habe dies mit fünf, 50, 500, 5000 und sogar 50.000 Objekten getestet: Es gibt (zumindest in meinem Szenario) keine Beschränkung für die Arraygröße, und es ist beeindruckend schnell! Bedenken Sie, dass diese Verbesserung nur zum Tragen kommt, wenn der Kontext für Objektaktionen abgerufen wird. Auf „SaveChanges“ hat dies keinerlei Auswirkungen. Der Aufruf von „SaveChanges“ führt nach wie vor nur einen Datenbankbefehl zurzeit aus. Folglich können Sie zwar schnell 50.000 Objekte für einen Kontext hinzufügen, aber dennoch werden beim Aufruf von „SaveChanges“ 50.000 Einfügebefehle einzeln ausgeführt. Das möchten Sie in einem Produktionssystem vermutlich nicht machen.

Auf der anderen Seite gab es ausgiebige Diskussionen darüber, ob die Implementierung der Unterstützung für Massenvorgänge ohne erforderliche Objektverfolgung durch EF erfolgen sollte (bit.ly/16tMHw4) und ob Batchvorgänge mehrere Befehle in einem einzelnen Aufruf an die Datenbank senden können sollten (bit.ly/PegT17). Keines dieser Features schaffte es in die erste EF6-Version, aber beide sind wichtig und für eine zukünftige Version vorgesehen.

Weniger Überschneidungen mit Ihrem Codierungsstil. In .NET kann die System.Object.Equals-Methode überschrieben werden, um die Regeln für Übereinstimmung im System festzulegen. Entity Framework hat jedoch einen eigenen Weg zur Ermittlung von Übereinstimmungen bei nachverfolgten Entitäten, und dieser ist abhängig von der Identität. Wenn Sie „Equals“ (und die GetHashCode-Methode, auf der „Equals“ basiert) überschrieben haben, schalten Sie damit praktisch das Entity Framework-Verhalten der Änderungsnachverfolgung ab. Petar Paar veranschaulicht dieses Problem sehr deutlich in seinem Blogbeitrag unter bit.ly/GJcohQ. Um dieses Problem zu beheben, verwendet EF6 nun eine eigene Equals- und GetHashCode-Logik, um Aufgaben der Änderungsnachverfolgung auszuführen. Dabei wird jedwede benutzerdefinierte Equals- und GetHashCode-Logik ignoriert, die Sie möglicherweise geschrieben haben. Sie können jedoch weiterhin eigene benutzerdefinierte Methoden in Ihrer Domänenlogik explizit aufrufen. Auf diese Weise harmonieren die beiden Ansätze miteinander.

Wenn Ihr Schwerpunkt auf Diagrammen und Aggregaten liegt, möchten Sie möglicherweise Typen in anderen Typen schachteln. Allerdings konnte der Code First-Modell-Generator keine geschachtelten Typen erkennen, um Entitäten oder komplexe Typen in einem Modell zu erstellen. Abbildung 3 zeigt ein Beispiel für den geschachtelten Typ: die Adresse. Ich verwende die Adresse nur im Casino-Typ, daher habe ich sie in der Casino-Klasse geschachtelt. Ich habe die Adresse auch als Domain-Driven Design(DDD)-Wertobjekt erstellt, da keine eigene Identität benötigt wird. (In der Rubrik „Datenpunkte“ vom Oktober 2013 mit dem Titel „Codierung für Domain-Driven Design: Tipps für Entwickler mit Datenschwerpunkt, Teil 3“ unter msdn.microsoft.com/magazine/dn451438 finden Sie weitere Informationen über DDD-Wertobjekte.)

Abbildung 3: Meine Casino-Klasse mit geschachteltem Adresstyp

public class Casino()
{
  //...other Casino properties & logic
  public Address PhysicalAddress { get; set; }
  public Address MailingAddress { get; set; }
  public class Address:ValueObject<Address>
  {
    protected Address(){    }
    public Address(string streetOrPoBox, string city,
                   string state,string postalCode)
    { City = city;
      State = state;
      PostalCode = postalCode;
      StreetOrPoBox = streetOrPoBox; }
    public string StreetOrPoBox { get; private set; }
    public string City { get; private set; }
    public string State { get; private set; }
    public string PostalCode { get; private set; }
  }
}

Ich habe die Entity Framework Power Tools verwendet, um eine visuelle Darstellung des Modells zu erhalten. In Abbildung 4 zeige ich Ihnen die daraus folgende Casino-Entity gerendert mit EF5 und EF6. EF5 hat den geschachtelten Typ nicht erkannt und somit weder die Adresse noch abhängige Eigenschaften („PhysicalAddress“ und „MailingAddress“) in das Modell eingebunden. Aber von EF6 wurde der geschachtelte Typ erkannt, und wie Sie sehen, sind die Adressfelder im Modell abgebildet.

Unlike EF5, EF6 Sees the Nested Address Type and Includes the Dependent Properties
Abbildung 4: Im Gegensatz zu EF5 erkennt EF6 den geschachtelten Adresstyp und bindet die abhängigen Eigenschaften ein

Dieselben im Hintergrund vorgenommenen Änderungen, die für eine Aktivierung der geschachtelten Typen sorgen, beheben auch ein weiteres Problem. Und zwar das Problem, das durch das Vorhandensein mehrerer Typen mit gleichen Namen unter unterschiedlichen Namespaces im gleichen Projekt entsteht. Wenn zuvor die EDMX-Metadaten von Entity Framework gelesen wurden und EF nach dem passenden Typ in der Assembly gesucht hat, wurden Namespaces nicht berücksichtigt.

Dies hat in dem gängigen Szenario mit einbezogenen EF-fremden Repräsentationen von Entitäten im gleichen Projekt wie das Modell, in dem die Entitäten enthalten waren, zu Problemen geführt. Also werde ich diese codegenerierte PokerTable-Klasse meines Modells verwenden:

namespace CasinoEntities
{
  public partial class PokerTable
  {
    public int Id { get; set; }
    public string Description { get; set; }
    public string SerialNo { get; set; }
  }
}

Zudem werde ich diese DTO-Klasse verwenden, die zwar nicht Teil meines Modells ist, sich aber im gleichen Projekt befindet:

namespace Casino.DataTransferObjects
{
  public class PokerTable
  {
    public int Id { get; set; }
    public string Description { get; set; }
    public string SerialNo { get; set; }
  }
}

Wenn das Projekt auf EF5 abzielt, wird beim Erstellen des Projekts folgende Fehlermeldung angezeigt:

Das Mapping des CLR-Typs zum EDM-Typ ist mehrdeutig, da mehrere CLR-Typen mit dem EDM-Typ 'PokerTable' übereinstimmen. Zuvor gefundener CLR-Typ 'MyDtos.PokerTable', neu gefundener CLR-Typ 'EDMXModel.DTOs.PokerTable'.

Dieser Fehler wird zur Entwurfszeit mit einer EDMX angezeigt. Hätten Sie dasselbe Szenario – zwei übereinstimmende Klassen mit gleichem Namen, aber unterschiedlichen Namespaces, eine der Klassen im Modell – mit Code First, würde das Problem zur Laufzeit auftreten, wenn der Modell-Generator mit der Interpretation des Code First-Modells beginnt.

Dieses Problem sorgt seit Langem für Verärgerung. Ich habe diese Fehlermeldung gelesen und meinen Computer gefragt: „Siehst du die unterschiedlichen Namespaces nicht? Hallo?“ Ich habe auch eine ganze Sammlung mit E-Mails von Freunden, Kunden und anderen Entwicklern, die sich mit diesem Problem konfrontiert sahen.

EF6 erkennt nun die Namespaces und lässt dieses Szenario zu. Weitere Informationen über die Interna, die diese beiden Änderungen ermöglichten, erhalten Sie im Blogbeitrag von Arthur Vickers unter bit.ly/Wi1rZA.

Kontextkonfiguration in Code verschieben. Es gibt einige Einstellungen, die Sie auf die Dateien „app.config“ oder „web.config“ anwenden können, um die Funktionsweise Ihres Kontexts festzulegen. Beispielsweise können Sie eine Datenbankinitialisierung oder -migration oder einen standardmäßigen Datenbankanbieter angeben. Meine Methode des Hinzufügens hat häufig aus Kopieren und Einfügen bestanden, da sich die Einstellungen nur schwer merken lassen und zahlreiche Zeichenfolgen umfassen. Mit EF6 lassen sich nun viele Kontextkonfigurationen im Code mit der DbConfiguration-Klasse deklarieren. Ich habe damit in der Rubrik „Datenpunkte“ vom März 2013 experimentiert, traf aber auf einen Fehler, der von einer race-Bedingung verursacht wurde. Ich habe diesen gemeldet, und er wurde behoben. Also versuche ich es nun noch einmal mit „DbConfigurations“ (auch bezeichnet als codebasierte Konfiguration). Beachten Sie, dass der Spielraum für Verwirrung groß ist, wenn es um Konfigurationszuordnungen für Code First versus DbConfiguration-Einstellungen versus „DbMigrationConfigurations“ im Hinblick auf die zu definierende Funktionsweise der Datenbankmigration bei Modelländerungen geht. „DbConfiguration“ richtet sich an DbContext-Einstellungen.

„DbConfiguration“ hängt von einem anderen niedrigstufigen Super-Ninja-Feature von EF6 ab: Unterstützung von Abhängigkeitsauflösungen, dies entspricht dem IDependencyResolver-Objekt, das in ASP.NET MVC und der Web-API verwendet wird. Mit der Abhängigkeitsauflösung können Sie das Service-Locator-Muster mit dem IoC(Inversion of Control)-Muster in Ihrem Code verwenden, und EF6 kann aus einer Hierarchie verfügbarer Objekte auswählen, die eine gemeinsame Schnittstelle implementieren. In diesem Fall lautet die Stammschnittstelle „IDbDependencyResolver“. EF6 enthält eine Reihe von DbConfigurations-Objekten, mit denen EF Kontexteinstellungen erkennt und Rangfolgen festlegt, und mithilfe der Abhängigkeitsauflösung lassen sich neue Features zu Entity Framework hinzufügen. Ich werde Ihnen einige dieser Konfigurationen zeigen. Für weitere Informationen über „IDbDependencyResolver“ verweise ich Sie auf die Featurespezifikation unter bit.ly/QKtvCr.

Sie können „DbConfiguration“ nutzen, um sowohl bekannte Kontextregeln als auch für EF6 neue Einstellungen anzugeben. Abbildung 5 zeigt eine beispielhafte Konfigurationsklasse, die eine Vielzahl von verschiedenen Einstellungen umfasst, die EF anstelle des Standardverhaltens verwendet. Beachten Sie, dass die Einstellungen im Klassenkonstruktor platziert werden.

Abbildung 5: Beispielhafte Konfigurationsklasse

public class CustomDbConfiguration : DbConfiguration
{
  public CustomDbConfiguration()
  {
    SetDefaultConnectionFactory(new LocalDbConnectionFactory("v11.0"));
    SetDatabaseInitializer
      (new MigrateDatabaseToLatestVersion<CasinoSlotsModel, Configuration>());
    //SetDatabaseInitializer(new MyInitializer());
    SetExecutionStrategy("System.Data.SqlClient", 
      () => new SqlAzureExecutionStrategy());
    AddInterceptor(new NLogEfCommandInterceptor());
    SetPluralizationService(new CustomPluralizationService());
  }
}

„SetDefaultConnectionFactory“ ersetzt das DefaultConnectionFactory-Tag, das Sie möglicherweise bereits in Ihrer Konfigurationsdatei im entityframework-Abschnitt verwenden. „SetDatabaseInitializer“ ersetzt das Spezifizieren eines Initialisierers oder einer Migrationskonfiguration in Ihrer Konfigurationsdatei oder beim Anwendungsstart. Ich zeige Ihnen zwei Beispiele, von denen eines auskommentiert ist. Mit „SetExecutionStrategy“ geben Sie die Aktionen an, die im Falle einer Verbindungsunterbrechung mitten in den EF-Abfragen oder anderen Ausführungsbefehlen ausgeführt werden sollen. „SetPluralizationService“ macht ein weiteres neues Feature von EF6 verfügbar: die Möglichkeit, benutzerdefinierte Pluralisierungen zu erstellen. Darauf werde ich später noch eingehen.

Es gibt viele andere Wege, um den Kontext mit diesen integrierten Abhängigkeitsauflösungen zu beeinflussen. Im MSDN-Dokument „IDbDependencyResolver-Dienste“ (bit.ly/13Aojso) werden alle Auflösungen aufgeführt, die mit „DbConfiguration“ verfügbar sind. Die Abhängigkeitsauflösung wird ebenfalls verwendet, um Entwickler des Anbieters bei der Behebung bestimmter Probleme zu unterstützen, beispielsweise, wenn Regeln und Logik in die Interaktionsweise von Kontext und Anbieter eingebunden werden müssen.

Abfrage- und Befehlsunterbrechung. Ich habe vergessen, die Verwendung von „AddInterceptor“ durch „CustomDbConfiguration“ zu erwähnen. Mit „DbConfiguration“ können Sie mehr machen, als nur „IDbDependencyResolvers“ hinzuzufügen. Ein weiteres neues Feature von EF6 ist die Möglichkeit, Abfragen und Befehle zu unterbrechen. Bei der Unterbrechung von Abfragen und Befehlen haben Sie nun Zugriff auf das generierte SQL, das an die Datenbank gesendet werden soll, und auf die Ergebnisse, die von diesen Befehlen zurückgegeben werden. Mithilfe dieser Informationen lassen sich die SQL-Befehle protokollieren oder sogar ändern; und EF kann angewiesen werden, die aktualisierten Befehle zu verwenden. Obwohl es mir Spaß gemacht hat, dieses Feature auszuprobieren, spare ich hier Platz und verweise Sie auf Teil 3 von Arthur Vickers dreiteiliger Blogreihe zu diesem Thema: „SQL-Protokollierung mit EF6 – Teil 3: Unterbrechungsbausteine“ (bit.ly/19om5du), in diesem finden Sie Links zu Teil 1 („Einfaches Protokollieren“) und Teil 2 („Ändern von Inhalt/Formatierung“).

Anpassen der EF-Pluralisierung. Ich werde gleich über die Möglichkeit sprechen, die EF-Pluralisierung anzupassen. Doch zuvor möchte ich sichergehen, dass sie deren Standardverhalten kennen. EF nutzt den internen Pluralisierungsdienst für drei Aufgaben:

  1. In Database First stellt der Dienst sicher, dass Entitäten über einen in den Singular gesetzten Namen verfügen. Falls Ihre Datenbanktabelle „Menschen“ heißt, wird daraus im Modell „Person“.
  2. Im EF Designer für Database First oder Model First werden damit pluralisierte EntitySet-Namen (Basis von „DbSets“) auf Grundlage des Entity-Namens erstellt. Beispielsweise stellt der Dienst sicher, dass der EntitySet-Name der Entität „Person“ dem Wert „Menschen“ entspricht. Wenn Sie das Modell mit Database First erstellt haben, wird einfach der Tabellenname nicht verwendet.
  3. In Code First erfolgt eine explizite Benennung von „DBSets“, und EF verwendet den Dienst, um Tabellennamen abzuleiten. Wenn Sie mit einer Klasse mit der Bezeichnung „Person“ beginnen, wird gemäß Konvention davon ausgegangen, dass die Datenbanktabelle den Namen „Menschen“ trägt.

Der Dienst funktioniert nicht wie ein Rechtschreiblexikon, in dem Sie eine Textliste mit benutzerdefinierten Schreibweisen vorfinden. Stattdessen werden interne Regeln angewendet. Neben gelegentlichen Unregelmäßigkeiten – ich hatte zu Beginn viel Spaß mit dem Entity-Namen Rhinozeros – besteht das größte Problem mit dem Dienst darin, dass die Regeln auf Englisch basieren.

Für EF6 erstellte Zorrilla die IPluralizationService-Schnittstelle, sodass Sie eigene Logik hinzufügen können. Sobald Sie einen benutzerdefinierten Dienst erstellt haben, können Sie diesen wie zuvor gezeigt mithilfe von „DbConfiguration“ implementieren.

Zurzeit ist diese Anpassung nur mit dem dritten Punkt der obigen Liste möglich, nämlich wenn Code First die Tabellennamen ableitet. Seit der ersten EF6-Version kann die Anpassung nicht auf den Designer angewendet werden.

Es gibt zwei Möglichkeiten zur Nutzung des Diensts. Sie können mit einem Basisdienst – entweder dem EnglishPluralizationService-Dienst in EF oder einem anderen bereits erstellten Dienst – beginnen und dann die Methode „Singularize“ bzw. „Pluralize“ überschreiben, um eigene Regeln hinzuzufügen. Zudem können Sie ein Wortpaar in einer CustomPluralizationEntry-Klasse angeben und diese dann an einen vorhandenen Dienst anfügen. Zorrilla veranschaulicht „CustomPluralizationEntry“ in seinem Blogbeitrag (bit.ly/161JrD6).

In einer kommenden Ausgabe der Rubrik „Datenpunkte“ werde ich im Rahmen eines Beispiels Regeln (nicht nur Wortpaare) für die Pluralisierung hinzufügen und die Auswirkung auf Code First-Datenbankzuordnungen veranschaulichen.

Weitere Funktionen von Code First

Es gibt einige neue Features für Code First, die ich bisher noch nicht erwähnt habe – dies gilt besonders für Code First-Migrationen. Aus Platzgründen werde ich sie hier kurz vorstellen, eine ausführlichere Betrachtung folgt dann in der Rubrik „Datenpunkte“ im Januar 2014:

  • Migrationsskripts können erstellt werden, die dann prüfen, welche Skripts bereits ausgeführt wurden, sodass eine Datenbank von jedem beliebigen Migrationspunkt aus bearbeitbar ist.
  • Durch eine bessere Steuerung der Migrations_History-Tabelle lassen sich unterschiedliche Datenbankanbieter berücksichtigen.
  • Für die Datenbankzuordnung kann ein Standardschema angegeben werden, anstatt „dbo“ als Standardwert zu nutzen.
  • Migrationen können verschiedene „DbContexts“ behandeln, die an dieselbe Datenbank gerichtet sind.
  • Mit „ModelBuilder“ lassen sich mehrere „EntityTypeConfigurations“ in einem Schritt hinzufügen, anstatt dafür jeweils eine Codezeile zu verwenden.

Werden Sie zum Ninja!

Meiner Ansicht nach ist das wichtigste Argument für EF6, dass damit zu den in EF bereits vorhandenen Features noch weitere großartige dazukommen. Wenn Sie Projekte von EF5 nach EF6 verschieben, sollten Sie wissen, dass einige Namespaceänderungen vorliegen. Für diesen Wechsel bietet das Team eine ausführliche Anleitung unter bit.ly/17eCB4U. Ansonsten sollten Sie bei der Nutzung von EF6 – der aktuellen stabilen Version von Entity Framework, die über NuGet bereitgestellt wird – keinerlei Probleme haben. Auch wenn Sie keine sofortige Nutzung der Ninja-Features anstreben, werden Sie dennoch von der besseren Leistung (wie zuvor in diesem Artikel beschrieben) profitieren. Ich freue mich jedoch über diese Ninja-Features und bin den Entwicklern aus der Community für ihre Beiträge zu EF6 sehr dankbar.

EF wird sich weiterentwickeln. Bei der ersten EF6-Version gab es eine zeitliche Übereinstimmung mit der Veröffentlichung von Visual Studio 2013, aber EF 6.1 und weitere Versionen sind bereits in Arbeit. Sie können den Entwicklungsfortschritt auf der CodePlex-Website mitverfolgen.

Julie Lerman ist Microsoft MVP, .NET-Mentor und Unternehmensberaterin und lebt in den Bergen von Vermont. Sie hält weltweit in Benutzergruppen und bei Konferenzen Vorträge zum Thema „Datenzugriff“ und zu anderen Microsoft .NET-Themen. Julie Lerman führt unter thedatafarm.com/blog einen Blog. Sie ist die Autorin von „Programming Entity Framework“ (2010) sowie der Ausgaben „Code First“ (2011) und „DbContext“ (2012). Alle Ausgaben sind im Verlag O’Reilly Media erschienen. Folgen Sie ihr auf Twitter unter twitter.com/julielerman, und besuchen Sie ihre Pluralsight-Kurse unter juliel.me/PS-Videos.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Rowan Miller (Microsoft)