Breaking Changes in EF Core 8 (EF8)

Auf dieser Seite werden API-Änderungen und Behavior Changes dokumentiert, die bei einem Update von EF Core 7 auf EF Core 8 zu Problemen mit bestehenden Anwendungen führen können. Überprüfen Sie frühere grundlegende Änderungen, wenn sie von einer früheren Version von EF Core aktualisiert werden:

Zielframework

EF Core 8 zielt auf .NET 8 ab. Anwendungen für ältere .NET-, .NET Core- und .NET Framework-Versionen müssen auf .NET 8 aktualisiert werden.

Zusammenfassung

Wichtige Änderung Auswirkung
Contains in LINQ-Abfragen funktioniert möglicherweise nicht mehr auf älteren SQL Server-Versionen Hoch
Enumerationen in JSON werden standardmäßig als Ganzzahlen statt als Zeichenfolgen gespeichert Hoch
SQL Serverdate und time fügen ein Gerüst zu .NET DateOnly und TimeOnly hinzu Medium
Boolesche Spalten mit einem von der Datenbank generierten Wert werden nicht mehr als Nullwerte zulassend gerüstet Medium
SQLite-Math-Methoden werden jetzt in SQL übersetzt Niedrig
ITypeBase ersetzt IEntityType in einigen APIs Niedrig
ValueGenerator-Ausdrücke müssen öffentliche APIs verwenden Niedrig
ExcludeFromMigrations schließt andere Tabellen in einer TPC-Hierarchie nicht mehr aus Niedrig
Ganzzahlige Schlüssel ohne Schatten werden in Cosmos-Dokumenten beibehalten Niedrig
Relationales Modell wird im kompilierten Modell generiert Niedrig
Gerüstbau kann verschiedene Navigationsnamen generieren Niedrig
Für Diskriminatoren gilt jetzt eine maximale Länge Niedrig
Beim Vergleich von SQL Server-Schlüsselwerten wird die Groß-/Kleinschreibung nicht berücksichtigt Niedrig

Änderungen mit hoher Auswirkung

Contains in LINQ-Abfragen funktioniert möglicherweise nicht mehr auf älteren SQL Server-Versionen

Nachverfolgung von Issue #13617

Altes Verhalten

Wenn früher der Contains-Operator in LINQ-Abfragen mit einer parametrisierten Werteliste verwendet wurde, generierte EF ein ineffizientes SQL, das jedoch auf allen SQL Server-Versionen funktionierte.

Neues Verhalten

Ab EF Core 8.0 generiert EF jetzt SQL, die effizienter ist, aber unter SQL Server 2014 und tiefer nicht unterstützt wird.

Beachten Sie, dass neuere SQL Server-Versionen möglicherweise mit einer älteren Kompatibilitätsstufe konfiguriert werden, wodurch sie ebenfalls nicht mit dem neuen SQL kompatibel sind. Dies kann auch mit einer Azure SQL-Datenbank auftreten, die von einer früheren lokalen SQL Server-Instanz migriert wurde, wobei die alte Kompatibilitätsstufe übernommen wird.

Warum?

Die bisherige von EF Core für Contains generierte SQL fügte die parametrisierten Werte als Konstanten in die SQL ein. Beispiel: Die folgende LINQ-Abfrage:

var names = new[] { "Blog1", "Blog2" };

var blogs = await context.Blogs
    .Where(b => names.Contains(b.Name))
    .ToArrayAsync();

... würde in die folgende SQL übersetzt werden:

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')

Eine solche Einfügung konstanter Werte in SQL führt zu zahlreichen Leistungsproblemen, beeinträchtigt das Zwischenspeichern von Abfrageplänen und führt zu unnötigen Ausschlüssen anderer Abfragen. Die neue EF Core 8.0-Übersetzung verwendet die SQL Server-Funktion OPENJSON, um stattdessen die Werte als JSON-Array zu übertragen. Dadurch werden die Leistungsprobleme gelöst, die der vorherigen Technik vorhanden sind. Die OPENJSON-Funktion ist jedoch in SQL Server 2014 und tiefer nicht verfügbar.

Weitere Informationen zu dieser Änderung finden Sie in diesem Blogbeitrag.

Gegenmaßnahmen

Wenn Ihre Datenbank ein SQL Server 2016 (13.x) oder höher ist, oder wenn Sie Azure SQL verwenden, überprüfen Sie die konfigurierte Kompatibilitätsstufe Ihrer Datenbank mit dem folgenden Befehl:

SELECT name, compatibility_level FROM sys.databases;

Wenn die Kompatibilitätsstufe unter 130 (SQL Server 2016) liegt, sollten Sie diese in einen neueren Wert (Dokumentation) ändern.

Wenn Ihre Datenbankversion tatsächlich älter als SQL Server 2016 ist oder auf eine alte Kompatibilitätsstufe festgelegt ist, die Sie aus irgendeinem Grund nicht ändern können, konfigurieren Sie EF Core wie folgt, um auf das ältere, weniger effiziente SQL zurückzugreifen:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));

Enumerationen in JSON werden standardmäßig als Ganzzahlen statt als Zeichenfolgen gespeichert

Nachverfolgung von Issue #13617

Altes Verhalten

In EF7 werden Enumerationen, die JSON zugeordnet sind, standardmäßig als Zeichenfolgenwerte im JSON-Dokument gespeichert.

Neues Verhalten

Ab EF Core 8.0 ordnet EF jetzt standardmäßig Enumerationen ganzzahligen Werten im JSON-Dokument zu.

Warum?

EF weist Enumerationen standardmäßig einer numerischen Spalte in relationalen Datenbanken zu. Da EF Abfragen unterstützt, bei denen Werte aus JSON mit Werten aus Spalten und Parametern interagieren, ist es wichtig, dass die Werte in JSON mit den Werten in der Nicht-JSON-Spalte übereinstimmen.

Gegenmaßnahmen

Um weiterhin Zeichenfolgen zu verwenden, konfigurieren Sie die Enumerationseigenschaft mit einer Konvertierung. Beispiel:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().Property(e => e.Status).HasConversion<string>();
}

Oder für alle Eigenschaften des Enumerationstyps:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<StatusEnum>().HaveConversion<string>();
}

Änderungen mit mittlerer Auswirkung

SQL Server date und time fügen ein Gerüst zu .NET DateOnly und TimeOnly hinzu

Nachverfolgung von Issue 24507

Altes Verhalten

Wenn Sie früher ein Gerüstbau für eine SQL Server-Datenbank mit date- oder time-Spalten durchgeführt hätten, würde EF Entitätseigenschaften mit den Typen DateTime und TimeSpan erstellen.

Neues Verhalten

Ab EF Core 8.0 werden date und time als Gerüst DateOnly und TimeOnly aufgebaut.

Warum?

DateOnly und TimeOnly wurden in .NET 6.0 eingeführt und passen perfekt zur Zuordnung der date- und time-Typen der Datenbank. DateTime enthält insbesondere eine Zeitkomponente, die nicht verwendet wird und zu Verwirrung führen kann, wenn sie date zugeordnet wird. TimeSpan stellt ein Zeitintervall dar – möglicherweise einschließlich Tage – anstelle einer Tageszeit, zu der ein Ereignis eintritt. Die Verwendung der neuen Typen verhindert Fehler und Verwirrung und bietet Klarheit über die Absicht.

Gegenmaßnahmen

Diese Änderung wirkt sich nur auf Benutzer*innen aus, die ihre Datenbank regelmäßig in einem EF-Codemodell („Database-First“-Flow) neu erstellen.

Es wird empfohlen, auf diese Änderung zu reagieren, indem Sie Ihren Code so ändern, dass die neu erstellten DateOnly und TimeOnly-Typen verwendet werden. Wenn dies jedoch nicht möglich ist, können Sie die Gerüstvorlagen bearbeiten, um das System auf die vorherige Zuordnung zurückzusetzen. Richten Sie dazu die Vorlagen wie auf dieser Seite beschrieben ein. Bearbeiten Sie dann die Datei EntityType.t4, suchen Sie nach dem Ort, wo die Entitätseigenschaften generiert werden (suchen Sie nach property.ClrType), und ändern Sie den Code wie folgt:

        var clrType = property.GetColumnType() switch
        {
            "date" when property.ClrType == typeof(DateOnly) => typeof(DateTime),
            "date" when property.ClrType == typeof(DateOnly?) => typeof(DateTime?),
            "time" when property.ClrType == typeof(TimeOnly) => typeof(TimeSpan),
            "time" when property.ClrType == typeof(TimeOnly?) => typeof(TimeSpan?),
            _ => property.ClrType
        };

        usings.AddRange(code.GetRequiredUsings(clrType));

        var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !clrType.IsValueType;
        var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !clrType.IsValueType;
#>
    public <#= code.Reference(clrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#

Boolesche Spalten mit einem von der Datenbank generierten Wert werden nicht mehr als Nullwerte zulassend gerüstet

Nachverfolgung von Issue 15070

Altes Verhalten

Zuvor wurden Non-Nullable-bool-Spalten mit einer Datenbankstandardeinschränkung als Nullwerte zulassende bool?-Eigenschaften gerüstet.

Neues Verhalten

Ab EF Core 8.0 werden Non-Nullable-bool-Spalten immer als Non-Nullable-Eigenschaften gerüstet.

Warum?

Der Wert einer bool-Eigenschaft wird nicht an die Datenbank gesendet, wenn dieser Wert false ist, was der CLR-Standardeinstellung entspricht. Wenn die Datenbank über einen Standardwert von true für die Spalte verfügt, ist der Wert in der Datenbank auch dann true, wenn der Wert der Eigenschaft false ist. In EF8 ermittelt der Sentinel jedoch, ob eine Eigenschaft einen Wert aufweist, der geändert werden kann. Dies erfolgt automatisch für bool-Eigenschaften mit einem von der Datenbank generierten Wert von true, was bedeutet, dass es nicht mehr erforderlich ist, die Eigenschaften als Nullwerte zulassend zu erstellen.

Gegenmaßnahmen

Diese Änderung wirkt sich nur auf Benutzer*innen aus, die ihre Datenbank regelmäßig in einem EF-Codemodell („Database-First“-Flow) neu erstellen.

Es wird empfohlen, auf diese Änderung zu reagieren, indem Sie Ihren Code so ändern, dass die boolesche Non-Nullable-Eigenschaft verwendet. Wenn dies jedoch nicht möglich ist, können Sie die Gerüstvorlagen bearbeiten, um das System auf die vorherige Zuordnung zurückzusetzen. Richten Sie dazu die Vorlagen wie auf dieser Seite beschrieben ein. Bearbeiten Sie dann die Datei EntityType.t4, suchen Sie nach dem Ort, wo die Entitätseigenschaften generiert werden (suchen Sie nach property.ClrType), und ändern Sie den Code wie folgt:

#>
        var propertyClrType = property.ClrType != typeof(bool)
                              || (property.GetDefaultValueSql() == null && property.GetDefaultValue() != null)
            ? property.ClrType
            : typeof(bool?);
#>
    public <#= code.Reference(propertyClrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
<#

Änderungen mit geringer Auswirkung

SQLite-Math-Methoden werden jetzt in SQL übersetzt

Nachverfolgung von Issue #18843

Altes Verhalten

Zuvor wurden nur die Methoden Abs, Max, Min und Round auf Math in SQL übersetzt. Alle anderen Member würden auf dem Client ausgewertet, wenn sie im endgültigen Select-Ausdruck einer Abfrage erscheinen.

Neues Verhalten

In EF Core 8.0 werden alle Math-Methoden mit entsprechenden mathematischen SQLite-Funktionen in SQL übersetzt.

Diese mathematischen Funktionen wurden in der nativen SQLite-Bibliothek aktiviert, die wir standardmäßig bereitstellen (durch unsere Abhängigkeit vom NuGet-Paket SQLitePCLRaw.bundle_e_sqlite3). Sie wurden auch in der Bibliothek aktiviert, die von SQLitePCLRaw.bundle_e_sqlcipher bereitgestellt wird. Wenn Sie eine dieser Bibliotheken verwenden, sollte Ihre Anwendung von dieser Änderung nicht betroffen sein.

Es besteht jedoch die Möglichkeit, dass Anwendungen, welche die native SQLite-Bibliothek auf andere Weise einschließen, die mathematischen Funktionen möglicherweise nicht aktivieren. In diesen Fällen werden die Math-Methoden in SQL übersetzt und treffen bei der Ausführung auf den Fehler keine solche Funktion.

Warum?

SQLite hat integrierte mathematische Funktionen in Version 3.35.0 hinzugefügt. Auch wenn sie standardmäßig deaktiviert sind, sind sie unterdessen so weit verbreitet, dass wir beschlossen haben, Standardübersetzungen für sie in unserem EF Core SQLite-Anbieter bereitzustellen.

Wir haben auch mit Eric Sink am SQLitePCLRaw-Projekt zusammengearbeitet, um mathematische Funktionen in allen nativen SQLite-Bibliotheken zu aktivieren, die im Rahmen dieses Projekts bereitgestellt werden.

Gegenmaßnahmen

Die einfachste Möglichkeit zum Beheben von Unterbrechungen ist es, wenn möglich die mathematische Funktion in der nativen SQLite-Bibliothek zu aktivieren, indem Sie die Kompilierzeitoption SQLITE_ENABLE_MATH_FUNCTIONS angeben.

Wenn Sie nicht die Kompilierung der nativen Bibliothek steuern, können Sie Unterbrechungen auch beheben, indem Sie die Funktionen zur Laufzeit mithilfe der Microsoft.Data.Sqlite-APIs selber erstellen.

sqliteConnection
    .CreateFunction<double, double, double>(
        "pow",
        Math.Pow,
        isDeterministic: true);

Alternativ können Sie die Clientauswertung erzwingen, indem Sie den Select-Ausdruck in zwei durch AsEnumerable getrennte Teile trennen.

// Before
var query = dbContext.Cylinders
    .Select(
        c => new
        {
            Id = c.Id
            // May throw "no such function: pow"
            Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
        });

// After
var query = dbContext.Cylinders
    // Select the properties you'll need from the database
    .Select(
        c => new
        {
            c.Id,
            c.Radius,
            c.Height
        })
    // Switch to client-eval
    .AsEnumerable()
    // Select the final results
    .Select(
        c => new
        {
            Id = c.Id,
            Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
        });

ITypeBase ersetzt IEntityType in einigen APIs

Nachverfolgung von Issue 13947

Altes Verhalten

Zuvor waren alle zugeordneten Strukturtypen Entitätstypen.

Neues Verhalten

Mit der Einführung komplexer Typen in EF8 verwenden einige APIs, die zuvor einen IEntityType verwendeten, jetzt ITypeBase, damit die APIs entweder mit Entitätstypen oder komplexen Typen verwendet werden können. Dies umfasst u. a.:

  • IProperty.DeclaringEntityType ist jetzt veraltet und IProperty.DeclaringType sollte stattdessen verwendet werden.
  • IEntityTypeIgnoredConvention ist jetzt veraltet und ITypeIgnoredConvention sollte stattdessen verwendet werden.
  • IValueGeneratorSelector.Select akzeptiert nun eine ITypeBase, die ein IEntityType sein kann, aber nicht sein muss.

Warum?

Mit der Einführung komplexer Typen in EF8 können diese APIs entweder mit IEntityType oder mit IComplexType verwendet werden.

Gegenmaßnahmen

Die alten APIs sind veraltet, werden jedoch erst nach EF10 entfernt. Der Code sollte aktualisiert werden, um die neuen APIs schnellstmöglich zu verwenden.

ValueConverter- und ValueComparer-Ausdrücke müssen öffentliche APIs für das kompilierte Modell verwenden.

Nachverfolgung von Issue 24896

Altes Verhalten

Zuvor waren ValueConverter- und ValueComparer-Definitionen nicht im kompilierten Modell enthalten und konnten somit beliebigen Code enthalten.

Neues Verhalten

EF extrahiert nun die Ausdrücke aus den ValueConverter- und ValueComparer-Objekten und schließt diese C# in das kompilierte Modell ein. Das bedeutet, dass diese Ausdrücke nur öffentliche API verwenden dürfen.

Warum?

Das EF-Team verschiebt schrittweise mehr Konstrukte in das kompilierte Modell, um in Zukunft die Verwendung von EF Core mit AOT zu unterstützen.

Gegenmaßnahmen

Machen Sie die APIs, die vom Comparer verwendet werden, öffentlich. Betrachten Sie als Beispiel diesen einfachen Konverter:

public class MyValueConverter : ValueConverter<string, byte[]>
{
    public MyValueConverter()
        : base(v => ConvertToBytes(v), v => ConvertToString(v))
    {
    }

    private static string ConvertToString(byte[] bytes)
        => ""; // ... TODO: Conversion code

    private static byte[] ConvertToBytes(string chars)
        => Array.Empty<byte>(); // ... TODO: Conversion code
}

Um diesen Konverter in einem kompilierten Modell mit EF8 zu verwenden, müssen die ConvertToString- und ConvertToBytes-Methoden öffentlich gemacht werden. Beispiel:

public class MyValueConverter : ValueConverter<string, byte[]>
{
    public MyValueConverter()
        : base(v => ConvertToBytes(v), v => ConvertToString(v))
    {
    }

    public static string ConvertToString(byte[] bytes)
        => ""; // ... TODO: Conversion code

    public static byte[] ConvertToBytes(string chars)
        => Array.Empty<byte>(); // ... TODO: Conversion code
}

ExcludeFromMigrations schließt andere Tabellen in einer TPC-Hierarchie nicht mehr aus

Nachverfolgung von Issue 30079

Altes Verhalten

Zuvor hat die Verwendung von ExcludeFromMigrations in einer Tabelle in einer TPC-Hierarchie auch andere Tabellen in der Hierarchie ausgeschlossen.

Neues Verhalten

Ab EF Core 8.0 wirkt sich ExcludeFromMigrations nicht auf andere Tabellen aus.

Warum?

Das alte Verhalten war ein Fehler und verhinderte, dass Migrationen verwendet wurden, um Hierarchien über Projekte hinweg zu verwalten.

Gegenmaßnahmen

Verwenden Sie ExcludeFromMigrations explizit für jede andere Tabelle, die ausgeschlossen werden soll.

Ganzzahlige Schlüssel ohne Schatten werden in Cosmos-Dokumenten beibehalten

Nachverfolgung von Issue 31664

Altes Verhalten

Bisher wurden ganzzahlige Eigenschaften ohne Schatten, die die Kriterien für eine synthetisierte Schlüsseleigenschaft erfüllen, nicht in das JSON-Dokument übernommen, sondern auf dem Weg neu synthetisiert.

Neues Verhalten

Ab EF Core 8.0 werden diese Eigenschaften nun beibehalten.

Warum?

Das alte Verhalten war ein Fehler und verhinderte, dass Eigenschaften, die den synthetisierten Schlüsselkriterien entsprechen, auf Cosmos beibehalten wurden.

Gegenmaßnahmen

Schließen Sie die Eigenschaft aus dem Modell aus, wenn der Wert nicht beibehalten werden soll. Außerdem können Sie dieses Verhalten vollständig deaktivieren, indem Sie den Microsoft.EntityFrameworkCore.Issue31664 AppContext-Schalter auf true setzen, siehe AppContext für Bibliothekskonsumenten für weitere Details.

AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);

Relationales Modell wird im kompilierten Modell generiert

Nachverfolgung von Issue 24896

Altes Verhalten

Zuvor wurde das relationale Modell zur Laufzeit auch bei Verwendung eines kompilierten Modells berechnet.

Neues Verhalten

Ab EF Core 8.0 ist das relationale Modell Teil des generierten kompilierten Modells. Bei besonders großen Modellen kann die generierte Datei jedoch nicht kompiliert werden.

Warum?

Dies wurde gemacht, um die Startzeit weiter zu verbessern.

Gegenmaßnahmen

Bearbeiten Sie die generierte *ModelBuilder.cs-Datei und entfernen Sie die Zeile AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel()); sowie die Methode CreateRelationalModel().

Gerüstbau kann verschiedene Navigationsnamen generieren

Nachverfolgung von Issue 27832

Altes Verhalten

Wenn früher ein Gerüst aus einem DbContext-Element und Entitätstypen aus einer vorhandenen Datenbank erstellt wurden, wurden die Navigationsnamen für Beziehungen manchmal von einem gemeinsamen Präfix mehrerer Fremdschlüsselspaltennamen abgeleitet.

Neues Verhalten

Ab EF Core 8.0 werden gemeinsame Präfixe von Spaltennamen aus einem zusammengesetzten Fremdschlüssel nicht mehr zum Generieren von Navigationsnamen verwendet.

Warum?

Dies ist eine undurchsichtige Benennungsregel, die manchmal sehr ungünstige Namen wie S, Student_ oder sogar nur_ generiert. Ohne diese Regel werden keine seltsamen Namen mehr generiert, und die Namenskonventionen für Navigationen werden ebenfalls vereinfacht, wodurch leichter zu verstehen und vorherzusagen ist, welche Namen generiert werden.

Gegenmaßnahmen

Die EF Core Power Tools bieten die Möglichkeit, Navigationen weiterhin auf die alte Weise zu generieren. Alternativ kann der generierte Code mithilfe von T4-Vorlagen vollständig angepasst werden. Dies kann als Beispiel für die Fremdschlüsseleigenschaften von Gerüstbaubeziehungen verwendet werden und jede beliebige Regel verwenden, die für Ihren Code geeignet ist, um die von Ihnen benötigten Navigationsnamen zu generieren.

Für Diskriminatoren gilt jetzt eine maximale Länge

Nachverfolgung von Issue 10691

Altes Verhalten

Zuvor wurden Diskriminatorspalten, die für TPH-Vererbungsmapping erstellt wurden, in SQL Server/Azure SQL als nvarchar(max) oder in anderen Datenbanken als äquivalenter ungebundener Zeichenfolgentyp konfiguriert.

Neues Verhalten

Ab EF Core 8.0 werden Diskriminatorspalten mit einer maximalen Länge erstellt, die alle bekannten Diskriminatorwerte abdeckt. EF generiert eine Migration, um diese Änderung vorzunehmen. Wenn die Diskriminatorspalte jedoch in irgendeiner Weise eingeschränkt ist – z. B. als Teil eines Index –, kann die durch Migrationen erstellte AlterColumn-Spalte fehlschlagen.

Warum?

nvarchar(max)-Spalten sind ineffizient und unnötig, wenn die Länge aller möglichen Werte bekannt ist.

Gegenmaßnahmen

Die Spaltengröße kann explizit als ungebunden festgelegt werden:

modelBuilder.Entity<Foo>()
    .Property<string>("Discriminator")
    .HasMaxLength(-1);

Beim Vergleich von SQL Server-Schlüsselwerten wird die Groß-/Kleinschreibung nicht berücksichtigt

Nachverfolgung von Issue 27526

Altes Verhalten

Bisher wurden beim Nachverfolgen von Entitäten mit Zeichenfolgenschlüsseln mit den SQL Server/Azure SQL-Datenbankanbietern die Schlüsselwerte mithilfe der standardmäßigen ordinalen Vergleichsfunktion von .NET verglichen, die Groß-/Kleinschreibung berücksichtigt.

Neues Verhalten

Ab EF Core 8.0 werden SQL Server/Azure SQL-Zeichenfolgenschlüsselwerte mithilfe der standardmäßigen ordinalen Vergleichsfunktion von .NET verglichen, die Groß-/Kleinschreibung ignoriert.

Warum?

Standardmäßig unterscheidet SQL Server bei Vergleichen von Fremdschlüsselwerten mit Prinzipalschlüsselwerten nicht zwischen Groß-/Kleinschreibung. Das bedeutet: Wenn EF die Groß-/Kleinschreibung beim Vergleichen berücksichtigt, kann es sein, dass ein Fremdschlüssel nicht mit einem Prinzipalschlüssel verbunden wird, obwohl dies der Fall sein sollte.

Gegenmaßnahmen

Vergleiche mit Berücksichtigung von Groß- und Kleinschreibung können durch Festlegen eines benutzerdefinierten ValueComparer verwendet werden. Zum Beispiel:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var comparer = new ValueComparer<string>(
        (l, r) => string.Equals(l, r, StringComparison.Ordinal),
        v => v.GetHashCode(),
        v => v);

    modelBuilder.Entity<Blog>()
        .Property(e => e.Id)
        .Metadata.SetValueComparer(comparer);

    modelBuilder.Entity<Post>(
        b =>
        {
            b.Property(e => e.Id).Metadata.SetValueComparer(comparer);
            b.Property(e => e.BlogId).Metadata.SetValueComparer(comparer);
        });
}