Breaking Changes in EF Core 6.0

Die folgenden API-Änderungen und Behavior Changes können dazu führen, dass vorhandene Anwendungen nach einem Upgrade auf EF Core 6.0 nicht mehr funktionieren.

Zielframework

EF Core 6.0 zielt auf .NET 6 ab. Anwendungen für ältere .NET-, .NET Core- und .NET Framework-Versionen müssen .NET 6 für die Verwendung von EF Core 6.0 verwenden.

Zusammenfassung

Wichtige Änderung Auswirkungen
Geschachtelte optionale abhängige Objekte, die eine Tabelle gemeinsam nutzen und keine erforderlichen Eigenschaften aufweisen, können nicht gespeichert werden Hoch
Die Änderung des Besitzers einer nicht eigenständigen Entität löst jetzt eine Ausnahme aus Medium
Azure Cosmos DB: Verwandte Entitätstypen werden als nicht eigenständig ermittelt Medium
SQLite: Verbindungen werden in einem Pool zusammengefasst Medium
Für m:n-Beziehungen ohne zugeordnete Joinentitäten wird jetzt ein Gerüst verwendet Medium
Bereinigte Zuordnung zwischen DeleteBehavior- und ON DELETE-Werten Niedrig
In-Memory-Datenbank überprüft, ob erforderliche Eigenschaften NULL-Werte enthalten Niedrig
Letzte ORDER BY-Reihenfolge beim Beitreten zu Sammlungen entfernt Niedrig
DbSet implementiert IAsyncEnumerable nicht mehr Niedrig
Der Rückgabeentitätstyp einer Tabellenwertfunktion wird standardmäßig auch einer Tabelle zugeordnet Niedrig
Die Eindeutigkeit der Namen von Überprüfungseinschränkungen wird jetzt überprüft Niedrig
IReadOnly-Metadatenschnittstellen wurden hinzugefügt und Erweiterungsmethoden entfernt Niedrig
IExecutionStrategy ist jetzt ein Singletondienst Niedrig
SQL Server: Weitere Fehler gelten als vorübergehend Niedrig
Azure Cosmos DB: Weitere Zeichen werden in id-Werten mit Escapezeichen versehen Niedrig
Einige Singletondienste sind jetzt bereichsbezogen Niedrig*
Neue Zwischenspeicherungs-API für Erweiterungen, die Dienste hinzufügen oder ersetzen Niedrig*
Neue Initialisierungsprozedur für Momentaufnahmen und Entwurfszeitmodelle Niedrig
OwnedNavigationBuilder.HasIndex gibt jetzt einen anderen Typ zurück Niedrig
DbFunctionBuilder.HasSchema(null) überschreibt [DbFunction(Schema = "schema")] Niedrig
Vorab initialisierte Navigationen werden durch Werte aus Datenbankabfragen überschrieben Niedrig
Unbekannte Enumerationszeichenfolgenwerte in der Datenbank werden beim Abfragen nicht in den Enumerationsstandard konvertiert Niedrig
DbFunctionBuilder.HasTranslation stellt jetzt die Funktionsargumente als IReadOnlyList statt als IReadOnlyCollection bereit Niedrig
Die Standardtabellenzuordnung wird nicht entfernt, wenn die Entität einer Tabellenwertfunktion zugeordnet wird Niedrig
dotnet-ef verwendet .NET 6 als Ziel Niedrig
IModelCacheKeyFactory-Implementierungen müssen möglicherweise aktualisiert werden, um die Entwurfszeitzwischenspeicherung zu verarbeiten. Niedrig

* Diese Änderungen sind für Autoren von Datenbankanbietern und -erweiterungen von besonderem Interesse.

Änderungen mit hoher Auswirkung

Geschachtelte optionale abhängige Objekte, die eine Tabelle gemeinsam nutzen und keine erforderlichen Eigenschaften aufweisen, können nicht gespeichert werden

Nachverfolgung von Issue 24558

Altes Verhalten

Modelle mit geschachtelten optionalen abhängigen Objekten, die eine Tabelle gemeinsam nutzen und keine erforderlichen Eigenschaften aufweisen, waren zulässig, konnten jedoch zu Datenverlusten führen, wenn die Daten abgefragt und dann erneut gespeichert wurden. Betrachten Sie beispielsweise das folgende Modell:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ContactInfo ContactInfo { get; set; }
}

[Owned]
public class ContactInfo
{
    public string Phone { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

Keine der Eigenschaften in ContactInfo oder Address ist erforderlich, und all diese Entitätstypen werden derselben Tabelle zugeordnet. Die Regeln für optionale abhängige Objekte (im Gegensatz zu erforderlichen abhängigen Objekten) legen fest, dass beim Abfragen des Besitzers Customer keine Instanz von ContactInfo erstellt wird, wenn alle Spalten für ContactInfo NULL sind. Dies bedeutet jedoch auch, dass auch dann keine Instanz von Address erstellt wird, wenn die Address-Spalten ungleich NULL sind.

Neues Verhalten

Wenn Sie versuchen, dieses Modell zu verwenden, wird jetzt die folgende Ausnahme ausgelöst:

System.InvalidOperationException: Der Entitätstyp „ContactInfo“ ist ein optionales abhängiges Objekt, das die Tabellenfreigabe verwendet und andere abhängige Objekte ohne erforderliche nicht freigegebene Eigenschaften enthält, um zu ermitteln, ob die Entität vorhanden ist. Wenn alle Nullwerte zulassenden Eigenschaften einen NULL-Wert in der Datenbank enthalten, wird in der Abfrage keine Objektinstanz erstellt, wodurch die Werte geschachtelter abhängiger Objekte verloren gehen. Fügen Sie eine erforderliche Eigenschaft hinzu, um Instanzen mit NULL-Werten für andere Eigenschaften zu erstellen, oder markieren Sie die eingehende Navigation als erforderlich, damit immer eine Instanz erstellt wird.

Dadurch werden Datenverluste beim Abfragen und Speichern von Daten verhindert.

Warum?

Die Verwendung von Modellen mit geschachtelten optionalen abhängigen Objekten, die eine Tabelle gemeinsam nutzen und keine erforderlichen Eigenschaften aufweisen, führten häufig zu stillschweigendem Datenverlust.

Gegenmaßnahmen

Vermeiden Sie die Verwendung optionaler abhängiger Objekte, die eine Tabelle gemeinsam nutzen und keine erforderlichen Eigenschaften aufweisen. Hierfür gibt es drei Möglichkeiten:

  1. Legen Sie die abhängigen Objekte als erforderlich fest. Dies bedeutet, dass die abhängige Entität immer einen Wert hat, nachdem sie abgefragt wurde, auch wenn alle ihre Eigenschaften NULL sind. Beispiel:

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        [Required]
        public Address Address { get; set; }
    }
    

    Oder:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.OwnsOne(e => e.Address);
                b.Navigation(e => e.Address).IsRequired();
            });
    
  2. Stellen Sie sicher, dass das abhängige Objekt mindestens eine erforderliche Eigenschaft enthält.

  3. Ordnen Sie optionale abhängige Objekte einer eigenen Tabelle zu, anstatt eine Tabelle gemeinsam mit dem Prinzipal zu verwenden. Beispiel:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.ToTable("Customers");
                b.OwnsOne(e => e.Address, b => b.ToTable("CustomerAddresses"));
            });
    

Die Probleme mit optionalen abhängigen Objekten und Beispiele für diese Gegenmaßnahmen sind in der Dokumentation zu Neuerungen in EF Core 6.0 enthalten.

Änderungen mit mittlerer Auswirkung

Die Änderung des Besitzers einer nicht eigenständigen Entität löst jetzt eine Ausnahme aus

Nachverfolgung von Issue 4073

Altes Verhalten

Es war möglich, eine nicht eigenständige Entität einem anderen Besitzer zuzuweisen.

Neues Verhalten

Diese Aktion löst nun eine Ausnahme aus:

The property '{entityType}.{property}' is part of a key and so cannot be modified or marked as modified. (Die Eigenschaft „{Entitätstyp}.{Eigenschaft}“ ist Teil eines Schlüssels und kann weder geändert noch als geändert gekennzeichnet werden.) Um den Prinzipal einer vorhandenen Entität mit einem identifizierenden Fremdschlüssel zu ändern, müssen Sie zuerst die abhängige Entität löschen, „SaveChanges“ aufrufen und die abhängige Entität dann mit dem neuen Prinzipal verknüpfen.

Warum?

Obwohl nicht eigenständige Typen keine Schlüsseleigenschaften aufweisen müssen, erstellt EF trotzdem Schatteneigenschaften, die als Primärschlüssel und als auf den Besitzer verweisender Fremdschlüssel verwendet werden. Wenn die besitzende Entität geändert wird, werden die Werte des Fremdschlüssels für die nicht eigenständige Entität geändert. Da sie außerderm als Primärschlüssel verwendet werden, ändert sich die Entitätsidentität. Dies wird in EF Core noch nicht vollständig unterstützt und war für nicht eigenständige Entitäten nur bedingt zulässig, was manchmal dazu führte, dass der interne Zustand inkonsistent wurde.

Gegenmaßnahmen

Anstatt die gleiche nicht eigenständige Instanz einem neuen Besitzer zuzuweisen, können Sie eine Kopie zuweisen und die alte löschen.

Nachverfolgung von Issue 24803Neuerungen: Standardmäßig impliziter Besitz

Altes Verhalten

Wie bei anderen Anbietern wurden verwandte Entitätstypen als normale (eigenständige) Typen ermittelt.

Neues Verhalten

Verwandte Entitätstypen befinden sich jetzt im Besitz des Entitätstyps, für den sie ermittelt wurden. Nur die Entitätstypen, die zu einer DbSet<TEntity>-Eigenschaft gehören, werden als eigenständige Entitätstypen ermittelt.

Warum?

Dieses Verhalten entspricht dem gängigen Muster, bei dem Daten in Azure Cosmos DB modelliert und verwandte Daten in ein einzelnes Dokument eingebettet werden. Azure Cosmos DB bietet keine native Unterstützung für das Verknüpfen verschiedener Dokumente, sodass die Modellierung verwandter Entitäten als eigenständige Entitäten nur eingeschränkt nützlich ist.

Gegenmaßnahmen

Um einen Entitätstyp so zu konfigurieren, dass er eigenständig ist, müssen Sie modelBuilder.Entity<MyEntity>(); aufrufen.

SQLite: Verbindungen werden in einem Pool zusammengefasst

Nachverfolgung von Issue 13837Neuerungen: Standardmäßig impliziter Besitz

Altes Verhalten

Bisher wurden Verbindungen in Microsoft.Data.Sqlite nicht in einem Pool zusammengefasst.

Neues Verhalten

Ab 6.0 werden Verbindungen jetzt standardmäßig in einem Pool zusammengefasst. Dies führt dazu, dass der Prozess Datenbankdateien auch nach dem Schließen des ADO.NET-Verbindungsobjekts geöffnet lässt.

Warum?

Durch das Zusammenfassen der zugrunde liegenden Verbindungen in einem Pool wird die Leistung beim Öffnen und Schließen von ADO.NET-Verbindungsobjekten erheblich verbessert. Dies ist besonders in Szenarien spürbar, in denen das Öffnen der zugrunde liegenden Verbindung kostspielig ist, wie z. B. bei der Verschlüsselung, oder in Szenarien, in denen eine große Menge kurzlebiger Verbindungen mit der Datenbank besteht.

Gegenmaßnahmen

Das Verbindungspooling kann durch Hinzufügen von Pooling=False zu einer Verbindungszeichenfolge deaktiviert werden.

In einigen Szenarien (z. B. beim Löschen der Datenbankdatei) können jetzt Fehler auftreten, die darauf hinweisen, dass die Datei noch verwendet wird. Sie können den Verbindungspool manuell über SqliteConnection.ClearPool() löschen, bevor Sie Vorgänge der Datei ausführen.

SqliteConnection.ClearPool(connection);
File.Delete(databaseFile);

Für m:n-Beziehungen ohne zugeordnete Joinentitäten wird jetzt ein Gerüst verwendet

Nachverfolgung von Issue 22475

Altes Verhalten

Beim Gerüstbau (Reverse Engineering) von DbContext und Entitätstypen aus einer vorhandenen Datenbank wurden Jointabellen immer explizit zugeordnet, um Entitätstypen für m:n-Beziehungen zu verknüpfen.

Neues Verhalten

Einfache Jointabellen, die nur zwei Fremdschlüsseleigenschaften für andere Tabellen enthalten, werden nicht mehr expliziten Entitätstypen, sondern als m:n-Beziehung zwischen den beiden verknüpften Tabellen zugeordnet.

Warum?

m:n-Beziehungen ohne explizite Jointypen wurden in EF Core 5.0 eingeführt und bieten eine übersichtlichere, natürlichere Möglichkeit zur Darstellung einfacher Jointabellen.

Gegenmaßnahmen

Es gibt zwei Gegenmaßnahmen. Der bevorzugte Ansatz besteht darin, Code so zu aktualisieren, dass die m:n-Beziehungen direkt verwendet werden. Es ist sehr selten, dass der Joinentitätstyp direkt verwendet werden muss, wenn er nur zwei Fremdschlüssel für die m:n-Beziehungen enthält.

Alternativ kann die explizite Joinentität wieder dem EF-Modell hinzugefügt werden. Wenn beispielsweise eine m:n-Beziehung zwischen Post und Tag besteht, fügen Sie den Jointyp und die Navigationen mithilfe von partiellen Klassen wieder hinzu:

public partial class PostTag
{
    public int PostsId { get; set; }
    public int TagsId { get; set; }

    public virtual Post Posts { get; set; }
    public virtual Tag Tags { get; set; }
}

public partial class Post
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

public partial class Tag
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

Fügen Sie dann eine Konfiguration für den Jointyp und Navigationen einer partiellen Klasse für DbContext hinzu:

public partial class DailyContext
{
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(entity =>
        {
            entity.HasMany(d => d.Tags)
                .WithMany(p => p.Posts)
                .UsingEntity<PostTag>(
                    l => l.HasOne<Tag>(e => e.Tags).WithMany(e => e.PostTags).HasForeignKey(e => e.TagsId),
                    r => r.HasOne<Post>(e => e.Posts).WithMany(e => e.PostTags).HasForeignKey(e => e.PostsId),
                    j =>
                    {
                        j.HasKey("PostsId", "TagsId");
                        j.ToTable("PostTag");
                    });
        });
    }
}

Entfernen Sie abschließend die generierte Konfiguration für die m:n-Beziehung aus dem Gerüstkontext. Dies ist erforderlich, weil der Joinentitätstyp des Gerüsts aus dem Modell entfernt werden muss, bevor der explizite Typ verwendet werden kann. Dieser Code muss bei jedem Gerüstbau des Kontexts entfernt werden. Da sich der obige Code jedoch in partiellen Klassen befindet, wird er beibehalten.

Beachten Sie, dass die Joinentität bei dieser Konfiguration wie in früheren Versionen von EF Core explizit verwendet werden kann. Die Beziehung kann jedoch auch als m:n-Beziehung verwendet werden. Die derartige Aktualisierung des Codes kann daher eine temporäre Lösung darstellen, während der Rest des Codes so aktualisiert wird, dass die Beziehung auf natürliche Weise als m:n verwendet wird.

Änderungen mit geringer Auswirkung

Bereinigte Zuordnung zwischen DeleteBehavior- und ON DELETE-Werten

Nachverfolgung von Issue #21252

Altes Verhalten

Einige der Zuordnungen zwischen dem OnDelete()-Verhalten einer Beziehung und dem ON DELETE-Verhalten von Fremdschlüsseln in der Datenbank waren in Migrationen und Gerüstbau nicht konsistent.

Neues Verhalten

In der folgenden Tabelle werden die Änderungen an Migrationen aufgeführt.

OnDelete() ON DELETE
NoAction NO ACTION
ClientNoAction NO ACTION
Einschränken RESTRICT
Cascasde CASCADE
ClientCascade RESTRICTNO ACTION
SetNull SET NULL
ClientSetNull RESTRICTNO ACTION

Die Änderungen an Gerüstbau lauten wie folgt.

ON DELETE OnDelete()
NO ACTION ClientSetNull
RESTRICT ClientSetNullRestrict
CASCADE Kaskadieren
SET NULL SetNull

Warum?

Die neuen Zuordnungen sind konsistenter. Das standardmäßige Datenbankverhalten von NO ACTION wird jetzt dem restriktiveren und weniger performanten RESTRICT-Verhalten vorgezogen.

Gegenmaßnahmen

Das OnDelete()-Standardverhalten optionaler Beziehungen ist ClientSetNull. Die Zuordnung wurde von RESTRICT in NO ACTION geändert. Dies kann dazu führen, dass bei Ihrer ersten Migration viele Vorgänge generiert werden, die nach dem Upgrade auf EF Core 6.0 hinzugefügt wird.

Sie können diese Vorgänge entweder anwenden oder manuell aus der Migration entfernen, da sie keine funktionalen Auswirkungen auf EF Core haben.

SQL Server unterstützt RESTRICT nicht, diese Fremdschlüssel wurden also bereits mit NO ACTION erstellt. Die Migrationsvorgänge wirken sich nicht auf SQL Server aus und können problemlos entfernt werden.

In-Memory-Datenbank überprüft, ob erforderliche Eigenschaften NULL-Werte enthalten

Nachverfolgung von Issue 10613

Altes Verhalten

Die In-Memory-Datenbank erlaubte auch dann das Speichern von NULL-Werten, wenn die Eigenschaft wie erforderlich konfiguriert wurde.

Neues Verhalten

Die In-Memory-Datenbank löst eine Microsoft.EntityFrameworkCore.DbUpdateException aus, wenn SaveChanges oder SaveChangesAsync aufgerufen wird und eine erforderliche Eigenschaft auf NULL festgelegt ist.

Warum?

Das In-Memory-Datenbankverhalten stimmt jetzt mit dem Verhalten anderer Datenbanken überein.

Gegenmaßnahmen

Das vorherige Verhalten (d. h. keine Überprüfung von NULL-Werten) kann beim Konfigurieren des In-Memory-Anbieters wiederhergestellt werden. Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseInMemoryDatabase("MyDatabase", b => b.EnableNullChecks(false));
}

Letzte ORDER BY-Anweisung beim Beitreten zu Sammlungen entfernt

Nachverfolgung von Issue #19828

Altes Verhalten

Beim Ausführen von SQL JOINs für Sammlungen (1:n-Beziehungen) wird mit EF Core für jede Schlüsselspalte der verbundenen Tabelle eine ORDER BY-Spalte hinzugefügt. Beispielsweise erfolgt ein Laden aller Blogs mit den zugehörigen Beiträgen über die folgenden SQL-Anweisung:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]

Diese Anweisungen sind für die ordnungsgemäße Materialisierung der Entitäten erforderlich.

Neues Verhalten

Die letzte ORDER BY-Anweisung für eine Sammlung mit JOIN wird jetzt ausgelassen:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]

Es wird keine ORDER BY-Anweisung für die Spalte POST-ID mehr generiert.

Warum?

Jede ORDER BY-Anweisung bedeutet zusätzliche Arbeit auf der Datenbankseite, und die letzte Anweisung ist für die Materialisierung von EF Core nicht erforderlich. Daten zeigen, dass das Entfernen dieser letzten Anweisung in einigen Szenarios zu einer erheblichen Leistungsverbesserung führen kann.

Gegenmaßnahmen

Wenn Ihre Anwendung erwartet, dass die verbundenen Entitäten in einer bestimmten Reihenfolge zurückgegeben werden, machen Sie diese explizit, indem Sie ihrer Abfrage einen OrderBy-Operator (LINQ) hinzufügen.

DbSet implementiert IAsyncEnumerable nicht mehr

Nachverfolgung von Issue #24041

Altes Verhalten

DbSet<TEntity> wird zum Ausführen von Abfragen für DbContext verwendet, die zum Implementieren von IAsyncEnumerable<T> verwendet werden.

Neues Verhalten

DbSet<TEntity> implementiert nicht mehr direkt IAsyncEnumerable<T>.

Warum?

DbSet<TEntity> wurde ursprünglich so gestaltet, dass hauptsächlich IAsyncEnumerable<T> implementiert wird, um eine direkte Enumeration über das Konstrukt foreach zu ermöglichen. Wenn ein Projekt auch auf System.Linq.Async verweist, um asynchrone LINQ-Operatoren clientseitig zu erstellen, führte dies zu einem mehrdeutigen Aufruffehler zwischen den Operatoren, die über IQueryable<T> definiert, und den Operatoren, die über IAsyncEnumerable<T> definiert wurden. C# 9 hat die Erweiterungsunterstützung für GetEnumerator für foreach-Schleifen hinzugefügt, wodurch der ursprüngliche Hauptgrund für den Verweis auf IAsyncEnumerable entfernt wird.

Die meisten Anwendungsszenarios von DbSet funktionieren weiterhin wie gewohnt, da zum Beispiel LINQ-Operatoren über DbSet erstellt werden oder eine Enumeration erfolgt. Die einzigen Anwendungsszenarios, die nicht funktionieren, sind diejenigen, in denen DbSet direkt in IAsyncEnumerable konvertiert werden soll.

Gegenmaßnahmen

Wenn Sie auf einen DbSet<TEntity> als IAsyncEnumerable<T> verweisen müssen, rufen Sie DbSet<TEntity>.AsAsyncEnumerable auf, um ihn explizit umzuwandeln.

Der Rückgabeentitätstyp einer Tabellenwertfunktion wird standardmäßig auch einer Tabelle zugeordnet

Nachverfolgung von Issue 23408

Altes Verhalten

Ein Entitätstyp wurde nicht standardmäßig einer Tabelle zugeordnet, wenn er als Rückgabetyp einer mit HasDbFunction konfigurierten Tabellenwertfunktion verwendet wurde.

Neues Verhalten

Ein Entitätstyp, der als Rückgabetyp einer Tabellenwertfunktion verwendet wird, behält die Standardtabellenzuordnung bei.

Warum?

Es ist nicht intuitiv, dass durch das Konfigurieren einer Tabellenwertfunktion die Standardtabellenzuordnung des Rückgabeentitätstyps entfernt wird.

Gegenmaßnahmen

Um die Standardtabellenzuordnung zu entfernen, müssen Sie ToTable(EntityTypeBuilder, String) aufrufen:

modelBuilder.Entity<MyEntity>().ToTable((string?)null));

Die Eindeutigkeit der Namen von Überprüfungseinschränkungen wird jetzt überprüft

Nachverfolgung von Issue 25061

Altes Verhalten

Überprüfungseinschränkungen mit demselben Namen konnten für dieselbe Tabelle deklariert und verwendet werden.

Neues Verhalten

Das explizite Konfigurieren von zwei Überprüfungseinschränkungen mit demselben Namen für die gleiche Tabelle führt jetzt zu einer Ausnahme. Überprüfungseinschränkungen, die durch eine Konvention erstellt werden, wird ein eindeutiger Name zugewiesen.

Warum?

Die meisten Datenbanken lassen nicht zu, dass zwei Überprüfungseinschränkungen mit demselben Namen für die gleiche Tabelle erstellt werden. Einige Datenbanken erfordern sogar, dass diese Namen auch tabellenübergreifend eindeutig sind. Dies würde dazu führen, dass beim Anwenden einer Migration eine Ausnahme ausgelöst wird.

Gegenmaßnahmen

In einigen Fällen können gültige Namen von Überprüfungseinschränkungen aufgrund dieser Änderung unterschiedlich sein. Um den gewünschten Namen explizit anzugeben, müssen Sie HasName aufrufen:

modelBuilder.Entity<MyEntity>().HasCheckConstraint("CK_Id", "Id > 0", c => c.HasName("CK_MyEntity_Id"));

IReadOnly-Metadatenschnittstellen wurden hinzugefügt und Erweiterungsmethoden entfernt

Nachverfolgung von Issue 19213

Altes Verhalten

Es gab drei verschiedene Metadatenschnittstellen: IModel, IMutableModel und IConventionModel sowie Erweiterungsmethoden.

Neues Verhalten

Eine neue IReadOnly-Schnittstelle wurde hinzugefügt, z. B. IReadOnlyModel. Erweiterungsmethoden, die zuvor für die Metadatenschnittstellen definiert wurden, wurden in Standardschnittstellenmethoden konvertiert.

Warum?

Standardschnittstellenmethoden ermöglichen es, die Implementierung zu überschreiben. Dies wird von der neuen Implementierung des Laufzeitmodells genutzt, um eine bessere Leistung zu bieten.

Gegenmaßnahmen

Der Großteil des Codes sollte nicht von diesen Änderungen betroffen sein. Sollten Sie die Erweiterungsmethoden jedoch über die statische Aufrufsyntax verwenden, muss diese in Instanzaufrufsyntax konvertiert werden.

IExecutionStrategy ist jetzt ein Singletondienst

Nachverfolgung von Issue 21350

Neues Verhalten

IExecutionStrategy ist jetzt ein Singleton-Dienst. Das bedeutet, dass jeder in benutzerdefinierten Implementierungen hinzugefügte Zustand ausführungsübergreifend beibehalten und der an ExecutionStrategy übergebene Delegat nur einmal ausgeführt wird.

Warum?

Dadurch werden Zuweisungen zu zwei langsamsten Pfaden in EF reduziert.

Gegenmaßnahmen

Implementierungen, die von ExecutionStrategy ableiten, sollten jeden Zustand in OnFirstExecution() löschen.

Die bedingte Logik im Delegaten, der an ExecutionStrategy übergeben wurde, sollte in eine benutzerdefinierte Implementierung von IExecutionStrategy verschoben werden.

SQL Server: Weitere Fehler gelten als vorübergehend

Nachverfolgung von Issue 25050

Neues Verhalten

Die im obigen Issue aufgeführten Fehler gelten jetzt als vorübergehend. Wenn Sie die Standardausführungsstrategie (ohne Wiederholungen) verwenden, werden diese Fehler jetzt von einer zusätzlichen Ausnahmeinstanz umschlossen.

Warum?

Wir sammeln weiterhin Feedback von Benutzer*innen und vom SQL Server-Team dazu, welche Fehler als vorübergehend betrachtet werden sollten.

Gegenmaßnahmen

Um die Gruppe von Fehlern zu ändern, die als vorübergehend gelten, müssen Sie eine benutzerdefinierte Ausführungsstrategie einsetzen, die von SqlServerRetryingExecutionStrategy - Verbindungsresilienz – EF Core abgeleitet werden kann.

Azure Cosmos DB: Weitere Zeichen werden in id-Werten mit Escapezeichen versehen

Nachverfolgung von Issue 25100

Altes Verhalten

In EF Core 5 wurde nur '|' in id-Werten mit Escapezeichen versehen.

Neues Verhalten

In EF Core 6 werden auch '/', '\', '?' und '#' in id-Werten mit Escapezeichen versehen.

Warum?

Diese Zeichen sind ungültig. Dies ist in Resource.Id dokumentiert. Wenn Sie sie in id verwenden, schlagen Abfragen fehl.

Gegenmaßnahmen

Sie können den generierten Wert überschreiben, indem Sie ihn festlegen, bevor die Entität als Added markiert wird:

var entry = context.Attach(entity);
entry.Property("__id").CurrentValue = "MyEntity|/\\?#";
entry.State = EntityState.Added;

Einige Singletondienste sind jetzt bereichsbezogen

Nachverfolgung von Issue 25084

Neues Verhalten

Viele Abfragedienste und einige Entwurfszeitdienste, die als Singleton registriert wurden, werden jetzt als Scoped registriert.

Warum?

Die Lebensdauer musste geändert werden, damit ein neues Feature (DefaultTypeMapping) Einfluss auf Abfragen nehmen kann.

Die Lebensdauer von Entwurfszeitdiensten wurde an die Lebensdauer von Laufzeitdiensten angepasst, um Fehler zu vermeiden, wenn beide verwendet werden.

Gegenmaßnahmen

Verwenden Sie TryAdd, um EF Core-Dienste mit der Standardlebensdauer zu registrieren. Verwenden Sie TryAddProviderSpecificServices nur für Dienste, die nicht von EF hinzugefügt werden.

Neue Zwischenspeicherungs-API für Erweiterungen, die Dienste hinzufügen oder ersetzen

Nachverfolgung von Issue 19152

Altes Verhalten

In EF Core 5 hat GetServiceProviderHashCodelong zurückgegeben und wurde direkt als Teil des Cacheschlüssels für den Dienstanbieter verwendet.

Neues Verhalten

GetServiceProviderHashCode gibt jetzt int zurück und wird nur verwendet, um den Hashcode des Cacheschlüssels für den Dienstanbieter zu berechnen.

Außerdem muss ShouldUseSameServiceProvider implementiert werden, um anzugeben, ob das aktuelle Objekt die gleiche Dienstkonfiguration darstellt und daher denselben Dienstanbieter verwenden kann.

Warum?

Wird nur ein Hashcode als Teil des Cacheschlüssels verwendet, führte dies zu gelegentlichen Kollisionen, die schwer zu diagnostizieren und zu beheben waren. Die zusätzliche Methode stellt sicher, dass derselbe Dienstanbieter nur wenn angebracht verwendet wird.

Gegenmaßnahmen

Viele Erweiterungen machen keine Optionen verfügbar, die sich auf registrierte Dienste auswirken, und können die folgende Implementierung von ShouldUseSameServiceProvider verwenden:

private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
    public ExtensionInfo(IDbContextOptionsExtension extension)
        : base(extension)
    {
    }

    ...

    public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
        => other is ExtensionInfo;
}

Andernfalls sollten zusätzliche Prädikate hinzugefügt werden, um alle relevanten Optionen zu vergleichen.

Neue Initialisierungsprozedur für Momentaufnahmen und Entwurfszeitmodelle

Nachverfolgung von Issue 22031

Altes Verhalten

In EF Core 5 mussten bestimmte Konventionen aufgerufen werden, bevor das Momentaufnahmemodell verwendet werden konnte.

Neues Verhalten

IModelRuntimeInitializer wurde eingeführt, um einige der erforderlichen Schritte zu verbergen, und es wurde ein Laufzeitmodell eingeführt, das nicht über alle Migrationsmetadaten verfügt. Aus diesem Grund sollte das Entwurfszeitmodell für Modellvergleiche (Diff) verwendet werden.

Warum?

IModelRuntimeInitializer abstrahiert die Modellabschlussschritte, sodass diese jetzt ohne weitere Breaking Changes für die Benutzer*innen geändert werden können.

Das optimierte Laufzeitmodell wurde eingeführt, um die Laufzeitleistung zu verbessern. Es weist mehrere Optimierungen auf. Eine Optimierung entfernt Metadaten, die zur Laufzeit nicht verwendet werden.

Gegenmaßnahmen

Der folgende Codeausschnitt veranschaulicht, wie Sie überprüfen können, ob sich das aktuelle Modell vom Momentaufnahmemodell unterscheidet:

var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;

if (snapshotModel is IMutableModel mutableModel)
{
    snapshotModel = mutableModel.FinalizeModel();
}

if (snapshotModel != null)
{
    snapshotModel = context.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
}

var hasDifferences = context.GetService<IMigrationsModelDiffer>().HasDifferences(
    snapshotModel?.GetRelationalModel(),
    context.GetService<IDesignTimeModel>().Model.GetRelationalModel());

Dieser Codeausschnitt veranschaulicht, wie Sie IDesignTimeDbContextFactory<TContext> implementieren, indem Sie ein Modell extern erstellen und UseModel aufrufen:

internal class MyDesignContext : IDesignTimeDbContextFactory<MyContext>
{
    public TestContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder();
        optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DB"));

        var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder();
        CustomizeModel(modelBuilder);
        var model = modelBuilder.Model.FinalizeModel();

        var serviceContext = new MyContext(optionsBuilder.Options);
        model = serviceContext.GetService<IModelRuntimeInitializer>().Initialize(model);
        return new MyContext(optionsBuilder.Options);
    }
}

OwnedNavigationBuilder.HasIndex gibt jetzt einen anderen Typ zurück

Nachverfolgung von Issue 24005

Altes Verhalten

In EF Core 5 gab HasIndexIndexBuilder<TEntity> zurück, wobei TEntity der besitzende Typ ist.

Neues Verhalten

Jetzt gibt HasIndexIndexBuilder<TDependentEntity> zurück, wobei TDependentEntity der besitzende Typ ist.

Warum?

Das zurückgegebene Builder-Objekt wurde nicht ordnungsgemäß typisiert.

Gegenmaßnahmen

Das Neukompilieren Ihrer Assembly für die neueste Version von EF Core reicht aus, um alle durch diese Änderung verursachten Probleme zu beheben.

DbFunctionBuilder.HasSchema(null) überschreibt [DbFunction(Schema = "schema")]

Nachverfolgung von Issue 24228

Altes Verhalten

In EF Core 5 wurde beim Aufrufen von HasSchema mit dem Wert null die Konfigurationsquelle nicht gespeichert, daher konnte sie von DbFunctionAttribute überschrieben werden.

Neues Verhalten

Durch Aufrufen von HasSchema mit dem Wert null wird die Konfigurationsquelle jetzt gespeichert und verhindert, dass das Attribut sie überschreiben kann.

Warum?

Die mit der ModelBuilder-API angegebene Konfiguration sollte nicht durch Datenanmerkungen überschrieben werden können.

Gegenmaßnahmen

Entfernen Sie den HasSchema-Aufruf, damit das Schema durch das Attribut konfiguriert werden kann.

Vorab initialisierte Navigationen werden durch Werte aus Datenbankabfragen überschrieben

Nachverfolgung von Issue 23851

Altes Verhalten

Navigationseigenschaften, die auf ein leeres Objekt festgelegt waren, wurden für Nachverfolgungsabfragen unverändert gelassen, aber für andere Abfragen überschrieben. Berücksichtigen Sie beispielsweise folgende Entitätstypen:

public class Foo
{
    public int Id { get; set; }

    public Bar Bar { get; set; } = new(); // Don't do this.
}

public class Bar
{
    public int Id { get; set; }
}

Eine Abfrage ohne Nachverfolgung für Foo, die Bar einschloss, legte Foo.Bar auf die von der Datenbank abgefragte Entität fest. Ein Beispiel ist der folgende Code:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Ausgabe: Foo.Bar.Id = 1.

Dieselbe Abfrage, die für die Nachverfolgung ausgeführt wurde, hat Foo.Bar jedoch nicht mit der aus der Datenbank abgefragten Entität überschrieben. Ein Beispiel ist der folgende Code:

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Ausgabe: Foo.Bar.Id = 0.

Neues Verhalten

In EF Core 6.0 entspricht das Verhalten von Nachverfolgungsabfragen jetzt dem Verhalten von Abfragen ohne Nachverfolgung. Demnach führen der Code:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Und dieser Code:

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

zur Ausgabe Foo.Bar.Id = 1.

Warum?

Es gibt zwei Gründe für diese Änderung:

  1. Hiermit wird sichergestellt, dass Nachverfolgungsabfragen und Abfragen ohne Nachverfolgung ein konsistentes Verhalten zeigen.
  2. Wenn eine Datenbank abgefragt wird, kann davon ausgegangen werden, dass der Anwendungscode die in der Datenbank gespeicherten Werte zurückerlangen möchte.

Gegenmaßnahmen

Es gibt zwei Gegenmaßnahmen:

  1. Fragen Sie keine Objekte aus der Datenbank ab, die nicht in den Ergebnissen enthalten sein sollen. Verwenden Sie in den obigen Codeausschnitten beispielsweise nicht IncludeFoo.Bar, wenn die Bar-Instanz nicht aus der Datenbank zurückgegeben und in die Ergebnisse einbezogen werden soll.
  2. Legen Sie den Wert der Navigation nach der Abfrage aus der Datenbank fest. Rufen Sie beispielsweise foo.Bar = new() in den obigen Codeausschnitten erst nach dem Ausführen der Abfrage auf.

Erwägen Sie außerdem, verknüpfte Entitätsinstanzen nicht als Standardobjekte zu initialisieren. Dadurch wird impliziert, dass es sich bei der verknüpften Instanz um eine neue Entität handelt, die nicht in der Datenbank gespeichert und für die kein Schlüsselwert festgelegt ist. Wenn die verknüpfte Entität jedoch in der Datenbank vorhanden ist, stehen die Daten im Code in einem grundsätzlichen Konflikt mit den in der Datenbank gespeicherten Daten.

Unbekannte Enumerationszeichenfolgenwerte in der Datenbank werden beim Abfragen nicht in den Enumerationsstandard konvertiert

Nachverfolgung von Issue 24084

Altes Verhalten

Enumerationseigenschaften können Zeichenfolgenspalten in der Datenbank mithilfe von HasConversion<string>() oder EnumToStringConverter zugeordnet werden. Dies führt dazu, dass EF Core Zeichenfolgenwerte in der Spalte in entsprechende Member des .NET-Enumerationstyps konvertiert. Wenn der Zeichenfolgenwert jedoch mit keinem Enumerationsmember übereinstimmte, wurde die Eigenschaft auf den Standardwert für die Enumeration festgelegt.

Neues Verhalten

EF Core 6.0 löst jetzt eine InvalidOperationException mit der Meldung aus, dass der Zeichenfolgenwert {value} aus der Datenbank nicht in einen Wert in der zugeordneten {enumType}-Enumeration konvertiert werden kann.

Warum?

Die Konvertierung in den Standardwert kann zu einer Beschädigung der Datenbank führen, wenn die Entität später wieder in die Datenbank zurück gespeichert wird.

Gegenmaßnahmen

Stellen Sie im Idealfall sicher, dass die Datenbankspalte nur gültige Werte enthält. Implementieren Sie alternativ einen ValueConverter mit dem alten Verhalten.

DbFunctionBuilder.HasTranslation stellt jetzt die Funktionsargumente als IReadOnlyList statt als IReadOnlyCollection bereit

Nachverfolgung von Issue 23565

Altes Verhalten

Beim Konfigurieren der Übersetzung für eine benutzerdefinierte Funktion mithilfe der HasTranslation-Methode wurden die Argumente für die Funktion als IReadOnlyCollection<SqlExpression> bereitgestellt.

Neues Verhalten

In EF Core 6.0 werden die Argumente jetzt als IReadOnlyList<SqlExpression> bereitgestellt.

Warum?

IReadOnlyList ermöglicht die Verwendung von Indexern, sodass jetzt einfacher auf die Argumente zugegriffen werden kann.

Gegenmaßnahmen

Keine. IReadOnlyList implementiert die IReadOnlyCollection-Schnittstelle, daher sollte der Übergang problemlos verlaufen.

Die Standardtabellenzuordnung wird nicht entfernt, wenn die Entität einer Tabellenwertfunktion zugeordnet wird

Nachverfolgung von Issue 23408

Altes Verhalten

Beim Zuordnen einer Entität zu einer Tabellenwertfunktion wurde die Standardzuordnung zu einer Tabelle entfernt.

Neues Verhalten

In EF Core 6.0 wird die Entität über die Standardzuordnung weiterhin einer Tabelle zugeordnet, selbst wenn sie einer Tabellenwertfunktion zugeordnet ist.

Warum?

Tabellenwertfunktionen, die Entitäten zurückgeben, werden häufig nicht als strikte Ersetzung der gesamten Tabelle verwendet, sondern entweder als Hilfsprogramm oder zum Kapseln eines Vorgangs, der eine Auflistung von Entitäten zurückgibt. Diese Änderung soll die wahrscheinliche Benutzerabsicht besser abbilden.

Gegenmaßnahmen

Die Zuordnung zu einer Tabelle kann in der Modellkonfiguration explizit deaktiviert werden:

modelBuilder.Entity<MyEntity>().ToTable((string)null);

dotnet-ef verwendet .NET 6 als Ziel

Nachverfolgung von Issue 27787

Altes Verhalten

Der Befehl „dotnet-ef“ verwendet schon seit einer Weile .NET Core 3.1 als Ziel. Dadurch konnten neuere Version des Tools verwendet werden, ohne neuere Versionen der .NET-Runtime zu installieren.

Neues Verhalten

In EF Core 6.0.6 verwendet das Tool „dotnet-ef“ nun .NET 6 als Ziel. Die Verwendung in Projekten, die ältere Versionen von .NET und .NET Core als Ziel verwenden, ist weiterhin möglich. Für die Ausführung des Tools muss jedoch die .NET 6-Runtime installiert werden.

Warum?

In Version 6.0.200 des .NET SDK wurde das Verhalten von dotnet tool install in osx-arm64 aktualisiert, damit ein osx-x64-Shim für Tools erstellt wird, die .NET Core 3.1 als Ziel verwenden. Um die gewohnte Funktionsweise von dotnet-ef aufrechtzuerhalten, mussten wir das Ziel auf .NET 6 aktualisieren.

Gegenmaßnahmen

Sie können dotnet-ef ohne Installation der .NET 6-Runtime ausführen, indem Sie eine ältere Version des Tools installieren:

dotnet tool install dotnet-ef --version 3.1.*

IModelCacheKeyFactory-Implementierungen müssen möglicherweise aktualisiert werden, um die Entwurfszeitzwischenspeicherung zu verarbeiten.

Nachverfolgung von Issue 25154

Altes Verhalten

IModelCacheKeyFactory hatte keine Möglichkeit, das Entwurfszeitmodell separat vom Laufzeitmodell zwischenzuspeichern.

Neues Verhalten

IModelCacheKeyFactory verfügt über eine neue Überladung, mit der das Entwurfszeitmodell separat vom Laufzeitmodell zwischengespeichert werden kann. Die Implementierung dieser Methode kann zu einer Ausnahme führen, die etwa wie folgt lautet:

System.InvalidOperationException: „Die angeforderte Konfiguration wird nicht im leseoptimierten Modell gespeichert. Verwenden Sie bitte ‚DbContext.GetService<IDesignTimeModel>().Model‘.“

Warum?

Die Implementierung kompilierter Modelle erforderte eine Trennung der Entwurfszeit (verwendet beim Erstellen des Modells) und der Laufzeitmodelle (die beim Ausführen von Abfragen usw.) verwendet werden. Wenn der Laufzeitcode Zugriff auf Entwurfszeitinformationen benötigt, muss das Entwurfszeitmodell zwischengespeichert werden.

Gegenmaßnahmen

Implementieren Sie die neue Überladung. Beispiel:

public object Create(DbContext context, bool designTime)
    => context is DynamicContext dynamicContext
        ? (context.GetType(), dynamicContext.UseIntProperty, designTime)
        : (object)context.GetType();