Neue Features in Entity Framework Core 3.x

Die folgende Liste enthält die wichtigsten neuen Features in EF Core 3.x:

EF Core 3.x ist eine Hauptversion und enthält damit zahlreiche Breaking Changes. Hierbei handelt es sich um Verbesserungen der API, die einen negativen Einfluss auf vorhandene Anwendungen haben können.

LINQ-Überarbeitung

Mit LINQ können Sie Datenbankabfragen in einer beliebigen .NET-Sprache schreiben. Dabei werden Rich-Type-Informationen genutzt, um IntelliSense und Typprüfung zur Kompilierungszeit bereitzustellen. LINQ ermöglicht es Ihnen darüber hinaus, eine unbegrenzte Anzahl komplizierter Abfragen zu schreiben, die beliebige Ausdrücke enthalten (Methodenaufrufe oder Operationen). Die Behandlung dieser Kombinationen ist die größte Herausforderung für LINQ-Anbieter.

In EF Core 3-x wurde der LINQ-Anbieter neu strukturiert, damit mehr Abfragemuster in SQL übersetzt und in mehr Fällen effiziente Abfragen erzeugt werden können. Zudem werden ineffiziente Abfragen erkannt. Der neue LINQ-Anbieter ist die Grundlage, dank derer wir in zukünftigen Versionen neue Abfragefunktionen und Leistungsverbesserungen anbieten können, ohne die Funktionsfähigkeit vorhandener Anwendungen und Datenanbieter zu beeinträchtigen.

Eingeschränkte Clientauswertung

Die wichtigste Änderung hat mit der Behandlung von LINQ-Ausdrücken zu tun, die nicht in Parameter konvertiert oder in SQL übersetzt werden können.

In vorherigen Versionen ermittelte EF Core einfach, welche Teile einer Abfrage in SQL übersetzt werden konnten, und führte den Rest der Abfrage auf dem Client aus. Diese Art von clientseitiger Ausführung kann in einigen Situationen wünschenswert sein, aber in vielen anderen Fällen kann sie zu ineffizienten Abfragen führen.

Wenn beispielsweise EF Core 2.2 ein Prädikat in einem Where()-Aufruf nicht übersetzen konnte, wurde eine SQL-Anweisung ohne Filter ausgeführt, alle Zeilen aus der Datenbank wurden übertragen und dann im Arbeitsspeicher gefiltert:

var specialCustomers = context.Customers
    .Where(c => c.Name.StartsWith(n) && IsSpecialCustomer(c));

Dies kann akzeptabel sein, wenn die Datenbank nur wenige Zeilen enthält, kann aber auch zu erheblichen Leistungsproblemen oder sogar dem Ausfall der Anwendung führen, wenn eine Datenbank eine große Anzahl von Zeilen enthält.

In EF Core 3.x wurde die Clientauswertung auf die Projektion der obersten Ebene (den letzten Aufruf von Select()) eingeschränkt. Wenn EF Core 3.x an einer anderen Stelle in der Abfrage Ausdrücke erkennt, die nicht übersetzt werden können, wird eine Runtimeausnahme ausgegeben.

Um eine Prädikatbedingung auf dem Client wie im vorherigen Beispiel auszuwerten, müssen Entwickler jetzt die Auswertung der Abfrage explizit auf LINQ to Objects umstellen:

var specialCustomers = context.Customers
    .Where(c => c.Name.StartsWith(n))
    .AsEnumerable() // switches to LINQ to Objects
    .Where(c => IsSpecialCustomer(c));

Weitere Informationen zu den Auswirkungen auf vorhandene Anwendungen finden Sie in der Dokumentation zu Breaking Changes.

Einzelne SQL-Anweisung pro LINQ-Abfrage

Eine weitere erhebliche Änderung in Version 3.x ist, dass jetzt immer eine einzelne SQL-Anweisung pro LINQ-Abfrage generiert wird. In früheren Versionen wurden in bestimmten Fällen mehrere SQL-Anweisungen generiert, z. B. um Include()-Aufrufe in Auflistungs-Navigationseigenschaften oder Abfragen zu übersetzen, die bestimmten Mustern mit Unterabfragen entsprachen. Obwohl dies in einigen Fällen praktisch war und für Include() sogar verhinderte, dass redundante Daten über das Netzwerk gesendet wurden, war die Implementierung komplex und führte zu einigen äußerst ineffizienten Verhaltensweisen (N+1-Abfragen). In einigen Situationen waren die Daten, die über mehrere Abfragen zurückgegeben werden, inkonsistent.

Ähnlich wie bei der Clientauswertung wird in EF Core 3.x eine Runtimeausnahme ausgelöst, wenn eine LINQ-Abfrage nicht in eine einzelne SQL-Anweisung übersetzt werden kann. Mit EF Core können aber einige der gängigen Muster, mit denen mehrere Abfragen generiert wurden, in eine einzelne Abfrage mit JOINs übersetzt werden.

Azure Cosmos DB-Unterstützung

Der Azure Cosmos DB-Anbieter für EF Core ermöglicht es Entwickler*innen, die mit dem EF-Programmiermodell vertraut sind, Azure Cosmos DB problemlos als Anwendungsdatenbank-Ziel einzusetzen. Ziel ist es, einige der Vorteile von Azure Cosmos DB – beispielsweise die globale Verteilung, Always On-Verfügbarkeit, elastische Skalierbarkeit und geringe Latenz – noch besser für .NET-Entwickler*innen zugänglich zu machen. Der Anbieter unterstützt die meisten EF Core-Features, darunter die automatische Änderungsnachverfolgung, LINQ und Wertekonvertierungen anhand von Azure Cosmos DB for NoSQL.

Weitere Informationen finden Sie in der Dokumentation zum Azure Cosmos DB-Anbieter.

C# 8.0-Unterstützung

EF Core 3.x nutzt einige der neuen Features in C# 8.0:

Asynchrone Streams

Asynchrone Abfrageergebnisse werden jetzt mithilfe der neuen IAsyncEnumerable<T>-Standardschnittelle verfügbar gemacht und können mithilfe von await foreach konsumiert werden.

var orders =
    from o in context.Orders
    where o.Status == OrderStatus.Pending
    select o;

await foreach(var o in orders.AsAsyncEnumerable())
{
    Process(o);
}

Weitere Informationen finden Sie in der C#-Dokumentation zu asynchronen Streams.

Nullwerte zulassende Verweistypen

Wenn diese neue Funktion in Ihrem Code aktiviert ist, prüft EF Core die NULL-Zulässigkeit von Verweistypeigenschaften und wendet sie auf die entsprechenden Spalten und Beziehungen in der Datenbank an: Eigenschaften von Verweistypen, die keine NULL-Werte zulassen, werden so behandelt, als wiesen sie das Datenanmerkungsattribut [Required] auf.

In der folgenden Klasse werden z. B. Eigenschaften mit dem Typ string? als optional konfiguriert, während string als erforderlich konfiguriert wird:

public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string? MiddleName { get; set; }
}

Weitere Informationen finden Sie in der EF Core-Dokumentation zum Arbeiten mit Verweistypen, die NULL-Werte zulassen.

Abfangen von Datenbankvorgängen

Durch die neue Abfang-API in EF Core 3.x kann benutzerdefinierte Logik automatisch aufgerufen werden, wenn spezifische Datenbankvorgänge im Rahmen des normalen Betriebs von EF Core durchgeführt werden. Hierzu zählen z. B. das Öffnen von Verbindungen, Committen von Transaktionen oder Ausführen von Befehlen.

Ähnlich wie bei den Abfangfunktionen in EF 6 können Sie mit den Abfangfunktionen Vorgänge vor oder nach dem Auftreten abfangen. Wenn Sie sie vorher abfangen, können Sie die Ausführung umgehen und alternative Ergebnisse aus der Abfanglogik angeben.

Zum Bearbeiten von Befehlstext können Sie beispielsweise DbCommandInterceptor erstellen:

public class HintCommandInterceptor : DbCommandInterceptor
{
    public override InterceptionResult<DbDataReader> ReaderExecuting(
        DbCommand command,
        CommandEventData eventData,
        InterceptionResult<DbDataReader> result)
    {
        // Manipulate the command text, etc. here...
        command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
        return result;
    }
}

Und bei DbContext registrieren:

services.AddDbContext(b => b
    .UseSqlServer(connectionString)
    .AddInterceptors(new HintCommandInterceptor()));

Reverse Engineering (Zurückentwicklung) von Datenbanksichten

Abfragetypen, die Daten darstellen, die aus der Datenbank gelesen, aber nicht aktualisiert werden können, wurden in schlüssellose Entitätstypen umbenannt. Da sie in den meisten Szenarien ideal für die Zuordnung von Datenbankansichten sind, erstellt EF Core jetzt automatisch schlüssellose Entitätstypen beim Reverse Engineering von Datenbankansichten.

Mit dem dotnet ef-Befehlszeilenprogramm können Sie z. B. Folgendes eingeben:

dotnet ef dbcontext scaffold "Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer

Das Tool erstellt dann automatisch einen Gerüsttyp für Ansichten und Tabellen ohne Schlüssel:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Names>(entity =>
    {
        entity.HasNoKey();
        entity.ToView("Names");
    });

    modelBuilder.Entity<Things>(entity =>
    {
        entity.HasNoKey();
    });
}

Abhängige Entitäten, die die Tabelle gemeinsam mit dem Prinzipal verwenden, sind jetzt optional

Ab EF Core 3.x kann Order ohne OrderDetails und die Eigenschaften von OrderDetails hinzugefügt werden, wenn OrderDetails im Besitz von Order oder explizit derselben Tabelle zugeordnet ist. Der Primärschlüssel wird jedoch Nullable-Spalten zugeordnet.

Bei Abfragen von EF Core wird OrderDetails auf null festgelegt, wenn eine der erforderlichen Eigenschaften keinen Wert aufweist oder keine erforderlichen Eigenschaften außer dem primären Schlüssel vorhanden und alle Eigenschaften null sind.

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public OrderDetails Details { get; set; }
}

[Owned]
public class OrderDetails
{
    public int Id { get; set; }
    public string ShippingAddress { get; set; }
}

EF 6.3 unter .NET Core

Dieses Feature stammt zwar nicht von EF Core 3.x, ist jedoch für viele unserer Kunden wichtig.

Uns ist bewusst, dass viele bestehende Anwendungen frühere Versionen von EF verwenden und dass die Portierung auf EF Core nur zur Nutzung von .NET Core unter Umständen mit einem erheblichen Aufwand verbunden ist. Aus diesem Grund wurde die neueste Version von EF 6 portiert, sodass sie unter .NET Core 3.x ausgeführt werden kann.

Weitere Informationen finden Sie im Artikel zu Neuerungen in EF 6.

Verschobene Features

Einige Features, die ursprünglich für EF Core 3.x geplant waren, wurden auf zukünftige Releases verschoben:

  • Die Fähigkeit, Teile eines Modells in Migrationen zu ignorieren, nachverfolgt als #2725.
  • Entitäten von Eigenschaftenbehältern, die in zwei separaten Problemen nachverfolgt werden: #9914 über Entitäten von gemeinsam genutzten Typen und #13610 über Unterstützung der Zuordnung von indizierten Eigenschaften.