Modifiche di rilievo in EF Core 8 (EF8)

Questa pagina illustra le modifiche all'API e al comportamento che potrebbero interrompere l'aggiornamento delle applicazioni esistenti da EF Core 7 a EF Core 8. Assicurarsi di esaminare le modifiche di rilievo precedenti se si esegue l'aggiornamento da una versione precedente di EF Core:

Framework di destinazione

EF Core 8 è destinato a .NET 8. Le applicazioni destinate a versioni precedenti di .NET, .NET Core e .NET Framework dovranno eseguire l'aggiornamento per .NET 8.

Riepilogo

Modifica di rilievo Impatto
Contains nelle query LINQ potrebbe smettere di funzionare nelle versioni precedenti di SQL Server Alto
Le enumerazioni in JSON vengono archiviate come ints anziché come stringhe per impostazione predefinita Alto
SQL Server date e time ora eseguire lo scaffolding in .NET DateOnly e TimeOnly Medio
Le colonne booleane con un valore generato dal database non vengono più scaffolding come nullable Medio
I metodi SQLite Math ora si traducono in SQL Basso
ITypeBase sostituisce IEntityType in alcune API Basso
Le espressioni ValueGenerator devono usare LE API pubbliche Basso
ExcludeFromMigrations non esclude più altre tabelle in una gerarchia TPC Basso
Le chiavi integer non shadow vengono mantenute nei documenti Cosmos Basso
Il modello relazionale viene generato nel modello compilato Basso
Lo scaffolding può generare nomi di navigazione diversi Basso
I discriminatori hanno ora una lunghezza massima Basso
I valori delle chiavi di SQL Server vengono confrontati senza distinzione tra maiuscole e minuscole Basso

Modifiche ad alto impatto

Contains nelle query LINQ potrebbe smettere di funzionare nelle versioni precedenti di SQL Server

Problema di rilevamento n. 13617

Comportamento precedente

In precedenza, quando l'operatore Contains veniva usato nelle query LINQ con un elenco di valori con parametri, EF generava SQL non efficiente ma funzionava su tutte le versioni di SQL Server.

Nuovo comportamento

A partire da EF Core 8.0, EF genera ora SQL più efficiente, ma non è supportato in SQL Server 2014 e versioni successive.

Si noti che le versioni più recenti di SQL Server possono essere configurate con un livello di compatibilità precedente, rendendole incompatibili anche con il nuovo SQL. Ciò può verificarsi anche con un database SQL di Azure di cui è stata eseguita la migrazione da un'istanza di SQL Server locale precedente, con il livello di compatibilità precedente.

Perché

Sql precedente generato da EF Core per Contains inserire i valori con parametri come costanti in SQL. Ad esempio, la query LINQ seguente:

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

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

... verrà convertito nel codice SQL seguente:

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

Tale inserimento di valori costanti in SQL crea molti problemi di prestazioni, sconfiggendo la memorizzazione nella cache del piano di query e causando eliminazioni non richieste di altre query. La nuova conversione di EF Core 8.0 usa la funzione SQL Server OPENJSON per trasferire invece i valori come matrice JSON. Questo risolve i problemi di prestazioni intrinseci nella tecnica precedente; Tuttavia, la OPENJSON funzione non è disponibile in SQL Server 2014 e versioni successive.

Per altre informazioni su questa modifica, vedere questo post di blog.

Soluzioni di prevenzione

Se il database è SQL Server 2016 (13.x) o versione successiva o se si usa Azure SQL, controllare il livello di compatibilità configurato del database tramite il comando seguente:

SELECT name, compatibility_level FROM sys.databases;

Se il livello di compatibilità è inferiore a 130 (SQL Server 2016), è consigliabile modificarlo in un valore più recente (documentazione).

In caso contrario, se la versione del database è effettivamente precedente a SQL Server 2016 o è impostata su un livello di compatibilità precedente che non è possibile modificare per qualche motivo, configurare EF Core per ripristinare sql meno recente, meno efficiente come indicato di seguito:

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

Le enumerazioni in JSON vengono archiviate come ints anziché come stringhe per impostazione predefinita

Problema di rilevamento n. 13617

Comportamento precedente

In EF7 le enumerazioni mappate a JSON sono, per impostazione predefinita, archiviate come valori stringa nel documento JSON.

Nuovo comportamento

A partire da EF Core 8.0, EF ora, per impostazione predefinita, esegue il mapping delle enumerazioni ai valori interi nel documento JSON.

Perché

Ef ha sempre, per impostazione predefinita, enumerazioni mappate a una colonna numerica nei database relazionali. Poiché EF supporta le query in cui i valori di JSON interagiscono con valori di colonne e parametri, è importante che i valori in JSON corrispondano ai valori nella colonna non JSON.

Soluzioni di prevenzione

Per continuare a usare le stringhe, configurare la proprietà enumerazione con una conversione. Ad esempio:

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

In alternativa, per tutte le proprietà del tipo di enumerazione::

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

Modifiche a impatto medio

SQL Server date e time ora eseguire lo scaffolding in .NET DateOnly e TimeOnly

Problema di rilevamento n. 24507

Comportamento precedente

In precedenza, quando si esegue lo scaffolding di un database di SQL Server con date colonne o time , Entity Framework generava proprietà di entità con tipi DateTime e TimeSpan.

Nuovo comportamento

A partire da EF Core 8.0 date e time vengono scaffolding come DateOnly e TimeOnly.

Perché

DateOnly e TimeOnly sono stati introdotti in .NET 6.0 e sono una corrispondenza perfetta per il mapping dei tipi di data e ora del database. DateTime contiene in particolare un componente temporale che passa inutilizzato e può causare confusione durante il mapping a datee TimeSpan rappresenta un intervallo di tempo, possibilmente inclusi i giorni, anziché un'ora del giorno in cui si verifica un evento. L'uso dei nuovi tipi impedisce bug e confusione e fornisce chiarezza sulla finalità.

Soluzioni di prevenzione

Questa modifica influisce solo sugli utenti che eseguono regolarmente lo scaffolding del database in un modello di codice EF ("flusso database-first").

È consigliabile reagire a questa modifica modificando il codice per usare i nuovi tipi e TimeOnly scaffoldingDateOnly. Tuttavia, se non è possibile, è possibile modificare i modelli di scaffolding per ripristinare il mapping precedente. A tale scopo, configurare i modelli come descritto in questa pagina. Modificare quindi il EntityType.t4 file, individuare dove vengono generate le proprietà dell'entità (cercare property.ClrType) e modificare il codice come segue:

        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!;" : "" #>
<#

Le colonne booleane con un valore generato dal database non vengono più scaffolding come nullable

Problema di rilevamento n. 15070

Comportamento precedente

In precedenza, le colonne non nullable bool con un vincolo predefinito del database venivano scaffolding come proprietà nullable bool? .

Nuovo comportamento

A partire da EF Core 8.0, le colonne non nullable bool vengono sempre scaffolding come proprietà non nullable.

Perché

Una bool proprietà non avrà il valore inviato al database se tale valore è false, ovvero l'impostazione predefinita CLR. Se il database ha un valore predefinito per true la colonna, anche se il valore della proprietà è false, il valore nel database finisce come true. In EF8, tuttavia, sentinel usato per determinare se una proprietà ha un valore può essere modificato. Questa operazione viene eseguita automaticamente per bool le proprietà con un valore generato dal database , trueil che significa che non è più necessario eseguire lo scaffolding delle proprietà come nullable.

Soluzioni di prevenzione

Questa modifica influisce solo sugli utenti che eseguono regolarmente lo scaffolding del database in un modello di codice EF ("flusso database-first").

È consigliabile reagire a questa modifica modificando il codice per usare la proprietà bool non nullable. Tuttavia, se non è possibile, è possibile modificare i modelli di scaffolding per ripristinare il mapping precedente. A tale scopo, configurare i modelli come descritto in questa pagina. Modificare quindi il EntityType.t4 file, individuare dove vengono generate le proprietà dell'entità (cercare property.ClrType) e modificare il codice come segue:

#>
        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!;" : "" #>
<#
<#

Modifiche a basso impatto

I metodi SQLite Math ora si traducono in SQL

Problema di rilevamento n. 18843

Comportamento precedente

In precedenza solo i metodi Abs, Max, Min e Round in Math sono stati convertiti in SQL. Tutti gli altri membri verranno valutati sul client se sono visualizzati nell'espressione Select finale di una query.

Nuovo comportamento

In EF Core 8.0 tutti i Math metodi con le funzioni matematiche SQLite corrispondenti vengono convertiti in SQL.

Queste funzioni matematiche sono state abilitate nella libreria SQLite nativa che viene fornito per impostazione predefinita (tramite la dipendenza dal pacchetto NuGet SQLitePCLRaw.bundle_e_sqlite3). Sono state abilitate anche nella libreria fornita da SQLitePCLRaw.bundle_e_sqlcipher. Se si usa una di queste librerie, l'applicazione non deve essere interessata da questa modifica.

È tuttavia possibile che le applicazioni che includono la libreria SQLite nativa non possano abilitare le funzioni matematiche. In questi casi, i Math metodi verranno convertiti in SQL e non si verificano errori di tale funzione durante l'esecuzione.

Perché

SQLite ha aggiunto funzioni matematiche predefinite nella versione 3.35.0. Anche se sono disabilitati per impostazione predefinita, sono diventati abbastanza diffusi che abbiamo deciso di fornire traduzioni predefinite per loro nel provider SQLite di EF Core.

Abbiamo anche collaborato con Eric Sink nel progetto SQLitePCLRaw per abilitare le funzioni matematiche in tutte le librerie SQLite native fornite come parte di tale progetto.

Soluzioni di prevenzione

Il modo più semplice per correggere le interruzioni è, quando possibile, per abilitare la funzione matematica è la libreria SQLite nativa specificando l'opzione SQLITE_ENABLE_MATH_FUNCTIONS in fase di compilazione.

Se non si controlla la compilazione della libreria nativa, è anche possibile correggere le interruzioni tramite la creazione delle funzioni in fase di esecuzione usando le API Microsoft.Data.Sqlite .

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

In alternativa, è possibile forzare la valutazione client suddividendo l'espressione Select in due parti separate da AsEnumerable.

// 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 sostituisce IEntityType in alcune API

Problema di rilevamento n. 13947

Comportamento precedente

In precedenza, tutti i tipi strutturali mappati erano tipi di entità.

Nuovo comportamento

Con l'introduzione di tipi complessi in EF8, alcune API usate in precedenza usano ITypeBase ora IEntityType in modo che le API possano essere usate con tipi complessi o di entità. Valuta gli ambiti seguenti:

  • IProperty.DeclaringEntityType è ora obsoleto e IProperty.DeclaringType deve essere invece usato.
  • IEntityTypeIgnoredConvention è ora obsoleto e ITypeIgnoredConvention deve essere invece usato.
  • IValueGeneratorSelector.Select accetta ora un oggetto ITypeBase che può essere, ma non deve essere un IEntityTypeoggetto .

Perché

Con l'introduzione di tipi complessi in EF8, queste API possono essere usate con IEntityType o IComplexType.

Soluzioni di prevenzione

Le API precedenti sono obsolete, ma non verranno rimosse fino a EF10. Il codice deve essere aggiornato per usare la nuova API ASAP.

Le espressioni ValueConverter e ValueComparer devono usare API pubbliche per il modello compilato

Problema di rilevamento n. 24896

Comportamento precedente

In precedenza, ValueConverter e ValueComparer le definizioni non erano incluse nel modello compilato e quindi potevano contenere codice arbitrario.

Nuovo comportamento

Ef estrae ora le espressioni dagli ValueConverter oggetti e ValueComparer e include questi C# nel modello compilato. Ciò significa che queste espressioni devono usare solo l'API pubblica.

Perché

Il team ef sta gradualmente spostando più costrutti nel modello compilato per supportare l'uso di EF Core con AOT in futuro.

Soluzioni di prevenzione

Rendere pubbliche le API usate dall'operatore di confronto. Si consideri ad esempio questo semplice convertitore:

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
}

Per usare questo convertitore in un modello compilato con EF8, i ConvertToString metodi e ConvertToBytes devono essere resi pubblici. Ad esempio:

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 non esclude più altre tabelle in una gerarchia TPC

Problema di rilevamento n. 30079

Comportamento precedente

In precedenza, l'uso ExcludeFromMigrations di in una tabella in una gerarchia TPC escludeva anche altre tabelle nella gerarchia.

Nuovo comportamento

A partire da EF Core 8.0, ExcludeFromMigrations non influisce sulle altre tabelle.

Perché

Il comportamento precedente era un bug e impediva l'uso delle migrazioni per gestire gerarchie tra progetti.

Soluzioni di prevenzione

Utilizzare ExcludeFromMigrations in modo esplicito in qualsiasi altra tabella che deve essere esclusa.

Le chiavi integer non shadow vengono mantenute nei documenti Cosmos

Problema di rilevamento n. 31664

Comportamento precedente

In precedenza, le proprietà di interi non shadow che corrispondono ai criteri per essere una proprietà chiave sintetizzata non verrebbero rese persistenti nel documento JSON, ma venivano invece sintetizzate nuovamente all'uscita.

Nuovo comportamento

A partire da EF Core 8.0, queste proprietà sono ora persistenti.

Perché

Il comportamento precedente era un bug e impediva la persistenza delle proprietà che corrispondono ai criteri della chiave sintetizzata in Cosmos.

Soluzioni di prevenzione

Escludere la proprietà dal modello se il relativo valore non deve essere salvato in modo permanente. Inoltre, è possibile disabilitare completamente questo comportamento impostando Microsoft.EntityFrameworkCore.Issue31664 l'opzione AppContext su true, vedere AppContext per i consumer di libreria per altri dettagli.

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

Il modello relazionale viene generato nel modello compilato

Problema di rilevamento n. 24896

Comportamento precedente

In precedenza, il modello relazionale veniva calcolato in fase di esecuzione anche quando si usava un modello compilato.

Nuovo comportamento

A partire da EF Core 8.0, il modello relazionale fa parte del modello generato. Tuttavia, per modelli particolarmente di grandi dimensioni, la compilazione del file generato potrebbe non riuscire.

Perché

Questa operazione è stata eseguita per migliorare ulteriormente il tempo di avvio.

Soluzioni di prevenzione

Modificare il file generato *ModelBuilder.cs e rimuovere la riga AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel()); e il metodo CreateRelationalModel().

Lo scaffolding può generare nomi di navigazione diversi

Problema di rilevamento n. 27832

Comportamento precedente

In precedenza, quando si esegue lo scaffolding di tipi DbContext di entità e da un database esistente, i nomi di navigazione per le relazioni venivano talvolta derivati da un prefisso comune di più nomi di colonna chiave esterna.

Nuovo comportamento

A partire da EF Core 8.0, i prefissi comuni dei nomi di colonna da una chiave esterna composita non vengono più usati per generare nomi di navigazione.

Perché

Si tratta di una regola di denominazione oscura che a volte genera nomi molto poveri come , SStudent_, o anche solo _. Senza questa regola, i nomi strani non vengono più generati e anche le convenzioni di denominazione per gli spostamenti sono rese più semplici, semplificando così la comprensione e la stima dei nomi che verranno generati.

Soluzioni di prevenzione

EF Core Power Tools ha la possibilità di continuare a generare spostamenti nel vecchio modo. In alternativa, il codice generato può essere completamente personalizzato usando i modelli T4. Può essere usato per esempio le proprietà di chiave esterna delle relazioni di scaffolding e usare qualsiasi regola appropriata per il codice per generare i nomi di navigazione necessari.

I discriminatori hanno ora una lunghezza massima

Problema di rilevamento n. 10691

Comportamento precedente

In precedenza, le colonne discriminatorie create per il mapping di ereditarietà TPH sono state configurate come nvarchar(max) in SQL Server/Azure SQL o il tipo di stringa non associato equivalente in altri database.

Nuovo comportamento

A partire da EF Core 8.0, le colonne discriminatorie vengono create con una lunghezza massima che copre tutti i valori discriminatori noti. Entity Framework genererà una migrazione per apportare questa modifica. Tuttavia, se la colonna discriminatoria è vincolata in qualche modo, ad esempio come parte di un indice, l'oggetto AlterColumn creato dalle migrazioni potrebbe non riuscire.

Perché

nvarchar(max) le colonne sono inefficienti e non necessarie quando sono note le lunghezze di tutti i valori possibili.

Soluzioni di prevenzione

Le dimensioni delle colonne possono essere rese esplicitamente non associate:

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

I valori delle chiavi di SQL Server vengono confrontati senza distinzione tra maiuscole e minuscole

Problema di rilevamento n. 27526

Comportamento precedente

In precedenza, durante il rilevamento delle entità con chiavi stringa con i provider di database SQL Server/Azure SQL, i valori delle chiavi venivano confrontati usando l'operatore di confronto ordinale con distinzione tra maiuscole e minuscole .NET predefinito.

Nuovo comportamento

A partire da EF Core 8.0, i valori di chiave stringa SQL Server/Azure SQL vengono confrontati usando l'operatore di confronto ordinale senza distinzione tra maiuscole e minuscole .NET predefinito.

Perché

Per impostazione predefinita, SQL Server usa confronti senza distinzione tra maiuscole e minuscole durante il confronto dei valori di chiave esterna per le corrispondenze con i valori di chiave principale. Ciò significa che quando Ef usa confronti con distinzione tra maiuscole e minuscole, potrebbe non connettere una chiave esterna a una chiave principale quando deve.

Soluzioni di prevenzione

È possibile usare confronti con distinzione tra maiuscole e minuscole impostando un oggetto personalizzato ValueComparer. Ad esempio:

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);
        });
}