Data Points

Spielen mit der EF6-Alphaversion

Julie Lerman

 

Julie LermanEs gibt doch nichts Schöneres, als ein glänzendes neues Spielzeug zum Spielen zu haben. So ging es mir mit Entity Framework 6 (EF6). Auch wenn es möglich ist, die nächtlichen Builds während der Entwicklung herunterzuladen, konnte ich es nicht erwarten, die erste verpackte Alphaversion (herausgegeben am 30. Oktober 2012) in meinen Händen zu halten und damit zu spielen.

Falls Sie sich fragen, was es mit den nächtlichen Builds auf sich hat, haben Sie vielleicht die Neuigkeit verpasst, dass Entity Framework nach Version EF5 in ein Open-Source-Projekt umgewandelt wurde. Nachfolgende Versionen werden offen (und gemeinsam) unter entityframework.codeplex.com entwickelt. Vor Kurzem verfasste ich einen Blogbeitrag „Zurechtfinden auf der CodePlex-Website für Open Source-Entity Framework“ (bit.ly/W9eqZS). Ich empfehle Ihnen, diesen Beitrag zu lesen, bevor Sie zu den CodePlex-Seiten wechseln.

Die neue Version trägt entscheidend dazu bei, EF flexibler und besser erweiterbar zu gestalten. Meiner Meinung nach sind die drei wichtigsten Features in Version 6 von EF folgende:

  1. Support für gespeicherte Prozeduren und Funktionen für Code First
  2. Support für das Async/Await-Muster in .NET 4.5
  3. Die zentralen Entity Framework-APIs im aktuellen Microsoft .NET Framework

Der letzte Punkt ermöglicht zum einen Support für Enumerationen und den räumlichen Typ für Apps für .NET Framework 4. Zum anderen profitiert das gesamte EF davon, dass EF jetzt ein Open-Source-Projekt ist.

Es stehen noch viele weitere wichtige Features vor der Tür, auch wenn sie vielleicht nicht die gleiche Attraktivität wie die drei aufgeführten Features genießen, wie zum Beispiel:

  • Die benutzerdefinierten Code First-Konventionen, die vor der Veröffentlichung von EF4.1 abgerufen wurden, befinden sich jetzt in EF6 und enthalten sehr viele unterschiedliche Implementierungsmöglichkeiten.
  • Code First-Migrationen unterstützen mehrere Datenbankschemas.
  • Sie können Entity Framework-Konfigurationen im Code definieren, anstatt sie in einer web.config- oder app.config-Datei festzulegen (was kompliziert sein kann).
  • Die codebasierte Konfiguration wird durch den neuen Support für die Erweiterbarkeit mit Abhängigkeitskonfliktlösern möglich.
  • Sie können anpassen, wie Code First die _Migrations­History-Tabelle erstellt, sodass sie unterschiedlichen Datenbankanbietern besser zugänglich ist.
  • Die EF Power Tools werden verbessert und dem EF Designer in Visual Studio zugefügt. Eine Verbesserung bietet einen angenehmeren Pfad zum Wählen eines Modellworkflows, einschließlich Code First.

Abrufen der Alphaversion von EF6

Eingefleischte Entwickler interessieren sich vielleicht für das Herunterladen der nächtlichen Builds. Wenn Sie die veröffentlichten Pakete bevorzugen, erfolgt die Installation am einfachsten mit dem NuGet-Paket. Verwenden Sie den NuGet-Paket-Manager, und wählen Sie die Option „Include Prerelease“ aus, um das EF6-Paket abzurufen. Wenn Sie die Installation von der Paket-Manager-Konsole vornehmen, müssen Sie an das Ende des Paketinstallationsbefehls „-prerelease“ hinzufügen.

Beachten Sie, dass die Version vom 10. Dezember 2012, um die es hier geht (mit der Dateiversion 6.0.11025.0 und der Produktversion 6.0.0-alpha2-11210) keinen Support für gespeicherte Prozeduren oder Funktionen oder die Toolkonsolidierung enthält. Da es sich außerdem um eine sehr frühe Alphaversion handelt, gehe ich davon aus, dass sich einige Details an den Features in neuen Versionen ändern werden. Auch wenn das Gesamtkonzept gleich bleiben wird, werden Teile der Syntax oder andere Einzelheiten anhand des Feedbacks aus der Community sicherlich weiterentwickelt. Im Rahmen meiner Arbeit an diesem Artikel konnte ich auch Feedback geben.

.NET 4.5-Async in EF6

Viele Entwickler schrecken davor zurück, die asynchrone Verarbeitung in .NET Framework mit dem Ziel zu nutzen, beim Warten auf zurückzugebende Daten eine Blockierung zu vermeiden, insbesondere beim Verwenden von unverbundenen Apps oder Remotedatenbanken. Seit .NET Framework 2.0 gibt es einen Workerprozess im Hintergrund, die Komplexität aber blieb. ADO.NET 2.0 brachte ein paar Vorteile mit Methoden wie „BeginExecuteQuery“ und „EndExecuteQuery“, Entity Framework wurde mit derartigen Methoden jedoch nie beglückt. Eine der wichtigsten Erweiterungen an .NET Framework 4.5 ist das neue asynchrone Muster. Durch die Fähigkeit, auf Ergebnisse von Methoden zu warten, die als asynchron definiert wurden, wurde die asynchrone Verarbeitung erheblich vereinfacht.

In EF6 wurde eine Fülle an Methoden hinzugefügt, die das asynchrone Muster in .NET 4.5 unterstützen. Nach den Richtlinien für das neue Muster wurde an den Namen aller neuen Methoden „Async“ angehängt, wie zum Beispiel „SaveChangesAsync“, „FindAsync“ und „Execute­SqlCommandAsync“. Für LINQ to Entities enthält ein neuer Namespace mit dem Namen „System.Data.Entity.IQueryableExtensions“ Async-Versionen der vielen LINQ-Methoden, einschließlich „ToListAsync“, „First­Or­DefaultAsync“, „MaxAsync“ und „SumAsync“. Jetzt steht „LoadAsync“ zur Verfügung, um explizit Daten von Entitäten zu laden, die von „DbContext“ verwaltet werden.

Im Folgenden beschreibe ich ein kleines Experiment mit diesem Feature: zuerst ohne die asynchronen Methoden und danach mit ihnen. Ich empfehle Ihnen wärmstens, sich über das neue asynchrone Muster zu informieren. „Asynchrone Programmierung mit Async und Await (C# und Visual Basic)“ ist unter bit.ly/U8FzhP verfügbar und bietet einen guten Ausgangspunkt.

Mein Beispiel enthält eine Casino-Klasse, die eine Bewertung für das Kasino enthält. Ich habe eine Repositorymethode erstellt, die ein bestimmtes Kasino sucht, seine Bewertung mithilfe der UpdateRating-Methode erhöht (was für diese Erklärung irrelevant ist und deshalb nicht aufgeführt wird) und die Änderung zurück in die Datenbank speichert.

public void IncrementCasinoRating(int id)
{
  using (var context = new CasinoSlotsModel())
  {
    var casino =  context.Casinos.Find(id);
    UpdateRating(casino);
    context.SaveChanges();
  }
}

An zwei Punkten der Methode kann ein Thread blockiert werden. Den ersten Punkt bildet das Aufrufen von „Find“. Hierdurch werden die Kontextsuche im Cache im Arbeitsspeicher nach dem angeforderten Kasino und eine Abfrage der Datenbank bei erfolgloser Suche verursacht. Der zweite Punkt ist die Abfrage des Kontexts zum Speichern der geänderten Daten in die Datenbank zurück.

Ich habe meinen Benutzeroberflächencode lediglich zur Demonstration des relevanten Verhaltens strukturiert. Die Benutzeroberfläche enthält eine Methode, die „IncrementCasinoRating“ des Repositorys abruft und nach Abschluss in die Konsole eine Benachrichtigung schreibt:

private static void UI_RequestIncrement (SimpleRepository repo)
{
  repo.IncrementCasinoRating(1);
  Console.WriteLine("Synchronous Finish ");
}

In einer anderen Methode löse ich den Test durch den Aufruf von „UI_ Increment­CasinoRating“ aus und verfolge das mit einer anderen Benachrichtigung:

UI_RequestIncrement (repo);
Console.WriteLine(" After sync call");

Beim Ausführen wird Folgendes in der Konsolenausgabe angezeigt:

Synchronous Finish
After sync call

Das ist darauf zurückzuführen, dass beim Warten darauf, dass die einzelnen Schritte in „IncrementCasinoRating“ abgeschlossen werden, alles angehalten wurde: das Suchen des Kasinos, das Aktualisieren der Bewertung und das Speichern in die Datenbank.

Jetzt ändere ich die Repositorymethode, sodass sie die neue FindAsync- und die SaveChangesAsync-Methode verwendet. Gemäß dem asynchronen Muster muss ich auch die Methode asynchron machen, und zwar folgendermaßen:

  • Durch Hinzufügen des async-Schlüsselworts an die Signatur.
  • Durch Anhängen von „Async“ an den Methodennamen.
  • Durch die Rückgabe von Task: Wenn die Methode Ergebnisse zurückgibt, geben Sie „Task<resulttype>“ zurück.

Ich rufe in der Methode die neuen Async-Methoden („FindAsync“ und „SaveChangesAsync“) wie vorgeschrieben auf und verwende dafür das await-Schlüsselwort:

public async Task IncrementCasinoRatingAsync(int id)
{
  using (var context = new CasinoSlotsModel())
  {
    var casino=await context.Casinos.FindAsync(id);
    // Rest is delayed until await has received results
    UpdateRating(casino);
    await context.SaveChangesAsync();
    // Method completion is delayed until await has received results
  }
}

Da die Methode mit „async“ gekennzeichnet ist, gibt sie die Kontrolle an den aufrufenden Prozess zurück, sobald das erste „await“ gefunden wurde. Ich muss jedoch die aufrufende Methode ändern. Es gibt beim Erstellen des Verhaltens für das asynchrone Muster einen Wasserfallpfad. Die Methode richtet daher nicht nur einen speziellen Aufruf an meine neue asynchrone Methode, sie muss selbst auch asynchron sein, weil sie von einem weiteren Prozess aufgerufen wird:

private static async void UI_RequestIncrementAsync(
  SimpleRepository repo)
{
  await repo.IncrementCasinoRatingAsync(1);
  // Rest is delayed until await has received results
  Console.WriteLine(" Asynchronous Finish ");
}

Beachten Sie, dass ich die Repositorymethode mithilfe von „await“ aufrufe. Dadurch erfährt der Aufrufer, dass es sich um eine asynchrone Methode handelt. Sobald „await“ erreicht wird, wird die Kontrolle an den Aufrufer zurückgegeben. Ich habe auch den Startcode geändert:

UI_RequestIncrementAsync(repo);
  Console.WriteLine(" After asynchronous call");

Wenn ich diesen Code ausführe, gibt die UI_RequestIncrementAsync-Methode die Kontrolle an den Aufrufer zurück, da sie auch die Repositorymethode aufruft. Das bedeutet, dass ich sofort zur nächsten Zeile des Startcodes wechsle, die „After asynchronous call“ anzeigt. Wenn die Repositorymethode das Speichern in die Datenbank abgeschlossen hat, gibt sie der Methode einen Task zurück, die sie aufgerufen hat, „UI_RequestIncrementAsync“. Diese führt anschließend den Rest des Codes aus und zeigt folgende Nachricht in der Konsole an:

After asynchronous call
Asynchronous Finish

Meine Benutzeroberfläche konnte also beendet werden, ohne dass sie auf den Abschluss der Arbeit von EF warten musste. Wenn die Repositorymethode Ergebnisse zurückgegeben hätte, wären sie im Task angezeigt worden, als sie bereit waren.

Dank dieser kleinen Aufgabe konnte ich die neuen asynchronen Methoden in Aktion beobachten. Ob Sie clientseitige oder getrennte Anwendungen schreiben, die von asynchroner Verarbeitung abhängen, es ist ein großer Vorteil, das EF6 jetzt das neue asynchrone Muster so unkompliziert unterstützt.

Benutzerdefinierte Konventionen

Code First besitzt einen Satz integrierter Konventionen, die das Standardverhalten steuern, wenn ein Modell zusammen mit Datenbankzuordnungen aus Ihren Klassen erstellt wird. Sie können diese Konventionen mit expliziten Konfigurationen unter Verwendung von „DataAnnotations“ oder der Fluent-API überschreiben. In den frühen Betaversionen von Code First konnten Sie auch Ihre eigenen Konventionen definieren, zum Beispiel eine Konvention, die festlegt, dass allen Zeichenfolgen Datenbankfelder mit einer maximalen Länge von „50“ zugeordnet werden. Leider gelang es dem Team nicht, dieses Feature zufriedenstellend fertig zu stellen, ohne die Veröffentlichung von Code First aufzuhalten, so konnte es in die endgültige Version nicht aufgenommen werden. In EF6 taucht es wieder auf.

Es gibt mehrere Möglichkeiten, eigene Konventionen zu definieren.

Die Verwendung einer kompakten Konvention bildet die einfachste Methode. So können Sie Konventionen reibungslos in die OnModelCreating-Überladung von „DbContext“ angeben. Kompakte Konventionen werden auf Ihre Klassen angewendet und beschränken sich auf das Konfigurieren von Eigenschaften, die eine direkte Korrelation in der Datenbank haben, wie zum Beispiel die Länge von „MaxLength“. Im Folgenden ist ein Beispiel für eine Klasse aufgeführt, die keine besonderen Konfigurationen hat. Standardmäßig werden daher ihre beiden Zeichenfolgenfelder nvarchar(max)-Datentypen in einer SQL Server-Datenbank zugeordnet:

public class Hotel
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
  }

Ich habe im Modell eine kompakte Konvention hinzugefügt, nach der die API die Eigenschaften aller Entitäten, die sie verarbeitet, überprüfen und den Wert von „MaxLength“ für die Zeichenfolgen auf „50“ festlegen soll:

modelBuilder.Properties<string>()
  .Configure(p => p.HasColumnType("nvarchar"));

Abbildung 1 können Sie entnehmen, dass die Felder „Name“ und „Description“ tatsächlich dank Code First eine maximale Länge von „50“ aufweisen.

A Custom Convention Made the Max Length of These nvarchars 50
Abbildung 1: Benutzerdefinierte Konvention legt maximale Länge dieser nvarchar-Typen auf „50“ fest

Sie können auch eine Konvention definieren, indem Sie eine vorhandene Schnittstelle implementieren, wie die Konventionsschnittstelle zum Behandeln von DateTime-Eigenschaften: die DateTimePropertyConfiguration-Klasse im System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive-Namespace. Abbildung 2 zeigt ein Beispiel, in dem ich die Zuordnung der DateTime-Eigenschaften zum SQL Server-Datentyp anstatt zum standardmäßigen datetime-Datentyp erzwungen habe. Beachten Sie, dass dieses Beispiel den Empfehlungen von Microsoft entspricht. Ich würde meine Konfiguration nicht anwenden, wenn das Attribut (in diesem Fall „ColumnType“) bereits konfiguriert wäre.

Abbildung 2: Zuordnen der DateTime-Eigenschaften zum SQL Server-Datentyp

public  class DateTimeColumnTypeConvention :
  IConfigurationConvention<PropertyInfo, DateTimePropertyConfiguration>
  {
    public void Apply(
      PropertyInfo propertyInfo,
      Func<DateTimePropertyConfiguration> configuration)
    {
      // If ColumnType hasn't been configured ...
      if (configuration().ColumnType == null)
      {
        configuration().ColumnType = "date";
      }
    }
  }

Für Konventionen gilt eine bestimmte Hackordnung. Deshalb müssen Sie sicherstellen, dass der Spaltentyp nicht bereits vor dem Anwenden des neuen „ColumnType“ konfiguriert wurde.

Der Ersteller des Modells muss wissen, wie er die neue Konvention finden kann. Gehen Sie hierzu wieder in der OnModelCreating-Überladungsmethode so vor:

modelBuilder.Conventions.Add(new DateTimeColumnTypeConvention());

Es gibt zwei weitere Methoden, Konventionen anzupassen. Eine Methode ermöglicht Ihnen, benutzerdefinierte Attribute zur Verwendung in Ihren Klassen so einfach wie „DataAnnotations“ zu erstellen. Die andere Methode ist etwas detaillierter: Anstatt eine Konvention zu erstellen, die davon abhängt, welche Informationen „ModelBuilder“ von Ihren Klassen erhält, können Sie mit dieser Methode die Metadaten direkt beeinflussen. Beispiele für alle vier benutzerdefinierten Konventionsarten finden Sie in der Dokumentation im MSDN-Datenentwicklercenter unter „Benutzerdefinierte Konventionen für First Code“ (msdn.microsoft.com/data/jj819164). Im Verlauf der Weiterentwicklung von EF6 wird diesem Dokument entweder ein Link auf eine aktuellere Version hinzugefügt, oder das Dokument wird an die neueste Version angepasst.

Support für mehrere Schemas für Migrationen

EF6 ermöglicht Code First-Migrationen die Behandlung mehrerer Schemas in Datenbanken. Weitere Informationen zu diesem Feature erhalten Sie im detaillierten Blogbeitrag, den ich kurz nach der Veröffentlichung der Alphaversion geschrieben habe: „Einzelheiten zu mehrinstanzenfähigen Migrationen mit der Alphaversion von EF6“ (bit.ly/Rrz1MD). Beachten Sie, dass sich der Name des Artikels von „Mehrinstanzenfähige Migrationen“ in „Mehrere Kontexte pro Datenbank“ geändert hat.

Codebasierte Konfigurationen

Sie können datenbankrelevante Konfigurationen für Entity Framework in Anwendungskonfigurationsdateien („app.config“ und „web.config“) bereits spezifizieren, wodurch Sie nicht mehr die Konfigurationen beim Anwendungsstart oder im Konstruktor des Kontexts angeben müssen. In EF6 ist es jetzt möglich, eine Klasse zu erstellen, die von einer neuen DbConfiguration-Klasse erbt, in der Sie Details, wie den standardmäßigen Datenbankanbieter für Code First, die Datenbankinitialisierungsstrategie (zum Beispiel „DropCreateDatabaseIfModelChanges“) und Weiteres angeben können. Sie können die DbConfiguration-Klasse im Projekt Ihres Kontexts oder in einem getrennten Projekt erstellen und dabei mehrere Kontexte zulassen, um von einer einzelnen Konfigurationsklasse zu profitieren. Die Übersicht über die codebasierte Konfiguration unter msdn.microsoft.com/data/jj680699 enthält Beispiele der unterschiedlichen Optionen.

Kernfeatures von EF jetzt in EF6 und dazu Open Source

Während die Code First- und DbContext-APIs immer vom .NET-Veröffentlichungszyklus getrennt waren, wurden die zentralen Features von EF jetzt in .NET Framework eingebettet. Zu den Kernfunktionen zählen die ObjectContext-API, Abfrage- und Änderungsnachverfolgungsvorgänge, der Entity­Client-Anbieter usw. Aus diesem Grund konnte der Support für Enumerationen und räumliche Daten erst in .NET Framework 4.5 veröffentlicht werden – diese Änderungen betrafen das Innerste der Kern-APIs.

In EF6 wurden alle diese Kern-APIs in das Open-Source-Projekt aufgenommen, und sie werden über das NuGet-Paket bereitgestellt. Es ist interessant, die beiden EF5- und EF6-Namespaces nebeneinander zu sehen (siehe Abbildung 3). Wie Sie sehen, enthält EF6 viel mehr Namespaces.

EF6 Has Acquired the Namespaces of the EF Core APIs from the .NET Framework
Abbildung 3: EF6 hat die Namespaces der EF-Kern-APIs aus .NET Framework übernommen

Support für Enumerationen und räumliche Daten für .NET 4-Apps

Wie oben erwähnt, besteht einer der größten Vorteile der Kern-APIs in EF6 darin, dass ein Teil der Abhängigkeit von .NET-Versionen für EF-spezifische Features entfällt. Dies betrifft in erster Linie den in .NET Framework 4.5 zu EF hinzugefügten Support für Enumerationen und räumliche Daten. Vorhandene Apps für .NET Framework 4 mit EF konnten diesen Vorteil mit EF5 nicht nutzen. Jetzt entfallen diese Einschränkungen, weil EF6 den Support für Enumerationen und räumliche Daten enthält und die Features deshalb nicht mehr von .NET Framework abhängig sind. Die vorhandene Dokumentation zu diesen Features kann Ihnen dabei helfen, sie in Apps für .NET Framework 4 zu verwenden.

Unterstützen Sie die Entwicklung von EF

Da Entity Framework jetzt ein Open-Source-Projekt ist, können Entwickler sich und anderen helfen, indem sie sich am Entwurf und an der Entwicklung beteiligen. Sie können Ihre Gedanken, Kommentare und ihr Feedback teilen, sei es durch das Lesen der Spezifikationen, die Teilnahme an den Diskussionen und Problemlösungen, das Spielen mit dem neuesten NuGet-Paket oder das Herunterladen und Diskutieren der nächtlichen Builds. Sie können auch Code beitragen, entweder für eines der auf der Website aufgelisteten Themen, an denen niemand arbeitet, oder für etwas Eigenes, das Sie in EF unbedingt benötigen.

Julie Lerman ist Microsoft MVP, .NET-Mentor und Unternehmensberaterin und lebt in den Bergen von Vermont. Sie hält bei User Groups und Konferenzen in der ganzen Welt Vorträge zum Thema Datenzugriff und anderen Microsoft .NET-Themen. Julie Lerman führt unter thedatafarm.com/blog einen Blog. Sie ist die Verfasserin 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.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Glenn Condron