Modifiche di rilievo 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 di rilievo precedenti se si esegue l'aggiornamento 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
Modifiche ad alto impatto
Encrypt
per impostazione predefinita per 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
SqlClient stringa di connessione usa 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
SqlClient stringa di connessione usa 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:
- 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.
- Se il server dispone di un certificato, ma non è considerato attendibile dal client,
TrustServerCertificate=True
per consentire di ignorare il normale meccanismo di attendibilità. - Aggiungere
Encrypt=False
in modo esplicito al 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à di cui è stato eseguito il mapping e alcune delle quali non sono mappate a una colonna in una tabella. |
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 registrato solo o eliminato completamente. 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 che le conseguenze negative delle modifiche di rilievo siano superiori al miglioramento delle prestazioni.
Soluzioni di prevenzione
A partire da EF Core 8.0, l'uso o meno della clausola "OUTPUT" può essere configurato in modo esplicito. Ad esempio:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.ToTable(tb => tb.UseSqlOutputClause(false));
}
In EF7 o versioni successive, se la tabella di destinazione ha un trigger, è possibile comunicare a EF Core questo comportamento e EF tornerà alla 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.HasTrigger("SomeTrigger"));
}
Si noti che questa operazione non comporta effettivamente la creazione o la gestione del trigger da parte di EF Core. Attualmente comunica solo a EF Core che i trigger sono presenti nella tabella. Di conseguenza, è possibile usare qualsiasi nome di trigger. È possibile specificare un trigger per ripristinare il comportamento precedente anche se non è effettivamente presente un trigger nella tabella.
Se la maggior parte o tutte le tabelle hanno trigger, è possibile rifiutare esplicitamente di usare la tecnica più recente ed efficiente per tutte le tabelle del modello usando la convenzione di compilazione del modello seguente:
public class BlankTriggerAddingConvention : IModelFinalizingConvention
{
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
if (table != null
&& entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null)
&& (entityType.BaseType == null
|| entityType.GetMappingStrategy() != RelationalAnnotationNames.TphMappingStrategy))
{
entityType.Builder.HasTrigger(table.Value.Name + "_Trigger");
}
foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
{
if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null))
{
entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger");
}
}
}
}
}
Usare la convenzione per l'oggetto DbContext
eseguendo l'override di ConfigureConventions
:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention());
}
Questa operazione chiama HasTrigger
in modo efficace tutte le tabelle del modello, invece di doverla eseguire manualmente per ogni tabella e ogni tabella.
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é
Per impostazione predefinita, le semplificazioni e i miglioramenti delle prestazioni collegati al nuovo metodo sono sufficientemente importanti da renderli agli utenti. Allo stesso tempo, si stima che l'utilizzo dei trigger di database e delle tabelle virtuali nelle applicazioni EF Core sia sufficientemente basso che le conseguenze negative delle modifiche di rilievo siano superiori al miglioramento delle prestazioni.
Soluzioni di prevenzione
In EF Core 8.0 il UseSqlReturningClause
metodo è stato introdotto per ripristinare in modo esplicito sql meno efficiente 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 è nullable. 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à viene eliminata, il dipendente verrà comunque eliminato perché le eliminazioni a catena sono configurate per la relazione.
Perché
Il dipendente può esistere senza alcuna relazione con un'entità di sicurezza, pertanto non è consigliabile eliminare l'entità.
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 dipendenti senza riferimenti all'entità. 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 catena per questa operazione.
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:
Microsoft.Data.SqlClient.SqlException: 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 la registrazione 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 è occupato/bloccato. In un'applicazione multithread (ad esempio un'app Web) che non usa la registrazione write-ahead, è comune riscontrare questi errori.
Perché
Per impostazione predefinita, le semplificazioni e i miglioramenti delle prestazioni collegati al nuovo metodo sono sufficientemente importanti da renderli agli utenti. I database creati da EF Core abilitano anche la registrazione write-ahead per impostazione predefinita. Il team di SQLite consiglia anche di abilitare la registrazione write-ahead per impostazione predefinita.
Soluzioni di prevenzione
Se possibile, è consigliabile abilitare la registrazione write-ahead nel database. Se il database è stato creato da ENTITY, questo dovrebbe essere già il caso. In caso contrario, è possibile abilitare la registrazione write-ahead 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 suddivisione delle tabelle possono comportare il mapping di più proprietà alla stessa colonna di 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 operatore di confronto dei valori del 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 operatore di confronto 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 Check e altri facet di tabella sono ora configurati nella tabella
Problema di rilevamento n. 28205
Comportamento precedente
In EF Core 6.0, HasCheckConstraint
, HasComment
e 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.
Gli spostamenti dalle nuove entità alle entità eliminate non vengono risolti
Problema di rilevamento n. 28249
Comportamento precedente
In EF Core 6.0, quando viene rilevata una nuova entità da una query di rilevamento o collegandola a DbContext
, gli spostamenti verso e dalle entità correlate nello Deleted
stato vengono risolte.
Nuovo comportamento
A partire da EF Core 7.0, gli spostamenti da e Deleted
verso le entità non vengono risolti.
Perché
Quando un'entità viene contrassegnata come Deleted
raramente ha senso associarla a entità non eliminate.
Soluzioni di prevenzione
Eseguire query o collegare entità prima di contrassegnare le entità come Deleted
o impostare manualmente le proprietà di navigazione su e dall'entità eliminata.
L'uso FromSqlRaw
e i metodi correlati del provider errato generano un'eccezione use-the-correct-method
Problema di rilevamento n. 26502
Comportamento precedente
In EF Core 6.0, l'uso del metodo di estensione Azure Cosmos DB FromSqlRaw quando si usa un provider relazionale o il metodo di estensione relazionale FromSqlRaw quando si usa il provider Azure Cosmos DB potrebbe non riuscire automaticamente. 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 = CosmosQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();
Oppure:
var result = RelationalQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();
Scaffolding OnConfiguring
non più chiamate IsConfigured
Problema di rilevamento n. 4274
Comportamento precedente
In EF Core 6.0 il DbContext
tipo sottoposto a 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 più sicuro e pulito dalla rimozione di questo è stato considerato utile, soprattutto dato che l'interfaccia --no-onconfiguring
della riga di comando (.NET) o -NoOnConfiguring
(Console di Visual Studio Gestione pacchetti) può essere usato per impedire lo scaffolding del OnConfiguring
metodo e che i modelli personalizzabili esistono per aggiungere di nuovo IsConfigured
se è davvero necessario.
Soluzioni di prevenzione
Uno dei seguenti:
- Usare l'argomento (interfaccia della riga di comando di .NET) o
-NoOnConfiguring
(Visual Studio Gestione pacchetti Console) durante lo--no-onconfiguring
scaffolding da un database esistente. - Personalizzare i modelli T4 per aggiungere nuovamente la chiamata a
IsConfigured
.