Modifiche in rilievo in EF Core 6.0

Le modifiche all'API e al comportamento seguenti hanno la possibilità di 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 essere destinate a .NET 6 per usare EF Core 6.0.

Riepilogo

Modifica di rilievo Impatto
Non è possibile salvare le dipendenze facoltative annidate che condividono una tabella e senza proprietà necessarie Alto
La modifica del proprietario di un'entità di proprietà genera ora un'eccezione Medio
Cosmos: i tipi di entità correlati vengono individuati come di proprietà Medio
SQLite: le connessioni vengono raggruppate Medio
Le relazioni molti-a-molti senza entità di join mappate sono ora scaffolded Medio
Mapping pulito tra i valori DeleteBehavior e ON DELETE Basso
Il database in memoria convalida le proprietà necessarie non contengono valori Null Basso
Rimosso l'ultimo ORDER BY durante l'aggiunta per le raccolte Basso
DbSet non implementa più IAsyncEnumerable Basso
Il tipo di entità restituito TVF viene anche 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 vengono considerati temporanei Basso
Cosmos: altri caratteri vengono 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 sostituiscino i servizi Basso*
Nuova procedura di inizializzazione del modello snapshot e progettazione Basso
OwnedNavigationBuilder.HasIndex restituisce un tipo diverso ora 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 enumerazione sconosciuti nel database non vengono convertiti nel valore predefinito dell'enumerazione quando viene eseguita una query Basso
DbFunctionBuilder.HasTranslation fornisce ora gli argomenti della funzione come IReadOnlyList anziché IReadOnlyCollection Basso
Il mapping di tabelle predefinito non viene rimosso quando l'entità viene mappata a una funzione con valori di tabella Basso
dotnet-ef destinazioni .NET 6 Basso
IModelCacheKeyFactory le implementazioni possono essere aggiornate per gestire la memorizzazione nella cache in fase di progettazione Basso

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

Modifiche ad alto impatto

I dipendenti facoltativi annidati che condividono una tabella e senza proprietà necessarie non sono consentite

Problema di rilevamento #24558

Comportamento precedente

I modelli con dipendenti facoltativi annidati che condividono una tabella e senza proprietà necessarie sono stati consentiti, ma potrebbero causare la perdita di dati quando si esegue una query sui dati e quindi si salva 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 è necessaria e tutti questi tipi di entità vengono mappati alla stessa tabella. Le regole per i dipendenti facoltativi (anziché i dipendenti obbligatori) dicono che se tutte le colonne per ContactInfo sono Null, non verrà creata alcuna istanza di ContactInfo quando si esegue una query per il proprietario Customer. Tuttavia, ciò significa anche che nessuna istanza di Address verrà creata, 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 usando la condivisione di tabelle e contenente 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, un'istanza dell'oggetto non verrà creata 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 in base alle esigenze 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 condivide una tabella e senza proprietà necessarie spesso comporta 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 necessari i dipendenti. 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 con dipendenti facoltativi ed esempi di queste mitigazioni sono inclusi nella documentazione relativa alle novità di EF Core 6.0.

Modifiche di impatto medio

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

Problema di rilevamento #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 quindi non può essere modificato o contrassegnato come modificato. Per modificare l'entità di un'entità esistente con una chiave esterna di identificazione, eliminare prima il dipendente e richiamare "SaveChanges" e quindi associare il dipendente alla nuova entità.

Perché

Anche se non è necessario che le proprietà chiave esistano in un tipo di proprietà, EF 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, ciò comporta la modifica dell'identità dell'entità. Questo non è ancora supportato completamente in EF Core ed è stato consentito solo per le entità di proprietà, a volte causando lo stato interno che 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 #24803Novità: Impostazione predefinita per la 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à corrispondenti a una DbSet<TEntity> proprietà verranno individuati come non di proprietà.

Perché

Questo comportamento segue il modello comune di dati di modellazione in Azure Cosmos DB di incorporamento di dati correlati in un singolo documento. Cosmos DB non supporta l'aggiunta nativa 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à da chiamare non di proprietà modelBuilder.Entity<MyEntity>();

SQLite: le connessioni vengono raggruppate

Problema di rilevamento #13837Novità: Impostazione predefinita per la proprietà implicita

Comportamento precedente

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

Nuovo comportamento

A partire da 6.0, le connessioni vengono ora raggruppate per impostazione predefinita. Ciò comporta che i file di database vengano mantenuti aperti dal processo anche dopo la chiusura dell'oggetto di connessione ADO.NET.

Perché

Il pool delle connessioni sottostanti migliora notevolmente le prestazioni dell'apertura e della 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 ci sono molte connessioni a breve durata al database.

Soluzioni di prevenzione

Il pool di connessioni può essere disabilitato aggiungendo Pooling=False a una 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à di join mappate sono ora scaffolded

Problema di rilevamento #22475

Comportamento precedente

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

Nuovo comportamento

Le tabelle di join semplici contenenti solo due proprietà 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.

Perché

Molte relazioni a molti senza tipi di join espliciti sono stati introdotti 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 esplicito può essere aggiunta nuovamente al modello EF. Ad esempio, presupponendo una relazione molti-a-molti tra Post e Tag, aggiungere nuovamente 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 scaffolded. Ciò è necessario perché il tipo di entità join scaffolded deve essere rimosso dal modello prima che sia possibile usare il tipo esplicito. Questo codice deve essere rimosso ogni volta che il contesto è scaffolded, ma perché il codice precedente è in classi parziali che persiste.

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 nel modo naturale.

Modifiche a basso impatto

Mapping pulito tra i valori DeleteBehavior e ON DELETE

Problema di rilevamento #21252

Comportamento precedente

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

Nuovo comportamento

Nella tabella seguente vengono illustrate le modifiche apportate alle migrazioni.

OnDelete() ON DELETE
NoAction NO ACTION
ClientNoAction NO ACTION
Limitazione RESTRICT
Cascasde CASCADE
ClientCascade LIMITANESSUNA AZIONE
SetNull SET NULL
ClientSetNull LIMITANESSUNA AZIONE

Le modifiche per Scaffolding sono le seguenti.

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

Perché

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

Soluzioni di prevenzione

Il comportamento predefinito di OnDelete() delle relazioni facoltative è ClientSetNull. Il mapping è cambiato 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 poiché 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 sulle SQL Server e sono sicure da rimuovere.

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

Problema di rilevamento #10613

Comportamento precedente

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

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

Durante la configurazione del provider in memoria, è possibile ripristinare il comportamento precedente, ad esempio non verificando i valori Null. 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 usa per aggiungere un ORDER BY per ogni colonna chiave della tabella unita. 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 appropriata 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]

La colonna ORDER BY per la colonna ID post non viene più generata.

Perché

Ogni ORDER BY impone operazioni aggiuntive 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 realizzato 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 delle estensioni 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 un DbSet<TEntity> oggetto come 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

Per impostazione predefinita, non è stato eseguito il mapping di un tipo di entità a una tabella quando viene usato come tipo restituito di una tabella configurata con HasDbFunction.

Nuovo comportamento

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

Perché

Non è intuitivo che la configurazione di un file TVF rimuove il mapping predefinito della tabella 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 le 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 metadati IReadOnly e metodi di estensione rimossi

Problema di rilevamento n. 19213

Comportamento precedente

Sono disponibili tre set di interfacce di metadati: IModele IConventionModelIMutableModel e 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 vengono 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é

Microsoft continua a raccogliere commenti e suggerimenti da parte di utenti e SQL Server team 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 dalla SqlServerRetryingExecutionStrategy - resilienza della connessione - EF Core.

Cosmos: 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 '|' nei id valori.

Nuovo comportamento

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

Perché

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

Soluzioni di prevenzione

È possibile eseguire l'override del valore generato impostandolo prima che l'entità sia 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 #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 deve essere modificata per consentire una nuova funzionalità: DefaultTypeMapping per influire sulle query.

Le durata dei servizi in fase di progettazione sono state modificate per corrispondere alla durata dei servizi di runtime 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 EF.

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

Problema di rilevamento #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 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 quindi può usare lo stesso provider di servizi.

Perché

Solo l'uso di un codice hash come parte della chiave della cache ha causato collisioni occasionali che erano 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 del modello snapshot e progettazione

Problema di rilevamento #22031

Comportamento precedente

In EF Core 5 sono necessarie convenzioni specifiche da richiamare prima che il modello di snapshot sia stato pronto per l'uso.

Nuovo comportamento

IModelRuntimeInitializer è stato introdotto per nascondere alcuni dei passaggi necessari e è 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, quindi questi possono 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 che non vengono 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 un tipo diverso ora

Problema di rilevamento #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 #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 null valore archivia ora l'origine di configurazione e impedisce l'override dell'attributo.

Perché

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

Soluzioni di prevenzione

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

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

Problema di rilevamento #23851

Comportamento precedente

Le proprietà di spostamento 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 l'inclusione Bar impostata Foo.Bar sull'entità eseguita dalla query del 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 di query per il rilevamento non ha sovrascritto 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. Ciò significa che entrambi i codici:

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 assicurarsi che il rilevamento e le query di rilevamento 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 dal database che non devono essere inclusi nei risultati. Ad esempio, nei frammenti di codice precedenti, non IncludeFoo.Bar se l'istanza Bar non deve essere restituita dal database e inclusa nei risultati.
  2. Impostare il valore dello spostamento dopo la 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 sia una nuova entità, non salvata nel database, senza alcun set di valori chiave. 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 enumerazione sconosciuti nel database non vengono convertiti nel valore predefinito dell'enumerazione quando viene eseguita una query

Problema di rilevamento #24084

Comportamento precedente

Le proprietà enumerazione possono essere mappate alle colonne stringa nel database usando HasConversion<string>() o EnumToStringConverter. Ciò comporta la conversione dei valori stringa nella colonna ai membri corrispondenti del tipo di 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 InvalidOperationException oggetto con il messaggio "Impossibile convertire il valore stringa '{value}{enumType}' dal database a qualsiasi valore nell'enumerazione mappata".

Perché

La conversione nel valore predefinito può causare un danneggiamento del database se l'entità viene salvata in un secondo momento nel database.

Soluzioni di prevenzione

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

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

Problema di rilevamento #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 delle tabelle 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' di tempo. In questo modo è possibile usare la 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, è stato 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 ha 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 a:

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 dei modelli in fase di progettazione (usata durante la compilazione del modello) e del runtime (usati 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();