Condividi tramite


Modifiche radicali in EF Core 7.0 (EF7)

Questa pagina illustra le modifiche di comportamento e API che potrebbero interrompere l'aggiornamento delle applicazioni esistenti da EF Core 6 a EF Core 7. Assicurarsi di esaminare le modifiche sostanziali precedenti se si aggiorna da una versione precedente di EF Core.

Framework di destinazione

EF Core 7.0 è destinato a .NET 6. Ciò significa che le applicazioni esistenti destinate a .NET 6 possono continuare a farlo. Le applicazioni destinate a versioni precedenti di .NET, .NET Core e .NET Framework dovranno usare .NET 6 o .NET 7 per usare EF Core 7.0.

Riepilogo

Modifica di rilievo Impatto
Encrypt si imposta per default su true per le connessioni a SQL Server Alto
Alcuni avvisi provocheranno nuovamente eccezioni per impostazione predefinita Alto
Le tabelle di SQL Server con trigger o determinate colonne calcolate richiedono ora una configurazione speciale di EF Core Alto
Le tabelle SQLite con trigger AFTER e tabelle virtuali richiedono ora una configurazione speciale di EF Core Alto
Le dipendenze orfane delle relazioni facoltative non vengono eliminate automaticamente Medio
L'eliminazione a catena viene configurata tra tabelle quando si usa il mapping TPT con SQL Server Medio
Maggiore probabilità di errori occupati/bloccati in SQLite quando non si usa la registrazione write-ahead Medio
Potrebbe essere necessario configurare le proprietà chiave con un operatore di confronto dei valori del provider Basso
I vincoli Check e altre caratteristiche della tabella sono ora configurati nella tabella Basso
Gli spostamenti dalle nuove entità alle entità eliminate non vengono risolti Basso
L'uso FromSqlRaw e i metodi correlati dal provider errato generano un'eccezione Basso
Scaffolded OnConfiguring non chiama più IsConfigured Basso

Modifiche ad alto impatto

Encrypt si imposta per impostazione predefinita su true le connessioni di SQL Server

Problema di rilevamento: SqlClient #1210

Importante

Si tratta di una modifica di rilievo grave nel pacchetto Microsoft.Data.SqlClient . Non è possibile eseguire alcuna operazione in EF Core per ripristinare o attenuare questa modifica. Inviare commenti e suggerimenti al repository GitHub Microsoft.Data.SqlClient oppure contattare un supporto tecnico Microsoft Professional per ulteriori domande o assistenza.

Comportamento precedente

Una stringa di connessione SqlClient viene utilizzata per Encrypt=False impostazione predefinita. Ciò consente le connessioni nei computer di sviluppo in cui il server locale non dispone di un certificato valido.

Nuovo comportamento

Le stringhe di connessione SqlClient sono utilizzate per Encrypt=True impostazione predefinita. Ciò significa che:

  • Il server deve essere configurato con un certificato valido
  • Il client deve considerare attendibile questo certificato

Se queste condizioni non vengono soddisfatte, verrà generata un'eccezione SqlException . Ad esempio:

È stata stabilita una connessione con il server, ma si è verificato un errore durante il processo di accesso. (provider: provider SSL, errore: 0: la catena di certificati è stata emessa da un'autorità non attendibile)

Perché

Questa modifica è stata apportata per assicurarsi che, per impostazione predefinita, la connessione sia sicura o che l'applicazione non riesca a connettersi.

Soluzioni di prevenzione

Esistono tre modi per procedere:

  1. Installare un certificato valido nel server. Si noti che si tratta di un processo coinvolto e richiede di ottenere un certificato e di assicurarsi che sia firmato da un'autorità attendibile dal client.
  2. Se il server dispone di un certificato, ma non è considerato attendibile dal client, TrustServerCertificate=True per consentire di ignorare il normale meccanismo di attendibilità.
  3. Aggiungere Encrypt=False esplicitamente alla stringa di connessione.

Avviso

Le opzioni 2 e 3 lasciano il server in uno stato potenzialmente non sicuro.

Alcuni avvisi generano nuovamente eccezioni per impostazione predefinita

Problema di rilevamento n. 29069

Comportamento precedente

In EF Core 6.0, un bug nel provider SQL Server significava che alcuni avvisi configurati per generare eccezioni per impostazione predefinita venivano registrati ma non generavano eccezioni. Questi avvisi sono:

EventId Descrizione
RelationalEventId.AmbientTransactionWarning È possibile che un'applicazione abbia previsto che venga usata una transazione di ambiente quando è stata effettivamente ignorata.
RelationalEventId.IndexPropertiesBothMappedAndNotMappedToTable Un indice specifica le proprietà, alcune delle quali sono mappate a una colonna nella tabella e altre no.
RelationalEventId.IndexPropertiesMappedToNonOverlappingTables Un indice specifica le proprietà di cui viene eseguito il mapping alle colonne in tabelle non sovrapposte.
RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables Una chiave esterna specifica le proprietà che non eseguono il mapping alle tabelle correlate.

Nuovo comportamento

A partire da EF Core 7.0, questi avvisi, per impostazione predefinita, generano un'eccezione.

Perché

Si tratta di problemi che indicano molto probabilmente un errore nel codice dell'applicazione che deve essere risolto.

Soluzioni di prevenzione

Consente di risolvere il problema sottostante che è il motivo dell'avviso.

In alternativa, il livello di avviso può essere modificato in modo che venga solo registrato o completamente soppresso. Ad esempio:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Ignore(RelationalEventId.AmbientTransactionWarning));

Le tabelle di SQL Server con trigger o determinate colonne calcolate richiedono ora una configurazione speciale di EF Core

Problema di rilevamento n. 27372

Comportamento precedente

Le versioni precedenti del provider SQL Server hanno salvato le modifiche tramite una tecnica meno efficiente che funzionava sempre.

Nuovo comportamento

Per impostazione predefinita, EF Core salva ora le modifiche tramite una tecnica significativamente più efficiente; purtroppo questa tecnica non è supportata in SQL Server se la tabella di destinazione include trigger di database o determinati tipi di colonne calcolate. Per altri dettagli, vedere la documentazione di SQL Server.

Perché

I miglioramenti delle prestazioni collegati al nuovo metodo sono sufficientemente significativi da renderli importanti per gli utenti per impostazione predefinita. Allo stesso tempo, si stima che l'utilizzo dei trigger di database o delle colonne calcolate interessate nelle applicazioni EF Core sia sufficientemente basso da far sì che le conseguenze negative delle modifiche che interrompono la compatibilità siano superiori al miglioramento delle prestazioni.

Soluzioni di prevenzione

In EF7 o versioni successive, se la tabella di destinazione ha un trigger, è possibile informare EF Core di ciò e EF ripristinerà la tecnica precedente meno efficiente. Questa operazione può essere eseguita configurando il tipo di entità corrispondente come indicato di seguito:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.UseSqlOutputClause(false));
}

Per altre informazioni, tra cui su come configurare questa opzione per tutte le tabelle, vedere la documentazione di SQL Server.

Le tabelle SQLite con trigger AFTER e tabelle virtuali richiedono ora una configurazione speciale di EF Core

Problema di rilevamento n. 29916

Comportamento precedente

Le versioni precedenti del provider SQLite hanno salvato le modifiche tramite una tecnica meno efficiente che funzionava sempre.

Nuovo comportamento

Per impostazione predefinita, EF Core salva ora le modifiche tramite una tecnica più efficiente, usando la clausola RETURNING. Sfortunatamente, questa tecnica non è supportata in SQLite se la tabella di destinazione include trigger AFTER del database, è virtuale o se vengono usate versioni precedenti di SQLite. Per altri dettagli, vedere la documentazione di SQLite.

Perché

Le semplificazioni e i miglioramenti delle prestazioni associati al nuovo metodo sono così significativi da renderli disponibili di default per gli utenti. Allo stesso tempo, stimiamo che l'utilizzo dei trigger di database e delle tabelle virtuali nelle applicazioni EF Core sia abbastanza basso da far sì che le conseguenze negative delle modifiche rompenti siano compensate dal miglioramento delle prestazioni.

Soluzioni di prevenzione

In EF Core 8.0, il metodo UseSqlReturningClause è stato introdotto per ripristinare in modo esplicito al SQL più vecchio e meno efficiente. Ad esempio:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.UseSqlReturningClause(false));
}

Se si usa ancora EF Core 7.0, è possibile ripristinare il meccanismo precedente per l'intera applicazione inserendo il codice seguente nella configurazione del contesto:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlite(...)
        .ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();

Modifiche a impatto medio

Le dipendenze orfane delle relazioni facoltative non vengono eliminate automaticamente

Problema di rilevamento n. 27217

Comportamento precedente

Una relazione è facoltativa se la chiave esterna è annullabile. L'impostazione della chiave esterna su Null consente l'esistenza dell'entità dipendente senza alcuna entità principale correlata. Le relazioni facoltative possono essere configurate per l'uso di eliminazioni a catena, anche se non è l'impostazione predefinita.

Un dipendente facoltativo può essere rimosso dalla relativa entità impostando la relativa chiave esterna su Null oppure cancellando la navigazione da o verso di essa. In EF Core 6.0, ciò causerebbe l'eliminazione del dipendente quando la relazione è stata configurata per l'eliminazione a catena.

Nuovo comportamento

A partire da EF Core 7.0, il dipendente non viene più eliminato. Si noti che se l'entità principale viene eliminata, l'entità dipendente verrà comunque eliminata perché le eliminazioni a catena sono configurate per la relazione.

Perché

La dipendenza può esistere senza alcuna relazione con un'entità principale, quindi interrompere la relazione non dovrebbe causare l'eliminazione della dipendenza.

Soluzioni di prevenzione

Il dipendente può essere eliminato in modo esplicito:

context.Remove(blog);

Oppure SaveChanges può essere sottoposto a override o intercettato per eliminare dipendenze senza riferimento principale. Ad esempio:

context.SavingChanges += (c, _) =>
    {
        foreach (var entry in ((DbContext)c!).ChangeTracker
            .Entries<Blog>()
            .Where(e => e.State == EntityState.Modified))
        {
            if (entry.Reference(e => e.Author).CurrentValue == null)
            {
                entry.State = EntityState.Deleted;
            }
        }
    };

L'eliminazione a catena viene configurata tra tabelle quando si usa il mapping TPT con SQL Server

Problema di rilevamento n. 28532

Comportamento precedente

Quando si esegue il mapping di una gerarchia di ereditarietà usando la strategia TPT, la tabella di base deve contenere una riga per ogni entità salvata, indipendentemente dal tipo effettivo di tale entità. L'eliminazione della riga nella tabella di base deve eliminare righe in tutte le altre tabelle. EF Core configura un'eliminazione a cascata per questo.

In EF Core 6.0, un bug nel provider di database di SQL Server significava che queste eliminazioni a catena non venivano create.

Nuovo comportamento

A partire da EF Core 7.0, le eliminazioni a catena vengono ora create per SQL Server esattamente come per gli altri database.

Perché

Le eliminazioni a catena dalla tabella di base alle tabelle secondarie in TPT consentono l'eliminazione di un'entità eliminandone la riga nella tabella di base.

Soluzioni di prevenzione

Nella maggior parte dei casi, questa modifica non dovrebbe causare problemi. Tuttavia, SQL Server è molto restrittivo quando sono configurati più comportamenti a catena tra tabelle. Ciò significa che se esiste una relazione di propagazione esistente tra le tabelle nel mapping TPT, SQL Server potrebbe generare l'errore seguente:

La Microsoft.Data.SqlClient.SqlException: l'istruzione DELETE è in conflitto con il vincolo REFERENCE "FK_Blogs_People_OwnerId". Il conflitto si è verificato nel database "Scratch", tabella "dbo. Blogs", colonna 'OwnerId'. L'istruzione è stata interrotta.

Ad esempio, questo modello crea un ciclo di relazioni a catena:

[Table("FeaturedPosts")]
public class FeaturedPost : Post
{
    public int ReferencePostId { get; set; }
    public Post ReferencePost { get; set; } = null!;
}

[Table("Posts")]
public class Post
{
    public int Id { get; set; }
    public string? Title { get; set; }
    public string? Content { get; set; }
}

Uno di questi dovrà essere configurato per non usare le eliminazioni a catena nel server. Ad esempio, per modificare la relazione esplicita:

modelBuilder
    .Entity<FeaturedPost>()
    .HasOne(e => e.ReferencePost)
    .WithMany()
    .OnDelete(DeleteBehavior.ClientCascade);

In alternativa, per modificare la relazione implicita creata per il mapping TPT:

modelBuilder
    .Entity<FeaturedPost>()
    .HasOne<Post>()
    .WithOne()
    .HasForeignKey<FeaturedPost>(e => e.Id)
    .OnDelete(DeleteBehavior.ClientCascade);

Maggiore probabilità di errori occupati/bloccati in SQLite quando non si usa il logging in modalità write-ahead

Comportamento precedente

Le versioni precedenti del provider SQLite hanno salvato le modifiche tramite una tecnica meno efficiente che è stata in grado di riprovare automaticamente quando la tabella era bloccata/occupata e la registrazione write-ahead (WAL) non era abilitata.

Nuovo comportamento

Per impostazione predefinita, EF Core salva ora le modifiche tramite una tecnica più efficiente, usando la clausola RETURNING. Sfortunatamente, questa tecnica non è in grado di riprovare automaticamente quando è occupata/bloccata. In un'applicazione multithread (ad esempio un'applicazione web) che non utilizza il log scrittura anticipata, è comune riscontrare questi errori.

Perché

Le semplificazioni e i miglioramenti delle prestazioni associati al nuovo metodo sono così significativi da renderli disponibili di default per gli utenti. I database creati da EF Core abilitano di default anche il write-ahead logging. Il team di SQLite consiglia anche di abilitare la registrazione write-ahead per impostazione predefinita.

Soluzioni di prevenzione

Se possibile, è consigliabile abilitare la registrazione anticipata nel database. Se il database è stato creato da EF, questo dovrebbe già essere il caso. In caso contrario, è possibile abilitare la registrazione anticipata eseguendo il comando seguente.

PRAGMA journal_mode = 'wal';

Se, per qualche motivo, non è possibile abilitare la registrazione write-ahead, è possibile ripristinare il meccanismo precedente per l'intera applicazione inserendo il codice seguente nella configurazione del contesto:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlite(...)
        .ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();

Modifiche a basso impatto

Potrebbe essere necessario configurare le proprietà chiave con un operatore di confronto dei valori del provider

Problema di rilevamento n. 27738

Comportamento precedente

In EF Core 6.0, i valori chiave acquisiti direttamente dalle proprietà dei tipi di entità sono stati usati per il confronto dei valori delle chiavi durante il salvataggio delle modifiche. In questo modo si userà qualsiasi operatore di confronto di valori personalizzato configurato in queste proprietà.

Nuovo comportamento

A partire da EF Core 7.0, i valori del database vengono usati per questi confronti. Questo "funziona solo" per la maggior parte dei casi. Tuttavia, se le proprietà utilizzavano un operatore di confronto personalizzato e tale operatore di confronto non può essere applicato ai valori del database, potrebbe essere necessario un "operatore di confronto dei valori del provider", come illustrato di seguito.

Perché

Varie suddivisioni di entità e di tabelle possono comportare l'attribuzione di più proprietà alla stessa colonna del database e viceversa. Questo richiede che i valori vengano confrontati dopo la conversione in valore che verrà usato nel database.

Soluzioni di prevenzione

Configurare un comparatore di valori per il provider. Si consideri ad esempio il caso in cui un oggetto valore viene usato come chiave e l'operatore di confronto per tale chiave usa confronti tra stringhe senza distinzione tra maiuscole e minuscole:

var blogKeyComparer = new ValueComparer<BlogKey>(
    (l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
    v => v.Id.ToUpper().GetHashCode(),
    v => v);

var blogKeyConverter = new ValueConverter<BlogKey, string>(
    v => v.Id,
    v => new BlogKey(v));

modelBuilder.Entity<Blog>()
    .Property(e => e.Id).HasConversion(
        blogKeyConverter, blogKeyComparer);

I valori del database (stringhe) non possono usare direttamente l'operatore di confronto definito per BlogKey i tipi. Pertanto, è necessario configurare un comparatore di provider per confronti tra stringhe senza distinzione tra maiuscole e minuscole.

var caseInsensitiveComparer = new ValueComparer<string>(
    (l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
    v => v.ToUpper().GetHashCode(),
    v => v);

var blogKeyComparer = new ValueComparer<BlogKey>(
    (l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
    v => v.Id.ToUpper().GetHashCode(),
    v => v);

var blogKeyConverter = new ValueConverter<BlogKey, string>(
    v => v.Id,
    v => new BlogKey(v));

modelBuilder.Entity<Blog>()
    .Property(e => e.Id).HasConversion(
        blogKeyConverter, blogKeyComparer, caseInsensitiveComparer);

I vincoli di controllo e altri aspetti della tabella sono ora configurati sulla tabella.

Problema di rilevamento n. 28205

Comportamento precedente

In EF Core 6.0, HasCheckConstraint, HasCommente IsMemoryOptimized sono stati chiamati direttamente nel generatore dei tipi di entità. Ad esempio:

modelBuilder
    .Entity<Blog>()
    .HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023");

modelBuilder
    .Entity<Blog>()
    .HasComment("It's my table, and I'll delete it if I want to.");

modelBuilder
    .Entity<Blog>()
    .IsMemoryOptimized();

Nuovo comportamento

A partire da EF Core 7.0, questi metodi vengono invece chiamati nel generatore di tabelle:

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023"));

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.HasComment("It's my table, and I'll delete it if I want to."));

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.IsMemoryOptimized());

I metodi esistenti sono stati contrassegnati come Obsolete. Attualmente hanno lo stesso comportamento dei nuovi metodi, ma verranno rimossi in una versione futura.

Perché

Questi facet si applicano solo alle tabelle. Non verranno applicate a viste, funzioni o stored procedure mappate.

Soluzioni di prevenzione

Usare i metodi del generatore di tabelle, come illustrato in precedenza.

Problema di rilevamento n. 28249

Comportamento precedente

In EF Core 6.0, quando una nuova entità viene tracciata tramite una query di tracciamento o collegandola a , le navigazioni verso e dalle entità correlate nello stato sono corrette.

Nuovo comportamento

A partire da EF Core 7.0, le navigazioni da e verso le entità Deleted non vengono fissate.

Perché

Quando un'entità viene contrassegnata come Deleted raramente ha senso associarla a entità non eliminate.

Soluzioni di prevenzione

Interrogare o allegare entità prima di contrassegnarle come Deleted, o impostare manualmente le proprietà di navigazione verso e dall'entità eliminata.

Problema di rilevamento n. 26502

Comportamento precedente

In EF Core 6.0, l'uso del metodo di estensione di Azure Cosmos DB FromSqlRaw con un provider relazionale o del metodo di estensione relazionale FromSqlRaw con il provider Azure Cosmos DB potrebbe fallire senza avviso. Analogamente, l'uso di metodi relazionali nel provider in memoria è un no-op invisibile all'utente.

Nuovo comportamento

A partire da EF Core 7.0, l'uso di un metodo di estensione progettato per un provider in un provider diverso genererà un'eccezione.

Perché

Il metodo di estensione corretto deve essere usato per funzionare correttamente in tutte le situazioni.

Soluzioni di prevenzione

Usare il metodo di estensione corretto per il provider in uso. Se viene fatto riferimento a più provider, chiamare il metodo di estensione come metodo statico. Ad esempio:

var result = await CosmosQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToListAsync();

Oppure:

var result = await RelationalQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToListAsync();

Scaffolded OnConfiguring non chiama più IsConfigured

Problema di rilevamento n. 4274

Comportamento precedente

In EF Core 6.0, il tipo DbContext generato tramite scaffolding da un database esistente conteneva una chiamata a IsConfigured. Ad esempio:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
        optionsBuilder.UseNpgsql("MySecretConnectionString");
    }
}

Nuovo comportamento

A partire da EF Core 7.0, la chiamata a IsConfigured non è più inclusa.

Perché

Esistono scenari molto limitati in cui il provider di database è configurato all'interno di DbContext in alcuni casi, ma solo se il contesto non è già configurato. Lasciando qui, invece, OnConfiguring è più probabile che un stringa di connessione contenente informazioni riservate venga lasciato nel codice, nonostante l'avviso in fase di compilazione. Di conseguenza, il codice risultante è stato giudicato più sicuro e pulito grazie alla rimozione di questo elemento, e pertanto considerato vantaggioso, soprattutto dato che l'interfaccia della riga di comando --no-onconfiguring (.NET CLI) o la console del gestione pacchetti di Visual Studio -NoOnConfiguring può essere usata per impedire lo scaffolding del metodo OnConfiguring, e che sono disponibili modelli personalizzabili per riaggiungere il metodo IsConfigured se è davvero necessario.

Soluzioni di prevenzione

Uno dei seguenti:

  • Usare l'argomento --no-onconfiguring (interfaccia della riga di comando di .NET) o -NoOnConfiguring (Console Gestione Pacchetti di Visual Studio) durante il scaffolding da un database esistente.
  • Personalizzare i modelli T4 per aggiungere nuovamente la chiamata a IsConfigured.