Condividi tramite


Modifiche di rilievo in EF Core 9 (EF9)

Questa pagina illustra le modifiche di comportamento e API che potrebbero interrompere l'aggiornamento delle applicazioni esistenti da EF Core 8 a EF Core 9. 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 9 è destinato a .NET 8. Ciò significa che le applicazioni esistenti destinate a .NET 8 possono continuare a farlo. Le applicazioni destinate a versioni precedenti di .NET, .NET Core e .NET Framework dovranno usare .NET 8 o .NET 9 per usare EF Core 9.

Riepilogo

Nota

Se si usa Azure Cosmos DB, vedere la sezione separata seguente sulle modifiche che causano un'interruzione di Azure Cosmos DB.

Modifica di rilievo Impatto
eccezione viene generata quando si applicano migrazioni se sono presenti modifiche al modello in sospeso Alto
EF.Functions.Unhex() ora restituisce byte[]? Basso
Convalidati argomenti nullability di SqlFuncionExpression Basso
ToString() il metodo restituisce ora una stringa vuota per null le istanze Basso
Le dipendenze del framework condiviso sono state aggiornate alla versione 9.0.x Basso

Modifiche ad alto impatto

Viene generata un'eccezione durante l'applicazione delle migrazioni se ci sono modifiche al modello in sospeso.

problema di rilevamento n. 33732

Comportamento precedente

Se il modello presenta modifiche in sospeso rispetto all'ultima migrazione, non vengono applicate al resto delle migrazioni quando viene chiamato Migrate.

Nuovo comportamento

A partire da EF Core 9.0, se il modello presenta modifiche in sospeso rispetto all'ultima migrazione, viene generata un'eccezione quando viene chiamata dotnet ef database update, Migrate o MigrateAsync:

Il modello per il contesto 'DbContext' presenta modifiche in sospeso. Aggiungere una nuova migrazione prima di aggiornare il database. Questa eccezione può essere soppressa o registrata passando l'ID evento 'RelationalEventId.PendingModelChangesWarning' al metodo 'ConfigureWarnings' in 'DbContext.OnConfiguring' o 'AddDbContext'.

Perché

Dimenticare di aggiungere una nuova migrazione dopo aver apportato modifiche al modello è un errore comune che può essere difficile da diagnosticare in alcuni casi. La nuova eccezione garantisce che il modello dell'app corrisponda al database dopo l'applicazione delle migrazioni.

Soluzioni di prevenzione

Esistono diverse situazioni comuni in cui è possibile generare questa eccezione:

  • Non sono presenti migrazioni. Ciò è comune quando il database viene aggiornato tramite altri mezzi.
    • Mitigazione: se non si prevede di usare le migrazioni per la gestione dello schema del database, rimuovere la chiamata Migrate o MigrateAsync, altrimenti aggiungere una migrazione.
  • È presente almeno una migrazione, ma manca lo snapshot del modello. Questa operazione è comune per le migrazioni create manualmente.
    • Mitigazione: aggiungere una nuova migrazione utilizzando gli strumenti di EF, il che aggiornerà lo snapshot del modello.
  • Il modello non è stato modificato dallo sviluppatore, ma è costruito in modo non deterministico, che porta EF a rilevarlo come modificato. Ciò è comune quando new DateTime(), DateTime.Now, DateTime.UtcNowo Guid.NewGuid() vengono usati negli oggetti forniti per HasData().
    • Mitigazione: aggiungere una nuova migrazione, esaminarne il contenuto per determinare la causa e sostituire i dati dinamici con un valore statico codificato manualmente nel modello. La migrazione deve essere ricreata dopo la correzione del modello. Se i dati dinamici devono essere usati per il seeding, è consigliabile usare nuovo modello di seeding anziché HasData().
  • L'ultima migrazione è stata creata per un provider diverso da quello usato per applicare le migrazioni.
    • Mitigazione: Si tratta di uno scenario non supportato. L'avviso può essere eliminato usando il frammento di codice seguente, ma questo scenario probabilmente smetterà di funzionare in una versione futura di EF Core. La soluzione consigliata è generare un set separato di migrazioni per ogni provider.
  • Le migrazioni vengono generate o scelte in modo dinamico sostituendo alcuni dei servizi di Entity Framework.
    • Mitigazione: l'avviso è un falso positivo in questo caso e deve essere eliminato:

      options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))

Se il tuo scenario non rientra in nessuno dei casi precedenti e l'aggiunta di una nuova migrazione crea ogni volta la stessa migrazione o una migrazione vuota, e l'eccezione continua a verificarsi, crea un piccolo progetto di riproduzione e condividilo con il team EF in un nuovo problema.

Modifiche a basso impatto

EF.Functions.Unhex() ora restituisce byte[]?

Problema n. 33864

Comportamento precedente

La EF.Functions.Unhex() funzione è stata annotata in precedenza per restituire byte[].

Nuovo comportamento

A partire da EF Core 9.0, Unhex() viene ora annotato per restituire byte[]?.

Perché

Unhex() viene convertito nella funzione SQLite unhex , che restituisce NULL per gli input non validi. Di conseguenza, Unhex() restituito null per tali casi, in violazione dell'annotazione.

Soluzioni di prevenzione

Se si è certi che il contenuto di testo passato a Unhex() rappresenta una stringa esadecimale valida, è sufficiente aggiungere l'operatore null-forgiving come asserzione che la chiamata non restituirà mai null:

var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync();

In caso contrario, aggiungere i controlli di runtime per i valori Null nel valore restituito di Unhex().

Convalidati gli argomenti nullability SqlFunctionExpression

Problema n. 33852

Comportamento precedente

In precedenza era possibile creare un oggetto SqlFunctionExpression con un numero diverso di argomenti e argomenti di propagazione dei valori Null.

Nuovo comportamento

A partire da EF Core 9.0, EF genera ora un'eccezione se il numero di argomenti e gli argomenti di propagazione dei valori Null non corrispondono.

Perché

Non avere un numero corrispondente di argomenti e argomenti di propagazione dei valori Null può causare un comportamento imprevisto.

Soluzioni di prevenzione

Verificare che abbia lo argumentsPropagateNullability stesso numero di elementi di arguments. In caso di dubbi, usare false per l'argomento supporto dei valori null.

ToString() il metodo restituisce ora una stringa vuota per null le istanze

Problema di rilevamento n. 33941

Comportamento precedente

In precedenza EF restituiva risultati incoerenti per il ToString() metodo quando il valore dell'argomento era null. Ad esempio ToString() , nella bool? proprietà con null valore restituito null, ma per le espressioni non di proprietà bool? il cui valore è stato null restituito True. Il comportamento è stato anche incoerente per altri tipi di dati, ad esempio ToString() per null l'enumerazione del valore ha restituito una stringa vuota.

Nuovo comportamento

A partire da EF Core 9.0, il ToString() metodo restituisce ora in modo coerente una stringa vuota in tutti i casi quando il valore dell'argomento è null.

Perché

Il comportamento precedente era incoerente in diversi tipi di dati e situazioni, nonché non allineato al comportamento C#.

Soluzioni di prevenzione

Per ripristinare il comportamento precedente, riscrivere la query di conseguenza:

var newBehavior = context.Entity.Select(x => x.NullableBool.ToString());
var oldBehavior = context.Entity.Select(x => x.NullableBool == null ? null : x.NullableBool.ToString());

Le dipendenze del framework condiviso sono state aggiornate alla versione 9.0.x

Comportamento precedente

Le app che usano l'SDK Microsoft.NET.Sdk.Web e la destinazione di net8.0 risolverebbero pacchetti come System.Text.Json, Microsoft.Extensions.Caching.MemoryMicrosoft.Extensions.Configuration.Abstractions, Microsoft.Extensions.Logging e Microsoft.Extensions.DependencyModel dal framework condiviso, quindi questi assembly non verrebbero normalmente distribuiti con l'app.

Nuovo comportamento

Mentre EF Core 9.0 supporta ancora net8.0 ora fa riferimento alle versioni 9.0.x di System.Text.Json, Microsoft.Extensions.Caching.Memory, Microsoft.Extensions.Configuration.AbstractionsMicrosoft.Extensions.Logging e Microsoft.Extensions.DependencyModel. Le app destinate a net8.0 non saranno in grado di sfruttare il framework condiviso per evitare la distribuzione di questi assembly.

Perché

Le versioni delle dipendenze corrispondenti contengono le correzioni di sicurezza più recenti e le usano semplificano il modello di manutenzione per EF Core.

Soluzioni di prevenzione

Modificare l'app impostando come destinazione net9.0 per ottenere il comportamento precedente.

Modifiche che causano un'interruzione di Azure Cosmos DB

Un'ampia gamma di attività ha reso il provider Azure Cosmos DB migliore nella versione 9.0. Le modifiche includono una serie di modifiche di rilievo ad alto impatto; se si sta aggiornando un'applicazione esistente, leggere attentamente quanto segue.

Modifica di rilievo Impatto
La proprietà discriminatoria è ora denominata $type invece di Discriminator Alto
La id proprietà non contiene più il discriminatorio per impostazione predefinita Alto
L'I/O di sincronizzazione tramite il provider Azure Cosmos DB non è più supportato Medio
Le query SQL devono ora proiettare direttamente i valori JSON Medio
I risultati non definiti vengono ora filtrati automaticamente dai risultati della query Medio
Le query tradotte in modo non corretto non vengono più tradotte Medio
HasIndex ora genera invece di essere ignorato Basso
IncludeRootDiscriminatorInJsonId è stato rinominato HasRootDiscriminatorInJsonId dopo la versione 9.0.0-rc.2 Basso

Modifiche ad alto impatto

La proprietà discriminatoria è ora denominata $type invece di Discriminator

Problema n. 34269

Comportamento precedente

EF aggiunge automaticamente una proprietà discriminatoria ai documenti JSON per identificare il tipo di entità rappresentato dal documento. Nelle versioni precedenti di EF questa proprietà JSON viene usata per essere denominata Discriminator per impostazione predefinita.

Nuovo comportamento

A partire da EF Core 9.0, la proprietà discriminatoria viene ora chiamata $type per impostazione predefinita. Se si dispone di documenti esistenti in Azure Cosmos DB da versioni precedenti di ENTITY, questi usano la denominazione precedente Discriminator e dopo l'aggiornamento a EF 9.0, le query su tali documenti avranno esito negativo.

Perché

Una pratica JSON emergente usa una $type proprietà negli scenari in cui è necessario identificare il tipo di un documento. Per esempio, System.Text.Json di .NET supporta anche il polimorfismo, usando $type come nome di proprietà discriminatorio predefinito (docs). Per allinearsi al resto dell'ecosistema e semplificare l'interoperabilità con gli strumenti esterni, il valore predefinito è stato modificato.

Soluzioni di prevenzione

La mitigazione più semplice consiste nel configurare semplicemente il nome della proprietà discriminatoria in modo che sia Discriminator, come in precedenza:

modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");

Questa operazione per tutti i tipi di entità di primo livello farà in modo che EF si comporti come in precedenza.

A questo punto, se lo si desidera, è anche possibile aggiornare tutti i documenti in modo da usare la nuova $type denominazione.

La proprietà id contiene ora solo la proprietà della chiave EF per impostazione predefinita

Problema n. 34179

Comportamento precedente

In precedenza, EF ha inserito il valore discriminatorio del tipo di entità nella proprietà id del documento. Ad esempio, se è stato salvato un Blog tipo di entità con una proprietà Id contenente 8, la proprietà JSON id conterrà Blog|8.

Nuovo comportamento

A partire da EF Core 9.0, la proprietà JSON id non contiene più il valore discriminatorio e contiene solo il valore della proprietà della chiave. Per l'esempio precedente, la proprietà JSON id sarebbe semplicemente 8. Se si dispone di documenti esistenti in Azure Cosmos DB da versioni precedenti di ENTITY, questi hanno il valore discriminatorio nella proprietà JSON id e dopo l'aggiornamento a EF 9.0, le query su tali documenti avranno esito negativo.

Perché

Poiché la proprietà JSON id deve essere univoca, il discriminatorio è stato aggiunto in precedenza per consentire l'esistenza di entità diverse con lo stesso valore di chiave. Ad esempio, ciò ha consentito di avere sia un oggetto Blog che un Post con una Id proprietà contenente il valore 8 all'interno dello stesso contenitore e della stessa partizione. Questo modello è allineato meglio ai modelli di modellazione dei dati del database relazionale, in cui ogni tipo di entità viene mappato alla propria tabella e pertanto ha uno spazio chiave proprio.

EF 9.0 ha in genere modificato il mapping in modo che sia più allineato alle procedure e alle aspettative comuni di Azure Cosmos DB NoSQL, anziché corrispondere alle aspettative degli utenti provenienti da database relazionali. Inoltre, la presenza del valore discriminatorio nella proprietà id rendeva più difficile per gli strumenti e i sistemi esterni interagire con i documenti JSON generati da EF. Tali sistemi esterni non sono generalmente consapevoli dei valori discriminatori di EF, che sono per impostazione predefinita derivati dai tipi .NET.

Soluzioni di prevenzione

La mitigazione più semplice consiste nel configurare semplicemente Entity Framework per includere il discriminatorio nella proprietà JSON id, come in precedenza. A questo scopo è stata introdotta una nuova opzione di configurazione:

modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();

Questa operazione per tutti i tipi di entità di primo livello farà in modo che EF si comporti come in precedenza.

A questo punto, se si vuole, è anche possibile aggiornare tutti i documenti per riscrivere la proprietà JSON id. Si noti che questo è possibile solo se le entità di tipi diversi non condividono lo stesso valore ID all'interno dello stesso contenitore.

Modifiche a impatto medio

L'I/O di sincronizzazione tramite il provider Azure Cosmos DB non è più supportato

Problema n. 32563

Comportamento precedente

In precedenza, la chiamata a metodi sincroni come ToList o SaveChanges causerebbe il blocco di EF Core in modo sincrono usando .GetAwaiter().GetResult() quando si eseguono chiamate asincrone su Azure Cosmos DB SDK. Ciò può causare un deadlock.

Nuovo comportamento

A partire da EF Core 9.0, EF genera ora per impostazione predefinita quando si tenta di usare le I/O sincrone. Il messaggio di eccezione è "Azure Cosmos DB non supporta operazioni di I/O sincrone. Assicurarsi di usare e attendere correttamente solo i metodi asincroni quando si usa Entity Framework Core per accedere ad Azure Cosmos DB. Per maggiori informazioni, consultare https://aka.ms/ef-cosmos-nosync."

Perché

Il blocco sincrono sui metodi asincroni può causare deadlock e Azure Cosmos DB SDK supporta solo metodi asincroni.

Soluzioni di prevenzione

In EF Core 9.0 l'errore può essere eliminato con:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.ConfigureWarnings(w => w.Ignore(CosmosEventId.SyncNotSupported));
}

Detto questo, le applicazioni devono smettere di usare le API di sincronizzazione con Azure Cosmos DB, perché non è supportato da Azure Cosmos DB SDK. La possibilità di eliminare l'eccezione verrà rimossa in una versione futura di EF Core, dopo la quale l'unica opzione consiste nell'usare le API asincrone.

Le query SQL devono ora proiettare direttamente i valori JSON

Problema n. 25527

Comportamento precedente

In precedenza, EF generava query come le seguenti:

SELECT c["City"] FROM root c

Queste query causano il wrapping di ogni risultato in un oggetto JSON, come indicato di seguito:

[
    {
        "City": "Berlin"
    },
    {
        "City": "México D.F."
    }
]
Nuovo comportamento

A partire da EF Core 9.0, EF aggiunge ora il modificatore VALUE alle query come indicato di seguito:

SELECT VALUE c["City"] FROM root c

Queste query causano la restituzione diretta dei valori da parte di Azure Cosmos DB, senza eseguire il wrapping:

[
    "Berlin",
    "México D.F."
]

Se l'applicazione usa query SQL, è probabile che tali query vengano interrotte dopo l'aggiornamento a EF 9.0, perché non includono il modificatore VALUE.

Perché

Il wrapping di ogni risultato in un oggetto JSON aggiuntivo può causare una riduzione delle prestazioni in alcuni scenari, aumentare il payload del risultato JSON e non è il modo naturale per lavorare con Azure Cosmos DB.

Soluzioni di prevenzione

Per attenuare il problema, è sufficiente aggiungere il modificatore VALUE alle proiezioni delle query SQL, come illustrato in precedenza.

I risultati non definiti vengono ora filtrati automaticamente dai risultati della query

Problema n. 25527

Comportamento precedente

In precedenza, EF generava query come le seguenti:

SELECT c["City"] FROM root c

Queste query causano il wrapping di ogni risultato in un oggetto JSON, come indicato di seguito:

[
    {
        "City": "Berlin"
    },
    {
        "City": "México D.F."
    }
]

Se uno dei risultati non è definito (ad esempio, la proprietà City era assente dal documento), è stato restituito un documento vuoto e EF restituirà null per tale risultato.

Nuovo comportamento

A partire da EF Core 9.0, EF aggiunge ora il modificatore VALUE alle query come indicato di seguito:

SELECT VALUE c["City"] FROM root c

Queste query causano la restituzione diretta dei valori da parte di Azure Cosmos DB, senza eseguire il wrapping:

[
    "Berlin",
    "México D.F."
]

Il comportamento di Azure Cosmos DB consiste nel filtrare undefined automaticamente i valori dei risultati. Ciò significa che se una delle City proprietà è assente dal documento, la query restituirà un solo risultato, anziché due risultati, con uno che è null.

Perché

Il wrapping di ogni risultato in un oggetto JSON aggiuntivo può causare una riduzione delle prestazioni in alcuni scenari, aumentare il payload del risultato JSON e non è il modo naturale per lavorare con Azure Cosmos DB.

Soluzioni di prevenzione

Se si ottengono i valori null per i risultati non definiti è importante per l'applicazione, unire i valori undefined a null usando il nuovo operatore EF.Functions.Coalesce:

var users = await context.Customer
    .Select(c => EF.Functions.CoalesceUndefined(c.City, null))
    .ToListAsync();

Le query tradotte in modo non corretto non vengono più tradotte

Problema n. 34123

Comportamento precedente

In precedenza, EF traduceva query come le seguenti:

var sessions = await context.Sessions
    .Take(5)
    .Where(s => s.Name.StartsWith("f"))
    .ToListAsync();

Tuttavia, la traduzione SQL per questa query non è corretta:

SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Session") AND STARTSWITH(c["Name"], "f"))
OFFSET 0 LIMIT @__p_0

In SQL la WHERE clausola viene valutata prima delle OFFSET clausole e LIMIT , ma nella query LINQ precedente l'operatore Take viene visualizzato prima dell'operatore Where . Di conseguenza, tali query potrebbero restituire risultati non corretti.

Nuovo comportamento

A partire da EF Core 9.0, tali query non vengono più convertite e viene generata un'eccezione.

Perché

Le traduzioni non corrette possono causare un danneggiamento automatico dei dati, che può introdurre bug difficili da individuare nell'applicazione. Ef preferisce sempre eseguire il fail-fast generando in anticipo anziché causare il danneggiamento dei dati.

Soluzioni di prevenzione

Se si è soddisfatti del comportamento precedente e si vuole eseguire lo stesso SQL, è sufficiente scambiare l'ordine degli operatori LINQ:

var sessions = await context.Sessions
    .Where(s => s.Name.StartsWith("f"))
    .Take(5)
    .ToListAsync();

Sfortunatamente, Azure Cosmos DB attualmente non supporta le OFFSET clausole e LIMIT nelle sottoquery SQL, ovvero la traduzione corretta della query LINQ originale.

Modifiche a basso impatto

HasIndex ora genera invece di essere ignorato

Problema n. 34023

Comportamento precedente

In precedenza, le chiamate a HasIndex venivano ignorate dal provider EF Cosmos DB.

Nuovo comportamento

Il provider genera ora un'eccezione se HasIndex è specificato.

Perché

In Azure Cosmos DB tutte le proprietà vengono indicizzate per impostazione predefinita e non è necessario specificare l'indicizzazione. Sebbene sia possibile definire un criterio di indicizzazione personalizzato, questo non è attualmente supportato da Entity Framework e può essere eseguito tramite il portale di Azure senza il supporto di Entity Framework. Poiché le chiamate HasIndex non facevano niente, non sono più consentite.

Soluzioni di prevenzione

Rimuovere le chiamate a HasIndex.

IncludeRootDiscriminatorInJsonId è stato rinominato in HasRootDiscriminatorInJsonId dopo 9.0.0-rc.2

Problema n. 34717

Comportamento precedente

L'API IncludeRootDiscriminatorInJsonId è stata introdotta nella versione 9.0.0 rc.1.

Nuovo comportamento

Per la versione finale di EF Core 9.0, l'API è stata rinominata in HasRootDiscriminatorInJsonId

Perché

Un'altra API correlata è stata rinominata per iniziare con Has invece di Include, e questo è stato rinominato anche per coerenza.

Soluzioni di prevenzione

Se il codice usa l'API IncludeRootDiscriminatorInJsonId, è sufficiente modificarlo in modo che faccia riferimento HasRootDiscriminatorInJsonId.