August 2019

Band 34, Nummer 8

[Data Points]

EF6 plattformübergreifend mit .NET Core 3.0!

Von Julie Lerman

Julie LermanObwohl Entity Framework Core seit einigen Jahren verfügbar ist, gibt es immer noch unzählige Produktions-Apps, die EF6 verwenden. Und selbst bei all der Innovation, die in EF Core steckt, gibt es keinen Grund, funktionierenden, stabilen Produktionscode nach EF Core zu portieren, wenn Sie nicht planen, die EF6-Logik zu ändern und keine Notwendigkeit besteht, die Vorteile der neuen Funktionen oder der verbesserten Leistung von EF Core zu nutzen. Dank seines Open-Source-Charakters wird EF6 immer noch von Microsoft und der Community gepflegt und sogar optimiert. Doch viele Entwickler und Teams, die sich nicht mit der eigentlichen EF6-Funktionalität herumschlagen möchten, möchten ihre Software trotzdem aus .NET nach .NET Core portieren, um die zahlreichen Vorteile von .NET Core zu nutzen, einschließlich der plattformübergreifenden Funktionen und vieler Innovationen.

Es war möglich, EF6-Logik in einer ASP.NET-Web-API zu kapseln und von .NET Core-Anwendungen aus darauf zuzugreifen. Mit dieser Lösung können Sie jedoch nicht von den Funktionen der ASP.NET Core-APIs oder den Neuerungen bei Windows-Desktop-Apps beim Umstieg auf .NET Core 3.0 profitieren. Glücklicherweise können Sie mit den bevorstehenden Releases von EF 6.3 und .NET Core 3.0 „auf beiden Hochzeiten tanzen“, um es salopp auszudrücken. EF6.3 kann nicht nur weiterhin unter .NET Framework ausgeführt werden, sondern funktioniert auch mit .NET Core 3.0. Dies ist möglich, da EF6.3 nicht nur unter .NET 4.0 und .NET 4.5 ausgeführt werden kann, sondern auch übergreifend mit dem Ziel .NET Standard 2.1 kompiliert wird.

In diesem Artikel werde ich mit der neuesten Vorschauversion von EF6.3 und ASP.NET Core 3.0 EF6.3 in einem plattformübergreifenden Szenario ausprobieren und eine neue ASP.NET Core 3.0-API mit EF6.3 erstellen. Auf meinem MacBook! Unter macOS. Mit Visual Studio Code. Abschließend werde ich die API in einem Linux-basierten Docker-Container bereitstellen! Bisher war mit EF6 keine dieser Aufgaben möglich. Sie können EF6.3 weiterhin in .NET Framework-Apps in Visual Studio ab Version 2017 v15.3 verwenden, obwohl Sie für .NET Core 3.0-Apps Vorschauversionen von Visual Studio 2019 benötigen, die .NET Core 3.0 unterstützen.

Die EF6.3-Vorschau weist mit .NET Core 3.0 einige Einschränkungen auf, die bei der Veröffentlichung voraussichtlich behoben sein werden. Zu den aktuellen Einschränkungen gehören:

  • .NET Core 3.0 kann bisher nicht mit EF Designer in Visual Studio verwendet werden.
  • Migrationsbefehle funktionieren noch nicht mit .NET Core 3.0-Projekten.
  • Derzeit ist der einzige funktionierende Anbieter SQL Server.

Möglicherweise finden Sie die Diskussion im Zusammenhang mit dem Ankündigungsblogbeitrag hilfreich (bit.ly/2F9xtDt), aber denken Sie daran, dass viele dieser Probleme gelöst sein sollten, wenn die Veröffentlichung näher rückt.

Selbst mit diesen Einschränkungen liegt mein wirkliches Interesse an einem Proof of Concept, der EF6.3 auf einem Nicht-Windows-Computer zeigt. Daher werde ich ein sehr einfaches Projekt mit dem einfachsten Modell erstellen – gerade genug, um es auf meinem MacBook funktionieren zu sehen. Und da ich unter macOS arbeite und der einzige derzeit verfügbare Datenbankanbieter SQL Server ist, habe ich eine gute Ausrede, SQL Server für Linux in einem Docker-Container zu verwenden, um die Daten persistent zu speichern.

Vorbereiten des Basis-API-Projekts

Die Installation von .NET Core 3.0 und die Erstellung einer ASP.NET Core-API bleiben unverändert. Ich werde diese Schritte kurz für diejenigen unter Ihnen erläutern, die sie noch nicht ausgeführt haben.

Stellen Sie zunächst sicher, dass Sie .NET Core 3.0 auf Ihrem Computer installiert haben. Während ich dies Anfang Juni 2019 schreibe, ist die neueste Version Vorschau 6, veröffentlicht am 12. Juni 2019. Die Installationsseite für .NET Core 3.0 lautet bit.ly/2KoSOxh. Sie müssen das SDK-Paket installieren, das nicht nur das SDK, sondern auch die Runtimes von .NET Core und ASP.NET Core enthält. Sobald das SDK installiert ist, listet der Befehl „dotnet --version“ die neueste Version auf, während „dotnet --list-sdks“ alle Versionen auf Ihrem System anzeigt.

Nun erstellen Sie einen neuen Ordner für Ihr Projekt (ich habe meinen Ordner „coreapi“ genannt) und stellen dann sicher, dass Sie sich in diesem Ordner in der Befehlszeile befinden. Geben Sie dann den CLI-Befehl ein: „dotnet new webapi“. Dadurch wird ein neues ASP.NET Core API-Projekt im Ordner mit C# als Standardsprache und standardmäßig der neuesten Version von .NET Core erstellt, die auf Ihrem Computer installiert ist.

Da ich an meinem MacBook arbeite, gibt es eine Möglichkeit, die plattformübergreifende Unterstützung sofort zu erleben: durch die Verwendung von Visual Studio Code für meine Entwicklungsumgebung. Ich verfüge über eine Tastenkombination, die es mir ermöglicht, Code in der Befehlszeile in meinem Projektordner einzugeben, um VS Code zu starten, wobei dieser Ordner geöffnet ist. Sie können auch einfach VS Code starten und den Projektordner öffnen.

Die wesentlichen Ressourcen des Projekts wurden durch die Vorlage erstellt, einschließlich eines Standardelements ValuesController. Wie Sie in Abbildung 1 sehen können, ist das Ziel der Projektdatei .NET Core 3.0. Es ist mit dieser Version von .NET Core nicht mehr erforderlich, auf eines der ASP.NET Core-Pakete in der CSPROJ-Datei explizit zu verweisen, weshalb ItemGroup leer ist.

Das coreapi-Projekt und Inhalte der CSPROJ-Datei
Abbildung 1: Das coreapi-Projekt und Inhalte der CSPROJ-Datei

Hinzufügen von EF6.3 zum Projekt

Sie können EF6.3 direkt zur CSPROJ-Datei hinzufügen, indem Sie den folgenden Paketverweis in den Abschnitt ItemGroup aufnehmen:

<PackageReference Include="EntityFramework" Version="6.3.0-preview6-19304-03" />

Beachten Sie, dass diese Angabe auf die neueste Vorschauversion verweist, die verfügbar ist, während ich an diesem Artikel arbeite. Besuchen Sie die NuGet-Seite für Entity Framework (bit.ly/2RgTo0l), um die aktuelle Version zu ermitteln. Wenn Sie diesen Artikel lesen, ist EF6.3 möglicherweise bereits vollständig freigegeben!

Ich benötige einige Daten zum Speichern, also habe ich eine kleine Klasse namens „Human“ erstellt:

public class Human {
  public int HumanId { get; set; }
  public string Name { get; set; }
}

Beachten Sie, dass Namespaces und using-Anweisungen im Downloadbeispiel enthalten sind, aber in den Codelisten hier nicht verwendet werden.

Um die Daten persistent zu speichern, habe ich eine DbContext-Klasse namens „HumanContext“ erstellt:

public class HumanContext : DbContext {
  public HumanContext (string connectionString) : base (connectionString)
  {
    Database.SetInitializer<HumanContext> (new HumanInitializer ());
  }
  public DbSet<Human> Humans { get; set; }
  protected override void OnModelCreating (DbModelBuilder modelBuilder
  {
    modelBuilder.Entity<Human> ().ToTable ("Humans");
  }
}

Der Konstruktor erwartet, dass eine Verbindungszeichenfolge übergeben wird.

Um zu verhindern, dass EF bei der Pluralbildung des Worts „Human“ als „Humen“ (!) völlig versagt, habe ich eine Zuordnung verwendet, um zu erzwingen, dass der Tabellenname „Humans“ lautet. Außerdem lege ich im Konstruktor den Datenbankinitialisierer auf einen benutzerdefinierten Initialisierer („HumanInitializer“) fest, der das Seeding der Testdatenbank mit einigen romantischen Menschen ausführt, wenn sich das Modell ändert. Falls Sie Ihre EF6-Lektionen vergessen haben, möchte ich Sie daran erinnern, dass dieser Initialisierer auch die Datenbank erstellt, wenn sie noch nicht vorhanden ist. Dies ist die HumanInitializer-Klasse:

public class HumanInitializer : DropCreateDatabaseIfModelChanges<HumanContext>
{
  protected override void Seed (HumanContext context)
  {
    context.Humans.AddRange (new Human[] {
      new Human { Name = "Juliette" },
      new Human { Name = "Romeo" }
    });
  }
}

Verbinden von ASP.NET Core und EF6

Wenn Sie EF6.3 in ASP.NET Core verwenden, gibt es einige Vorteile von EF Core, die Sie verpassen. Einer davon ist die integrierte Abhängigkeitsinjektion (Dependency Injection, DI). EF Core kann sich mithilfe der Erweiterungsmethode AddDbContext in ASP.NET Core-Dienste einbinden. Auf diese Weise kann ASP.NET Core bei Bedarf Objektinstanzen eines DbContext in Klassen einfügen, die sie benötigen, z.B. in eine Controllerklasse. Die AddDbContext-Methode ist jedoch ohne EF Core nicht verfügbar. Darüber hinaus verfügen die EF Core-Datenbankanbieter (etwa Microsoft.EntityFrameworkCore.SqlServer) über Erweiterungsmethoden, mit denen Sie der AddDbContext-Methode Details zum Anbieter hinzufügen können. SqlServer stellt beispielsweise eine UseSqlServer-Methode zur Verfügung, an die Sie Optionen wie die Verbindungszeichenfolge übergeben können.

Obwohl diese Methoden bei Verwendung von EF6.3 nicht verfügbar sind, können Sie „HumanContext“ trotzdem mit den Abhängigkeitsinjektionsdiensten von ASP.NET Core verbinden und diese Klasse über den Anbieter und die Verbindungszeichenfolge informieren. Sie müssen nur anderen Code verwenden, der an der gleichen Stelle wie der EF Core-Code platziert wird: innerhalb der ConfigureServices-Methode der Startklasse der API.

Mit der Add-Methode von IServiceCollection (zusammen mit Varianten wie AddScoped) können Sie den Diensten beliebige Klassen hinzufügen. Dann können Sie die Anweisungen übergeben, die besagen, welche Aktionen erforderlich sind, wenn dieses Objekt zur Laufzeit benötigt wird.

Dieser Code gibt außerdem an, dass Ausschau gehalten werden soll, wo ein HumanContext-Objekt benötigt wird. Als Reaktion darauf soll ein neuer HumanContext instanziiert werden, an den eine Verbindungszeichenfolge aus der Datei „appsettings.json“ übergeben wird:

services.AddScoped<HumanContext>
                (serviceProvider => new HumanContext (Configuration["Connection"]));

Dieser Code übernimmt die Abhängigkeitsinjektion für HumanContext.

Wie sieht es mit dem Ausgleich des Verlusts der UseSqlServer-Methode zur Angabe des Datenbankanbieters und der Verbindungszeichenfolge aus? In Vorschau 6 von EF6.3 verwendet EF System.Data.SqlClient, wenn kein Anbieter angegeben wird. Da ich SQL Server verwenden werde, ist es daher nicht notwendig, weiteren Code hinzuzufügen. SQL Server ist in diesem Fall der Standardanbieter.

Der letzte Schritt in diesem Beispiel besteht darin, die Verbindungszeichenfolge in „appsettings.json“ einzufügen, damit die Methode Configuration["Connection"] sie lesen kann:

"Connection":"Server=localhost,1601;Database=Humans;User Id=sa;Password=P@ssword1"

Erinnern Sie sich daran, dass ich bereits darauf hingewiesen habe, dass ich SQL Server für Linux in einem Docker-Container als Datenbank verwenden werde, weil ich auf meinem MacBook arbeite. Als ich den Container ausgeführt habe, habe ich 1601 als Port auf meinem Computer angegeben, über den der Datenbankserver bereitgestellt werden soll. Dies ist der Docker-Befehl, mit dem ich den Container ausgeführt habe:

docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=P@ssword1'
  -p 1601:1433 -d --name SQLforEF6  mcr.microsoft.com/mssql/server:2017-latest

Beachten Sie, dass dieser Befehl in einer einzigen Zeile stehen muss. Wenn Sie mehr über die Verwendung von SQL Server in einem Container erfahren möchten, lesen Sie meinen Artikel „On-the-Fly SQL Server with Docker“ aus Juli 2017 unter msdn.com/magazine/mt784660. Ein Unterschied zu diesem Artikel besteht darin, dass ich jetzt auf den neuen Speicherort von Microsofts Docker-Containern in der Microsoft Container Registry verweise, die Angabe „mcr“ in der URL des Images, auf das der Befehl „docker run“ abzielt.

Hinzufügen eines Controllers, der EF6 verwendet

Der Controller, der durch die Vorlage erstellt wurde, stellt eine gute Möglichkeit dar, um zu testen, ob Ihre API funktioniert, aber er erstellt nur Zeichenfolgen und gibt diese zurück. Um die Datenbankinteraktion zu testen, fügen Sie eine neue HumanController-Klasse hinzu, wie in Abbildung 2 dargestellt. Für diesen Proof of Concept benötigen Sie nur eine einzige HttpGet-Methode, um Daten abzurufen. Die Seed-Methode in HumanInitializer übernimmt das Einfügen von Daten.

Abbildung 2: Die HumanController-Klasse

[Route ("api/[controller]")]
[ApiController]
public class HumanController : ControllerBase
{
  HumanContext _context;
  public HumanController (HumanContext context)   {
    _context = context;
  }
  [HttpGet]
  public ActionResult<IEnumerable<Human>> Get ()
  {
   return _context.Humans.ToList ();
  }
}

Beachten Sie, dass EF das erste Mal in einer Anwendungsinstanz versucht, eine Interaktion mit der Datenbank durchzuführen, wenn die Initialisierung ausgelöst wird, die ich im HumanContext-Konstruktor definiert habe. Daher wird die Datenbank erst erstellt und das Seeding ausgeführt, wenn ich versuche, diese Methode auszuführen. Da es sich um eine einfache Demo handelt, mache ich mir keine Sorgen über die Möglichkeit, diese Anwendung auf mehreren Servern auszuführen, was zu einem Konflikt führen könnte, wenn die Server versuchen würden, den Initialisierungs- und Seedingcode gleichzeitig auszuführen. Aber dies ist immer ein Nebeneffekt, den Sie im Hinterkopf behalten sollten, wenn Sie einer Anwendung erlauben, für die Erstellung und das Seeding von Datenbanken verantwortlich zu sein.

Ausführen der API

Nachdem nun alles vorhanden ist, können Sie die API ausführen oder debuggen. Ich beginne normalerweise damit, indem ich den Code über die Befehlszeile mit „dotnet run“ ausführe. Sobald die Ausführung erfolgt, navigiere ich zunächst zum Wertecontroller unter localhost:5000/api/values, um sicherzustellen, dass die API selbst funktioniert. Im Browser sollten „value1“ und „value2“ ausgegeben werden. Dann untersuche ich die von der Datenbank abhängige API unter localhost:5000/api/human. Wenn alles gut geht, wird die API bei ihrer ersten Ausführung etwas Zeit benötigen, um die Humans-Datenbank und die Humans-Tabelle zu erstellen und das Seeding auszuführen. Sobald dies geschehen ist, sollte sie die IDs und Namen der beiden Humans ausgeben, wie in Abbildung 3 dargestellt.

Die Ergebnisse der Get-Methode von coreapi
Abbildung 3: Die Ergebnisse der Get-Methode von coreapi

Anzeigen der Containerdatenbank

Dies ist der Beweis dafür, dass die Datenbank erstellt und das Seeding ausgeführt wurde. Allerdings fühle ich mich immer besser, wenn ich die Datenbank nochmals überprüfe, besonders wenn sie sich in einem containerisierten Server befindet.

Der Aufruf des Befehls „docker ps“ zum Auflisten aktuell ausgeführter Container beweist, dass der Container tatsächlich ausgeführt wird:

➜  ~ docker ps

CONTAINER ID  IMAGE                                        COMMAND                 
4bfc01930095  mcr.microsoft.com/mssql/server:2017-latest   "/opt/mssql/bin/sqls…"
CREATED             STATUS              PORTS                    NAMES
23 hours ago        Up 13 hours         0.0.0.0:1601->1433/tcp   SQLforEF6

Der Docker-Explorer der Docker-Erweiterung von VS Code ist eine weitere Möglichkeit, um nachzuweisen, dass mein SQLforEF6-Container ausgeführt wird.

Und mit Azure Data Studio kann ich eine Verbindung mit der SQL Server-Instanz dieses Containers herstellen, um die Datenbank und ihre Daten zu untersuchen (siehe Abbildung 4). Weitere Informationen zu diesem Tool finden Sie in meinem früheren Artikel zu Azure Data Studio unter msdn.com/magazine/mt832858.

Untersuchen der neuen Datenbank in Azure Data Studio
Abbildung 4: Untersuchen der neuen Datenbank in Azure Data Studio

Bereitstellen der von EF6.3 abhängigen API für Docker für Linux

Es ist zwar möglich, Windows-abhängige Apps in Windows-Container einzufügen, aber Linux-Container sind viel einfacher zu verwenden. Und da EF6.3 nun plattformübergreifend ausgeführt werden kann, werde ich ein Linux-basiertes Image der API erstellen.

Zusätzlich zur Erstellung des Images erstelle ich eine docker-compose-Datei, um einen Container auf der Grundlage dieses Images auszuführen und ihm die Kommunikation mit SQL Server in einem Container zu ermöglichen. In meiner dreiteiligen Serie in den Ausgaben April, Mai und Juni 2019 dieses Magazins geht es darum, wie das alles funktioniert, also werde ich hier nur die Highlights aufzeigen. Außerdem können Sie den gesamten Code im Beispieldownload einsehen.

Auch hier hilft die Docker-Erweiterung von VS Code bei den meisten dieser Aufgaben. Wenn Sie F1 drücken und dann Docker eingeben, finden Sie eine Reihe von Befehlen, die die Erweiterung bereitstellt. Wählen Sie „Docker: Add Docker Files to Workspace“ (Docker: Docker-Dateien Arbeitsbereich hinzufügen) aus. Wenn Sie dazu aufgefordert werden, wählen Sie ASP.NET Core als Anwendungsplattform aus. Folgen Sie den weiteren Eingabeaufforderungen, indem Sie Linux als Betriebssystem sowie den Standardport 80 auswählen.

Die vorlagengenerierte Dockerfile-Datei benötigt jedoch ein Update, da ich Vorschau 6 von .NET Core 3.0 als Ziel verwende. Ich musste die FROM-Imageziele ändern, um Vorschau 6-Versionen direkt aus der Microsoft Container Registry (MCR) zu pullen. Ändern Sie die ersten vier Zeilen der Dockerfile-Datei wie folgt:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.0.0-preview6 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100-preview6 AS build

Anschließend habe ich eine docker-compose Datei hinzugefügt, um das Erstellen dieses neuen Images zu orchestrieren, es in einem Container auszuführen und auch einen weiteren SQL Server-Container auszuführen, mit dem die API kommunizieren kann. Wenn Sie meine früheren Artikel noch nicht gelesen haben: Dies ist wichtig, damit der API-Container weiß, wie er den Datenbankcontainer ermittelt.

Abbildung 5 zeigt die Datei „docker-compose.yml“, die ich meinem Projekt hinzugefügt habe.

Abbildung 5: Die Datei „docker-compose.yml“ für die Ausführung der API und der Datenbankcontainer

version: '3.4'
services:
  coreapi:
    image: ${DOCKER_REGISTRY-}api
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 80:80
  db:
    image: mcr.microsoft.com/mssql/server
    environment:
      SA_PASSWORD: "P@ssword1"
      ACCEPT_EULA: "Y"
    ports:
      - "1601:1433"

Die letzte Änderung, die vorgenommen werden muss, ist die Verbindungszeichenfolge in der Datei „appsettings.json“ der API. Zurzeit zeigt sie auf localhost, 1601. Aber die ASP.NET-API im neuen Container verwendet für den Server keinen eigenen localhost. Wenn Sie jedoch den Servernamen so ändern, dass er mit dem Dienstnamen (db) in der docker-compose-Datei übereinstimmt, stellt Docker sicher, dass der API-Container den Datenbankcontainer finden kann.

Dies ist die neue Verbindungsliste in „appsettings.json“:

"Connection":"Server=db;Database=Humans;User Id=sa;Password=P@ssword1"

Das ist alles. Jetzt können Sie die Container mit docker-compose ausführen. Klicken Sie einfach mit der rechten Maustaste auf die Datei „docker-compose.yml“ im Dokument-Explorer. Dank der Docker-Erweiterung ist eine der Optionen „Compose Up“. Wählen Sie diese Option aus, und warten Sie, bis der Vorgang abgeschlossen ist. Es kann ungefähr weitere 30 Sekunden dauern, bis SQL Server die Einrichtung der Systemdatenbanken abgeschlossen hat. Anschließend können Sie zur API unter localhost:80/api/human navigieren. Das Erstellen und Seeding der Humans-Datenbank nimmt ebenfalls einige Sekunden in Anspruch. SQL Server ist dabei etwas langsam, aber wenn der Vorgang abgeschlossen ist, können Sie zur API navigieren und die gleiche Ausgabe wie in Abbildung 4 sehen! Sie können dann erneut mit der rechten Maustaste auf diese YML-Datei klicken und „Compose Down“ auswählen, um die neuen Container zu entfernen.

EF6.3 ohne Windows!

Obwohl ich EF6.3 nicht vor wirkliche Herausforderungen gestellt habe, bin ich erstaunt und beeindruckt, dass ich diesen gesamten Proof of Concept in einer Nicht-Windows-Umgebung durchführen konnte. Ich würde kein neues Projekt mit EF6.3 beginnen. Alle neuen Projekte sollten EF Core verwenden, das so viele Vorteile bietet, insbesondere mit seiner plattformübergreifenden Reichweite. Wenn Sie jedoch vorhandenen Produktionscode problemlos mit EF6 einsetzen und dennoch wünschen, dass abhängige Apps von den Funktionen von .NET Core profitieren (sei es für plattformübergreifende Funktionen oder andere großartige Möglichkeiten), können Sie jetzt Ihre EF6-Bereitstellung beibehalten und trotzdem beide Welten nutzen.


Julie Lermanist Microsoft Regional Director, Microsoft MVP, Docker Captain und Coach für das Softwareteam. Sie arbeitet außerdem als 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 zu anderen Themen. Julie Lerman führt unter thedatafarm.com/blog einen Blog. Sie ist die Autorin von „Programming Entity Framework“ sowie der Ausgaben „Code First“ und „DbContext“ (alle bei O’Reilly Media erschienen). Folgen Sie ihr auf Twitter: @julielerman, und sehen Sie sich ihre Pluralsight-Kurse unter bit.ly/PS-Julie an.

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Diego Vega (Diego.Vega@microsoft.com), Brice Lambson <(bricelam@microsoft.com)>


Diesen Artikel im MSDN Magazine-Forum diskutieren