Condividi tramite


Modifiche in rilievo in EF Core 6.0

Le seguenti modifiche all'API e al comportamento possono interrompere l'aggiornamento delle applicazioni esistenti a EF Core 6.0.

Framework di destinazione

EF Core 6.0 è destinato a .NET 6. Le applicazioni destinate a versioni precedenti di .NET, .NET Core e .NET Framework dovranno usare .NET 6 per usare EF Core 6.0.

Riepilogo

Modifica di rilievo Impatto
Non è possibile salvare le proprietà facoltative annidate che condividono una tabella e senza proprietà obbligatorie Alto
La modifica del proprietario di un'entità di proprietà genera ora un'eccezione Medio
Azure Cosmos DB: i tipi di entità correlati vengono individuati come di proprietà Medio
SQLite: le connessioni vengono raggruppate Medio
Le relazioni molti-a-molti senza entità join mappate sono ora sottoposte a scaffolding Medio
Mapping pulito tra i valori DeleteBehavior e ON DELETE Basso
Il database in memoria convalida le proprietà necessarie non contengono valori Null Basso
Rimozione dell'ultimo ORDER BY durante l'unione per le raccolte Basso
DbSet non implementa più IAsyncEnumerable Basso
Anche il tipo di entità restituito TVF viene mappato a una tabella per impostazione predefinita Basso
Verificare l'univocità del nome del vincolo ora convalidata Basso
Aggiunta di interfacce di metadati IReadOnly e metodi di estensione rimossi Basso
IExecutionStrategy è ora un servizio singleton Basso
SQL Server: altri errori sono considerati temporanei Basso
Azure Cosmos DB: un numero maggiore di caratteri viene preceduto da un carattere di escape nei valori 'id' Basso
Alcuni servizi Singleton sono ora con ambito Basso*
Nuova API di memorizzazione nella cache per le estensioni che aggiungono o sostituiscono servizi Basso*
Nuova procedura di inizializzazione di snapshot e modello in fase di progettazione Basso
OwnedNavigationBuilder.HasIndex restituisce ora un tipo diverso Basso
DbFunctionBuilder.HasSchema(null) Esegue l' override [DbFunction(Schema = "schema")] Basso
Gli spostamenti pre-inizializzati vengono sottoposti a override dai valori delle query di database Basso
I valori di stringa di enumerazione sconosciuti nel database non vengono convertiti nell'enumerazione predefinita quando viene eseguita una query Basso
DbFunctionBuilder.HasTranslation fornisce ora gli argomenti della funzione come IReadOnlyList anziché IReadOnlyCollection Basso
Il mapping predefinito della tabella non viene rimosso quando l'entità viene mappata a una funzione con valori di tabella Basso
dotnet-ef è destinato a .NET 6 Basso
IModelCacheKeyFactory potrebbe essere necessario aggiornare le implementazioni per gestire la memorizzazione nella cache in fase di progettazione Basso
NavigationBaseIncludeIgnored è ora un errore per impostazione predefinita Basso

* Queste modifiche sono di particolare interesse per gli autori di provider di database e estensioni.

Modifiche ad alto impatto

Le dipendenze facoltative annidate che condividono una tabella e senza proprietà obbligatorie non sono consentite

Problema di rilevamento n. 24558

Comportamento precedente

I modelli con dipendenti facoltativi annidati che condividono una tabella e senza proprietà obbligatorie sono stati consentiti, ma potrebbero comportare la perdita di dati durante l'esecuzione di query sui dati e quindi il salvataggio di nuovo. Si consideri ad esempio il modello seguente:

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

Nessuna delle proprietà in ContactInfo o Address è obbligatoria e tutti questi tipi di entità vengono mappati alla stessa tabella. Le regole per i dipendenti facoltativi (anziché i dipendenti necessari) dicono che se tutte le colonne per ContactInfo sono Null, non verrà creata alcuna istanza di ContactInfo durante l'esecuzione di query per il proprietario Customer. Ciò significa anche che non verrà creata alcuna istanza di Address , anche se le Address colonne non sono null.

Nuovo comportamento

Il tentativo di usare questo modello genererà ora l'eccezione seguente:

System.InvalidOperationException: il tipo di entità 'ContactInfo' è un dipendente facoltativo tramite la condivisione di tabelle e contiene altre dipendenze senza alcuna proprietà non condivisa necessaria per identificare se l'entità esiste. Se tutte le proprietà nullable contengono un valore Null nel database, non verrà creata un'istanza dell'oggetto nella query, causando la perdita dei valori dipendenti annidati. Aggiungere una proprietà obbligatoria per creare istanze con valori Null per altre proprietà o contrassegnare lo spostamento in ingresso come necessario per creare sempre un'istanza.

Ciò impedisce la perdita di dati durante l'esecuzione di query e il salvataggio dei dati.

Perché

L'uso di modelli con dipendenti facoltativi annidati che condividono una tabella e senza proprietà obbligatorie comporta spesso la perdita di dati invisibile all'utente.

Soluzioni di prevenzione

Evitare di usare dipendenti facoltativi che condividono una tabella e senza proprietà necessarie. Esistono tre modi semplici per eseguire questa operazione:

  1. Rendere necessarie le dipendenze. Ciò significa che l'entità dipendente avrà sempre un valore dopo la query, anche se tutte le relative proprietà sono Null. Ad esempio:

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

    Oppure:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.OwnsOne(e => e.Address);
                b.Navigation(e => e.Address).IsRequired();
            });
    
  2. Assicurarsi che il dipendente contenga almeno una proprietà obbligatoria.

  3. Eseguire il mapping dei dipendenti facoltativi alla propria tabella, anziché condividere una tabella con l'entità. Ad esempio:

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

I problemi relativi ai dipendenti facoltativi ed esempi di queste mitigazioni sono inclusi nella documentazione relativa alle novità di EF Core 6.0.

Modifiche a impatto medio

La modifica del proprietario di un'entità di proprietà genera ora un'eccezione

Problema di rilevamento n. 4073

Comportamento precedente

È stato possibile riassegnare un'entità di proprietà a un'entità proprietario diversa.

Nuovo comportamento

Questa azione genererà ora un'eccezione:

Proprietà '{entityType}. {property}' fa parte di una chiave e pertanto non può essere modificata o contrassegnata come modificata. Per modificare l'entità di un'entità esistente con una chiave esterna di identificazione, eliminare prima il dipendente e richiamare "SaveChanges" e quindi associare l'entità dipendente alla nuova entità.

Perché

Anche se non è necessario che le proprietà chiave esistano in un tipo di proprietà, ENTITY creerà comunque proprietà shadow da usare come chiave primaria e la chiave esterna che punta al proprietario. Quando l'entità proprietario viene modificata, i valori della chiave esterna nell'entità di proprietà cambiano e, poiché vengono usati anche come chiave primaria, l'identità dell'entità cambia. Questo non è ancora completamente supportato in EF Core ed è stato consentito solo in modo condizionale per le entità di proprietà, a volte determinando che lo stato interno diventa incoerente.

Soluzioni di prevenzione

Anziché assegnare la stessa istanza di proprietà a un nuovo proprietario, è possibile assegnare una copia ed eliminare quella precedente.

Problema di rilevamento n. 24803Novità: Impostazione predefinita della proprietà implicita

Comportamento precedente

Come in altri provider, i tipi di entità correlati sono stati individuati come tipi normali (non di proprietà).

Nuovo comportamento

I tipi di entità correlati saranno ora di proprietà del tipo di entità in cui sono stati individuati. Solo i tipi di entità che corrispondono a una DbSet<TEntity> proprietà verranno individuati come non di proprietà.

Perché

Questo comportamento segue il modello comune di modellazione dei dati in Azure Cosmos DB per l'incorporamento di dati correlati in un singolo documento. Azure Cosmos DB non supporta in modo nativo l'unione di documenti diversi, quindi la modellazione di entità correlate come non di proprietà ha un'utilità limitata.

Soluzioni di prevenzione

Per configurare un tipo di entità come chiamata non di proprietà modelBuilder.Entity<MyEntity>();

SQLite: le connessioni vengono raggruppate

Problema di rilevamento n. 13837Novità: Impostazione predefinita della proprietà implicita

Comportamento precedente

In precedenza, le connessioni in Microsoft.Data.Sqlite non venivano raggruppate.

Nuovo comportamento

A partire dalla versione 6.0, le connessioni vengono ora raggruppate per impostazione predefinita. In questo modo, i file di database vengono mantenuti aperti dal processo anche dopo la chiusura dell'oggetto connessione ADO.NET.

Perché

Il pooling delle connessioni sottostanti migliora notevolmente le prestazioni di apertura e chiusura degli oggetti di connessione ADO.NET. Ciò è particolarmente evidente per gli scenari in cui l'apertura della connessione sottostante è costosa, come nel caso della crittografia o in scenari in cui sono presenti molte connessioni di breve durata al database.

Soluzioni di prevenzione

Il pool di connessioni può essere disabilitato aggiungendo Pooling=False a un stringa di connessione.

Alcuni scenari, ad esempio l'eliminazione del file di database, possono ora riscontrare errori che indicano che il file è ancora in uso. È possibile cancellare manualmente il pool di connessioni prima di eseguire operazioni del file usando SqliteConnection.ClearPool().

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

Le relazioni molti-a-molti senza entità join mappate sono ora sottoposte a scaffolding

Problema di rilevamento n. 22475

Comportamento precedente

Scaffolding (reverse engineering) di un DbContext tipo di entità e da un database esistente mappato sempre in modo esplicito alle tabelle di join per unire tipi di entità per relazioni molti-a-molti.

Nuovo comportamento

Le tabelle di join semplici contenenti solo due proprietà di chiave esterna ad altre tabelle non vengono più mappate ai tipi di entità espliciti, ma vengono mappate come relazione molti-a-molti tra le due tabelle unite in join.

Perché

Le relazioni molti-a-molti senza tipi di join espliciti sono state introdotte in EF Core 5.0 e sono un modo più pulito e più naturale per rappresentare tabelle di join semplici.

Soluzioni di prevenzione

Esistono due mitigazioni. L'approccio preferito consiste nell'aggiornare il codice per usare direttamente le relazioni molti-a-molti. È molto raro che il tipo di entità join debba essere usato direttamente quando contiene solo due chiavi esterne per le relazioni molti-a-molti.

In alternativa, l'entità di join esplicita può essere aggiunta di nuovo al modello di Entity Framework. Ad esempio, presupponendo una relazione molti-a-molti tra Post e Tag, aggiungere di nuovo il tipo di join e gli spostamenti usando classi parziali:

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

Aggiungere quindi la configurazione per il tipo di join e gli spostamenti a una classe parziale per DbContext:

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

Rimuovere infine la configurazione generata per la relazione molti-a-molti dal contesto di scaffolding. Questa operazione è necessaria perché il tipo di entità join con scaffolding deve essere rimosso dal modello prima di poter usare il tipo esplicito. Questo codice dovrà essere rimosso ogni volta che viene eseguito lo scaffolding del contesto, ma poiché il codice precedente si trova in classi parziali, verrà mantenuto.

Si noti che con questa configurazione, l'entità join può essere usata in modo esplicito, proprio come nelle versioni precedenti di EF Core. Tuttavia, la relazione può essere usata anche come relazione molti-a-molti. Ciò significa che l'aggiornamento del codice come questo può essere una soluzione temporanea mentre il resto del codice viene aggiornato per usare la relazione come molti-a-molti in modo naturale.

Modifiche a basso impatto

Mapping pulito tra i valori DeleteBehavior e ON DELETE

Problema di rilevamento n. 21252

Comportamento precedente

Alcuni mapping tra il comportamento di OnDelete() una relazione e il comportamento delle chiavi ON DELETE esterne nel database non sono coerenti sia nelle migrazioni che nello scaffolding.

Nuovo comportamento

Nella tabella seguente vengono illustrate le modifiche per le migrazioni.

OnDelete() ON DELETE
NoAction NO ACTION
ClientNoAction NO ACTION
Limita RESTRICT
Cascade CASCADE
ClientCascade LIMITA NESSUNA AZIONE
SetNull SET NULL
ClientSetNull LIMITA NESSUNA AZIONE

Di seguito sono riportate le modifiche per lo scaffolding .

ON DELETE OnDelete()
NO ACTION ClientSetNull
RESTRICT ClientSetNull Restrict
CASCADE Cascade
SET NULL SetNull

Perché

I nuovi mapping sono più coerenti. Il comportamento predefinito del database NO ACTION è ora preferibile rispetto al comportamento RESTRICT più restrittivo e meno efficiente.

Soluzioni di prevenzione

Il comportamento predefinito di OnDelete() delle relazioni facoltative è ClientSetNull. Il mapping è stato modificato da RESTRICT a NO ACTION. Ciò può causare la generazione di molte operazioni nella prima migrazione aggiunta dopo l'aggiornamento a EF Core 6.0.

È possibile scegliere di applicare queste operazioni o rimuoverle manualmente dalla migrazione perché non hanno alcun impatto funzionale su EF Core.

SQL Server non supporta RESTRICT, quindi queste chiavi esterne sono già state create usando NO ACTION. Le operazioni di migrazione non avranno alcun effetto su SQL Server e sono sicure da rimuovere.

Il database in memoria convalida le proprietà necessarie non contengono valori Null

Problema di rilevamento n. 10613

Comportamento precedente

Il database in memoria ha consentito il salvataggio di valori Null anche quando la proprietà è stata configurata in base alle esigenze.

Nuovo comportamento

Il database in memoria genera un'eccezione Microsoft.EntityFrameworkCore.DbUpdateException quando SaveChanges viene chiamato o SaveChangesAsync e una proprietà obbligatoria è impostata su Null.

Perché

Il comportamento del database in memoria corrisponde ora al comportamento di altri database.

Soluzioni di prevenzione

Il comportamento precedente (ad esempio, non verificando i valori Null) può essere ripristinato durante la configurazione del provider in memoria. Ad esempio:

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

Rimozione dell'ultimo ORDER BY durante l'unione per le raccolte

Problema di rilevamento n. 19828

Comportamento precedente

Quando si eseguono JOIN SQL nelle raccolte (relazioni uno-a-molti), EF Core viene usato per aggiungere un ORDER BY per ogni colonna chiave della tabella unita in join. Ad esempio, il caricamento di tutti i blog con i post correlati è stato eseguito tramite il codice SQL seguente:

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]

Questi ordini sono necessari per la materializzazione corretta delle entità.

Nuovo comportamento

L'ultimo ORDER BY per un join di raccolta viene ora omesso:

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]

Non viene più generato un ORDER BY per la colonna ID post.

Perché

Ogni ORDER BY impone ulteriori operazioni sul lato database e l'ultimo ordinamento non è necessario per le esigenze di materializzazione di EF Core. I dati mostrano che la rimozione di questo ultimo ordinamento può produrre un miglioramento significativo delle prestazioni in alcuni scenari.

Soluzioni di prevenzione

Se l'applicazione prevede che le entità unite vengano restituite in un ordine specifico, renderle esplicite aggiungendo un operatore LINQ OrderBy alla query.

DbSet non implementa più IAsyncEnumerable

Problema di rilevamento n. 24041

Comportamento precedente

DbSet<TEntity>, usato per eseguire query in DbContext, usato per implementare IAsyncEnumerable<T>.

Nuovo comportamento

DbSet<TEntity> non implementa IAsyncEnumerable<T>più direttamente .

Perché

DbSet<TEntity> è stato originariamente creato per implementare IAsyncEnumerable<T> principalmente per consentire l'enumerazione diretta su di esso tramite il foreach costrutto. Sfortunatamente, quando un progetto fa riferimento anche a System.Linq.Async per comporre operatori LINQ asincroni sul lato client, si è verificato un errore di chiamata ambiguo tra gli operatori definiti su IQueryable<T> e quelli definiti su IAsyncEnumerable<T>. C# 9 ha aggiunto il supporto dell'estensione GetEnumerator per foreach i cicli, rimuovendo il motivo principale originale per fare riferimento IAsyncEnumerablea .

La maggior parte degli DbSet utilizzi continuerà a funzionare così com'è, poiché compongono operatori LINQ su DbSet, enumerarlo e così via. Gli unici utilizzi interrotti sono quelli che tentano di eseguire il cast DbSet direttamente a IAsyncEnumerable.

Soluzioni di prevenzione

Se è necessario fare riferimento a come DbSet<TEntity> IAsyncEnumerable<T>, chiamare DbSet<TEntity>.AsAsyncEnumerable per eseguirne il cast in modo esplicito.

Anche il tipo di entità restituito TVF viene mappato a una tabella per impostazione predefinita

Problema di rilevamento n. 23408

Comportamento precedente

Un tipo di entità non è stato mappato a una tabella per impostazione predefinita quando viene usato come tipo restituito di un file CONF configurato con HasDbFunction.

Nuovo comportamento

Un tipo di entità usato come tipo restituito di un valore TVF mantiene il mapping predefinito della tabella.

Perché

Non è intuitivo che la configurazione di un file TVF rimuove il mapping di tabella predefinito per il tipo di entità restituito.

Soluzioni di prevenzione

Per rimuovere il mapping predefinito della tabella, chiamare ToTable(EntityTypeBuilder, String):

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

Verificare l'univocità del nome del vincolo ora convalidata

Problema di rilevamento n. 25061

Comportamento precedente

I vincoli Check con lo stesso nome possono essere dichiarati e usati nella stessa tabella.

Nuovo comportamento

La configurazione esplicita di due vincoli CHECK con lo stesso nome nella stessa tabella comporterà ora un'eccezione. Ai vincoli Check creati da una convenzione verrà assegnato un nome univoco.

Perché

La maggior parte dei database non consente la creazione di due vincoli CHECK con lo stesso nome nella stessa tabella e alcuni richiedono che siano univoci anche tra tabelle. Ciò comporta la generazione di un'eccezione durante l'applicazione di una migrazione.

Soluzioni di prevenzione

In alcuni casi, i nomi dei vincoli check validi potrebbero essere diversi a causa di questa modifica. Per specificare il nome desiderato in modo esplicito, chiamare HasName:

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

Aggiunta di interfacce di metadati IReadOnly e metodi di estensione rimossi

Problema di rilevamento n. 19213

Comportamento precedente

Sono presenti tre set di interfacce di metadati: IModele IConventionModel IMutableModel nonché metodi di estensione.

Nuovo comportamento

È stato aggiunto un nuovo set di IReadOnly interfacce, ad esempio IReadOnlyModel. I metodi di estensione definiti in precedenza per le interfacce di metadati sono stati convertiti in metodi di interfaccia predefiniti.

Perché

I metodi di interfaccia predefiniti consentono di eseguire l'override dell'implementazione, che viene sfruttata dalla nuova implementazione del modello di runtime per offrire prestazioni migliori.

Soluzioni di prevenzione

Queste modifiche non devono influire sulla maggior parte del codice. Tuttavia, se si usano i metodi di estensione tramite la sintassi di chiamata statica, è necessario convertirlo nella sintassi di chiamata dell'istanza.

IExecutionStrategy è ora un servizio singleton

Problema di rilevamento n. 21350

Nuovo comportamento

IExecutionStrategy è ora un servizio singleton. Ciò significa che qualsiasi stato aggiunto nelle implementazioni personalizzate rimarrà tra le esecuzioni e il delegato passato a ExecutionStrategy verrà eseguito una sola volta.

Perché

Questa riduzione delle allocazioni in due percorsi ad accesso frequente in Entity Framework.

Soluzioni di prevenzione

Le implementazioni derivate da ExecutionStrategy devono cancellare qualsiasi stato in OnFirstExecution().

La logica condizionale nel delegato passato a ExecutionStrategy deve essere spostata in un'implementazione personalizzata di IExecutionStrategy.

SQL Server: altri errori sono considerati temporanei

Problema di rilevamento n. 25050

Nuovo comportamento

Gli errori elencati nel problema precedente sono ora considerati temporanei. Quando si usa la strategia di esecuzione predefinita (non di ripetizione dei tentativi), questi errori verranno ora inclusi in un'istanza di eccezione aggiuntiva.

Perché

Continuiamo a raccogliere commenti e suggerimenti sia dagli utenti che dal team di SQL Server in cui gli errori devono essere considerati temporanei.

Soluzioni di prevenzione

Per modificare il set di errori considerati temporanei, usare una strategia di esecuzione personalizzata che può essere derivata da SqlServerRetryingExecutionStrategy - Resilienza connessione - EF Core.

Azure Cosmos DB: un numero maggiore di caratteri viene preceduto da un carattere di escape nei valori 'id'

Problema di rilevamento n. 25100

Comportamento precedente

In EF Core 5 è stato eseguito l'escape solo '|' in id valori.

Nuovo comportamento

In EF Core 6, '/', '\''?' e '#' vengono inoltre preceduti da un escape nei id valori .

Perché

Questi caratteri non sono validi, come documentato in Resource.Id. L'uso di tali id query causerà l'esito negativo delle query.

Soluzioni di prevenzione

È possibile eseguire l'override del valore generato impostandolo prima che l'entità venga contrassegnata come Added:

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

Alcuni servizi Singleton sono ora con ambito

Problema di rilevamento n. 25084

Nuovo comportamento

Molti servizi di query e alcuni servizi in fase di progettazione registrati come Singleton sono ora registrati come Scoped.

Perché

La durata doveva essere modificata per consentire a una nuova funzionalità di DefaultTypeMapping influire sulle query.

Le durate dei servizi in fase di progettazione sono state modificate in modo che corrispondano alla durata dei servizi in fase di esecuzione per evitare errori durante l'uso di entrambi.

Soluzioni di prevenzione

Usare TryAdd per registrare i servizi EF Core usando la durata predefinita. Usare TryAddProviderSpecificServices solo per i servizi che non vengono aggiunti da Entity Framework.

Nuova API di memorizzazione nella cache per le estensioni che aggiungono o sostituiscono servizi

Problema di rilevamento n. 19152

Comportamento precedente

In EF Core 5, GetServiceProviderHashCode restituito long ed è stato usato direttamente come parte della chiave della cache per il provider di servizi.

Nuovo comportamento

GetServiceProviderHashCode ora restituisce int e viene usato solo per calcolare il codice hash della chiave della cache per il provider di servizi.

Inoltre, ShouldUseSameServiceProvider deve essere implementato per indicare se l'oggetto corrente rappresenta la stessa configurazione del servizio e pertanto può usare lo stesso provider di servizi.

Perché

Solo l'uso di un codice hash come parte della chiave della cache ha causato conflitti occasionali difficili da diagnosticare e correggere. Il metodo aggiuntivo garantisce che lo stesso provider di servizi venga usato solo quando appropriato.

Soluzioni di prevenzione

Molte estensioni non espongono opzioni che influiscono sui servizi registrati e possono usare l'implementazione seguente di ShouldUseSameServiceProvider:

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

    ...

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

In caso contrario, è necessario aggiungere predicati aggiuntivi per confrontare tutte le opzioni pertinenti.

Nuova procedura di inizializzazione di snapshot e modello in fase di progettazione

Problema di rilevamento n. 22031

Comportamento precedente

In EF Core 5 era necessario richiamare convenzioni specifiche prima che il modello di snapshot fosse pronto per l'uso.

Nuovo comportamento

IModelRuntimeInitializer è stato introdotto per nascondere alcuni dei passaggi necessari ed è stato introdotto un modello di runtime che non dispone di tutti i metadati delle migrazioni, quindi il modello in fase di progettazione deve essere usato per il diffing del modello.

Perché

IModelRuntimeInitializer astrae i passaggi di finalizzazione del modello, in modo che possano ora essere modificati senza ulteriori modifiche di rilievo per gli utenti.

Il modello di runtime ottimizzato è stato introdotto per migliorare le prestazioni di runtime. Include diverse ottimizzazioni, una delle quali rimuove i metadati non usati in fase di esecuzione.

Soluzioni di prevenzione

Il frammento di codice seguente illustra come verificare se il modello corrente è diverso dal modello di snapshot:

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

Questo frammento di codice illustra come implementare IDesignTimeDbContextFactory<TContext> creando un modello esternamente e chiamando UseModel:

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 restituisce ora un tipo diverso

Problema di rilevamento n. 24005

Comportamento precedente

In EF Core 5, HasIndex restituito IndexBuilder<TEntity> dove TEntity è il tipo di proprietario.

Nuovo comportamento

HasIndex restituisce IndexBuilder<TDependentEntity>ora , dove TDependentEntity è il tipo di proprietà.

Perché

L'oggetto generatore restituito non è stato digitato correttamente.

Soluzioni di prevenzione

La ricompilazione dell'assembly rispetto alla versione più recente di EF Core sarà sufficiente per risolvere eventuali problemi causati da questa modifica.

DbFunctionBuilder.HasSchema(null) Esegue l' override [DbFunction(Schema = "schema")]

Problema di rilevamento n. 24228

Comportamento precedente

In EF Core 5, la chiamata HasSchema con null valore non ha archiviato l'origine di configurazione, quindi DbFunctionAttribute è stata in grado di eseguirne l'override.

Nuovo comportamento

La chiamata HasSchema con valore null archivia ora l'origine di configurazione e impedisce all'attributo di eseguirne l'override.

Perché

La configurazione specificata con l'API ModelBuilder non deve essere sostituibile dalle annotazioni dei dati.

Soluzioni di prevenzione

Rimuovere la HasSchema chiamata per consentire all'attributo di configurare lo schema.

Gli spostamenti pre-inizializzati vengono sottoposti a override dai valori delle query di database

Problema di rilevamento n. 23851

Comportamento precedente

Le proprietà di navigazione impostate su un oggetto vuoto sono state lasciate invariate per il rilevamento delle query, ma sono state sovrascritte per le query non di rilevamento. Si considerino ad esempio i tipi di entità seguenti:

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

Query senza rilevamento per Foo includere Bar l'oggetto impostato Foo.Bar sull'entità su cui viene eseguita una query dal database. Ad esempio, questo codice:

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

Stampata Foo.Bar.Id = 1.

Tuttavia, la stessa esecuzione della query per il rilevamento non è stata sovrascritta Foo.Bar con l'entità eseguita dal database. Ad esempio, questo codice:

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

Stampata Foo.Bar.Id = 0.

Nuovo comportamento

In EF Core 6.0 il comportamento delle query di rilevamento corrisponde ora a quello delle query senza rilevamento. Questo significa che sia questo codice:

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

E questo codice:

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

Stampare Foo.Bar.Id = 1.

Perché

Esistono due motivi per apportare questa modifica:

  1. Per garantire che il rilevamento e le query senza rilevamento abbiano un comportamento coerente.
  2. Quando viene eseguita una query su un database, è ragionevole presupporre che il codice dell'applicazione voglia recuperare i valori archiviati nel database.

Soluzioni di prevenzione

Esistono due mitigazioni:

  1. Non eseguire query sugli oggetti del database che non devono essere inclusi nei risultati. Ad esempio, nei frammenti di codice precedenti, non Include Foo.Bar deve essere restituita dall'istanza Bar del database e inclusa nei risultati.
  2. Impostare il valore della struttura di spostamento dopo l'esecuzione di query dal database. Ad esempio, nei frammenti di codice precedenti chiamare foo.Bar = new() dopo l'esecuzione della query.

È inoltre consigliabile non inizializzare le istanze di entità correlate agli oggetti predefiniti. Ciò implica che l'istanza correlata è una nuova entità, non salvata nel database, senza alcun valore di chiave impostato. Se invece l'entità correlata esiste nel database, i dati nel codice sono fondamentalmente in contrasto con i dati archiviati nel database.

I valori di stringa di enumerazione sconosciuti nel database non vengono convertiti nell'enumerazione predefinita quando viene eseguita una query

Problema di rilevamento n. 24084

Comportamento precedente

È possibile eseguire il mapping delle proprietà di enumerazione alle colonne stringa nel database tramite HasConversion<string>() o EnumToStringConverter. Di conseguenza, EF Core converte i valori stringa nella colonna in membri corrispondenti del tipo enumerazione .NET. Tuttavia, se il valore stringa non corrisponde e il membro enumerazione, la proprietà è stata impostata sul valore predefinito per l'enumerazione.

Nuovo comportamento

EF Core 6.0 genera ora un'eccezione InvalidOperationException con il messaggio "Impossibile convertire il valore stringa '{value}' dal database a qualsiasi valore nell'enumerazione '{enumType}' mappata".

Perché

La conversione nel valore predefinito può comportare un danneggiamento del database se l'entità viene salvata nuovamente nel database.

Soluzioni di prevenzione

In teoria, assicurarsi che la colonna del database contenga solo valori validi. In alternativa, implementare un ValueConverter oggetto con il comportamento precedente.

DbFunctionBuilder.HasTranslation fornisce ora gli argomenti della funzione come IReadOnlyList anziché IReadOnlyCollection

Problema di rilevamento n. 23565

Comportamento precedente

Quando si configura la conversione per una funzione definita dall'utente tramite HasTranslation il metodo , gli argomenti della funzione sono stati forniti come IReadOnlyCollection<SqlExpression>.

Nuovo comportamento

In EF Core 6.0 gli argomenti vengono ora forniti come IReadOnlyList<SqlExpression>.

Perché

IReadOnlyList consente di usare gli indicizzatori, quindi gli argomenti sono ora più facili da accedere.

Soluzioni di prevenzione

Nessuno. IReadOnlyList implementa l'interfaccia IReadOnlyCollection , quindi la transizione deve essere semplice.

Il mapping predefinito della tabella non viene rimosso quando l'entità viene mappata a una funzione con valori di tabella

Problema di rilevamento n. 23408

Comportamento precedente

Quando è stato eseguito il mapping di un'entità a una funzione con valori di tabella, il mapping predefinito a una tabella è stato rimosso.

Nuovo comportamento

In EF Core 6.0 l'entità viene ancora mappata a una tabella usando il mapping predefinito, anche se è mappata anche alla funzione con valori di tabella.

Perché

Le funzioni con valori di tabella che restituiscono entità vengono spesso usate come helper o per incapsulare un'operazione che restituisce una raccolta di entità, anziché come sostituzione rigorosa dell'intera tabella. Questa modifica mira a essere più in linea con la probabile intenzione dell'utente.

Soluzioni di prevenzione

Il mapping a una tabella può essere disabilitato in modo esplicito nella configurazione del modello:

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

dotnet-ef è destinato a .NET 6

Problema di rilevamento n. 27787

Comportamento precedente

Il comando dotnet-ef ha come destinazione .NET Core 3.1 per un po'. In questo modo è possibile usare una versione più recente dello strumento senza installare versioni più recenti del runtime .NET.

Nuovo comportamento

In EF Core 6.0.6 lo strumento dotnet-ef è ora destinato a .NET 6. È comunque possibile usare lo strumento nei progetti destinati a versioni precedenti di .NET e .NET Core, ma è necessario installare il runtime .NET 6 per eseguire lo strumento.

Perché

.NET 6.0.200 SDK ha aggiornato il comportamento di dotnet tool install in osx-arm64 per creare uno shim osx-x64 per gli strumenti destinati a .NET Core 3.1. Per mantenere un'esperienza predefinita funzionante per dotnet-ef, è necessario aggiornarla in .NET 6 come destinazione.

Soluzioni di prevenzione

Per eseguire dotnet-ef senza installare il runtime .NET 6, è possibile installare una versione precedente dello strumento:

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

IModelCacheKeyFactory potrebbe essere necessario aggiornare le implementazioni per gestire la memorizzazione nella cache in fase di progettazione

Problema di rilevamento n. 25154

Comportamento precedente

IModelCacheKeyFactory non dispone di un'opzione per memorizzare nella cache il modello in fase di progettazione separatamente dal modello di runtime.

Nuovo comportamento

IModelCacheKeyFactory dispone di un nuovo overload che consente di memorizzare nella cache il modello in fase di progettazione separatamente dal modello di runtime. L'implementazione di questo metodo può comportare un'eccezione simile alla seguente:

System.InvalidOperationException: 'La configurazione richiesta non è archiviata nel modello ottimizzato per la lettura, usare 'DbContext.GetService<IDesignTimeModel>(). Modello'.'

Perché

L'implementazione di modelli compilati richiedeva la separazione della fase di progettazione (usata durante la compilazione del modello) e del runtime (usata durante l'esecuzione di query e così via). Se il codice di runtime deve accedere alle informazioni in fase di progettazione, il modello in fase di progettazione deve essere memorizzato nella cache.

Soluzioni di prevenzione

Implementare il nuovo overload. Ad esempio:

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

La navigazione '{navigation}' è stata ignorata da 'Include' nella query perché la correzione lo popola automaticamente. Se in seguito vengono specificate altre operazioni di spostamento in "Includi", verranno ignorate. Non è consentito tornare indietro nell'albero di inclusione.

Problema di rilevamento n. 4315

Comportamento precedente

L'evento CoreEventId.NavigationBaseIncludeIgnored è stato registrato come avviso per impostazione predefinita.

Nuovo comportamento

L'evento CoreEventId.NavigationBaseIncludeIgnored è stato registrato come errore per impostazione predefinita e genera un'eccezione.

Perché

Questi modelli di query non sono consentiti, quindi EF Core ora genera un'eccezione per indicare che le query devono essere aggiornate.

Soluzioni di prevenzione

Il comportamento precedente può essere ripristinato configurando l'evento come avviso. Ad esempio:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Warn(CoreEventId.NavigationBaseIncludeIgnored));