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
* 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:
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(); });
Assicurarsi che il dipendente contenga almeno una proprietà obbligatoria.
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.
Azure Cosmos DB: i tipi di entità correlati vengono individuati come di proprietà
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 | |
SetNull | SET NULL |
ClientSetNull |
Di seguito sono riportate le modifiche per lo scaffolding .
ON DELETE | OnDelete() |
---|---|
NO ACTION | 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 IAsyncEnumerable
a .
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:
- Per garantire che il rilevamento e le query senza rilevamento abbiano un comportamento coerente.
- 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:
- 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'istanzaBar
del database e inclusa nei risultati. - 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.
NavigationBaseIncludeIgnored
è ora un errore per impostazione predefinita
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));