Modifiche di rilievo incluse in EF Core 3.x
Le seguenti modifiche all'API e al comportamento possono interrompere le applicazioni esistenti durante l'aggiornamento a 3.x. Le modifiche che si prevede abbiano impatto solo sui provider di database sono documentate nelle modifiche che influiscono sul provider.
Riepilogo
Modifiche ad alto impatto
Le query LINQ non vengono più valutate nel client
Problema n. 14935Vedere anche il problema n. 12795
Comportamento precedente
Nelle versioni precedenti alla versione 3.0, quando EF Core non era in grado di convertire un'espressione inclusa in una query in SQL o in un parametro, l'espressione veniva automaticamente valutata nel client. Per impostazione predefinita, la valutazione client di espressioni potenzialmente dispendiose si limitava ad attivare solo un avviso.
Nuovo comportamento
A partire dalla versione 3.0, EF Core consente solo la valutazione delle espressioni nella proiezione di primo livello (l'ultima chiamata Select()
nella query) nel client.
Quando le espressioni in altre posizioni all'interno della query non possono essere convertite in SQL o in un parametro, viene generata un'eccezione.
Perché
La valutazione client automatica delle query consente di eseguire numerose query anche nel caso in cui parti importanti delle query non possono essere convertite.
Questo comportamento può causare un comportamento imprevisto e potenzialmente dannoso che può diventare evidente solo in produzione.
Ad esempio, una condizione in una chiamata Where()
che non può essere convertita può causare il trasferimento di tutte le righe della tabella del server di database e l'applicazione del filtro nel client.
È probabile che questa situazione non venga rilevata se la tabella contiene solo alcune righe in fase di sviluppo, ma che abbia un grande impatto quando l'applicazione passa in produzione dove la tabella può contenere milioni di righe.
Gli avvisi di valutazione client inoltre si sono rivelati molto facili da ignorare durante lo sviluppo.
Inoltre, la valutazione client automatica può causare problemi in cui il miglioramento della conversione di query per espressioni specifiche causa modifiche impreviste che causano un'interruzione da una versione all'altra.
Soluzioni di prevenzione
Se una query non può essere convertita completamente, riscrivere la query in un formato che possa essere convertito o usare AsEnumerable()
, ToList()
o un elemento simile per riportare in modo esplicito i dati nel client dove possono essere quindi ulteriormente elaborati usando LINQ to Objects.
Modifiche a impatto medio
Entity Framework Core non è più incluso nel framework condiviso di ASP.NET Core
Comportamento precedente
Nelle versioni precedenti ad ASP.NET Core 3.0, quando si aggiungeva un riferimento a un pacchetto in Microsoft.AspNetCore.App
o Microsoft.AspNetCore.All
, veniva inserito EF Core e alcuni dei provider di dati di EF Core come il provider di SQL Server.
Nuovo comportamento
A partire dalla versione 3.0, il framework condiviso di ASP.NET Core non include EF Core o provider di dati di EF Core.
Perché
Prima di questa modifica, per ottenere EF Core erano necessarie procedure diverse, a seconda che l'applicazione avesse o meno come destinazione ASP.NET Core e SQL Server. Inoltre, l'aggiornamento di ASP.NET Core forzava l'aggiornamento di EF Core e del provider di SQL Server, non sempre auspicabile.
Con questa modifica, la procedura per ottenere EF Core è la stessa in tutti i provider, le implementazioni .NET supportate e i tipi di applicazioni. Gli sviluppatori possono ora controllare anche in modo preciso quando vengono aggiornati EF Core e i provider di dati di EF Core.
Soluzioni di prevenzione
Per usare EF Core in un'applicazione ASP.NET Core 3.0 o in un'altra applicazione supportata, aggiungere in modo esplicito un riferimento al pacchetto al provider di database di EF Core che verrà usato dall'applicazione.
Lo strumento da riga di comando di EF Core, dotnet ef, non è più incluso in .NET Core SDK
Comportamento precedente
Prima della versione 3.0, lo strumento dotnet ef
era incluso in .NET Core SDK ed era immediatamente disponibile dalla riga di comando di qualsiasi progetto senza richiedere passaggi aggiuntivi.
Nuovo comportamento
A partire dalla versione 3.0, .NET SDK non include lo strumento dotnet ef
pertanto, prima di poterlo usare, è necessario installarlo in modo esplicito come strumento locale o globale.
Perché
Questa modifica consente di distribuire e aggiornare dotnet ef
come uno strumento della riga di comando di .NET in NuGet, coerentemente con il fatto che anche EF Core 3.0 viene distribuito come pacchetto NuGet.
Soluzioni di prevenzione
Per essere in grado di gestire le migrazioni o eseguire lo scaffolding di DbContext
, installare dotnet-ef
come strumento globale:
dotnet tool install --global dotnet-ef
È inoltre possibile ottenerlo come strumento locale quando si ripristinano le dipendenze di un progetto che lo dichiara come dipendenza di strumenti utilizzando un file manifesto dello strumento.
Modifiche a basso impatto
I metodi FromSql, ExecuteSql ed ExecuteSqlAsync sono stati rinominati
Importante
ExecuteSqlCommand
e ExecuteSqlCommandAsync
sono deprecati. Usare invece questi metodi.
Comportamento precedente
Prima di EF Core 3.0, erano disponibili overload per questi nomi di metodo per supportare l'uso con una stringa normale o una stringa che deve essere interpolata in SQL e parametri.
Nuovo comportamento
A partire da EF Core 3.0, usare FromSqlRaw
, ExecuteSqlRaw
e ExecuteSqlRawAsync
per creare una query con parametri in cui i parametri vengono passati separatamente dalla stringa di query.
Ad esempio:
context.Products.FromSqlRaw(
"SELECT * FROM Products WHERE Name = {0}",
product.Name);
Usare FromSqlInterpolated
, ExecuteSqlInterpolated
, e ExecuteSqlInterpolatedAsync
per creare una query con parametri in cui i parametri vengono passati come parte di una stringa di query interpolata.
Ad esempio:
context.Products.FromSqlInterpolated(
$"SELECT * FROM Products WHERE Name = {product.Name}");
Si noti che entrambe le query precedenti produrranno lo stesso codice SQL con parametri con gli stessi parametri SQL.
Perché
Con gli overload di metodi come questi, è molto facile chiamare accidentalmente il metodo con stringa non elaborata anche se l'intento era chiamare il metodo con stringa interpolata e viceversa. Il risultato potrebbero essere query senza parametri, quando invece è prevista la parametrizzazione.
Soluzioni di prevenzione
Passare all'uso dei nuovi nomi di metodo.
Il metodo FromSql quando usato con la stored procedure non può essere composto
Problema di rilevamento n. 15392
Comportamento precedente
Prima di EF Core 3.0, il metodo FromSql tentò di rilevare se il codice SQL passato può essere composto. Ha eseguito la valutazione client quando SQL non era componibile come una stored procedure. La query seguente ha funzionato eseguendo la stored procedure nel server ed eseguendo FirstOrDefault sul lato client.
context.Products.FromSqlRaw("[dbo].[Ten Most Expensive Products]").FirstOrDefault();
Nuovo comportamento
A partire da EF Core 3.0, EF Core non tenterà di analizzare SQL. Pertanto, se si sta componendo dopo FromSqlRaw/FromSqlInterpolated, EF Core componi sql causando una sottoquery. Pertanto, se si usa una stored procedure con composizione, si otterrà un'eccezione per la sintassi SQL non valida.
Perché
EF Core 3.0 non supporta la valutazione automatica del client, perché è soggetta a errori, come spiegato qui.
Soluzioni di prevenzione
Se si usa una stored procedure in FromSqlRaw/FromSqlInterpolated, si sa che non può essere composta, quindi è possibile aggiungere AsEnumerable
/AsAsyncEnumerable
subito dopo la chiamata al metodo FromSql per evitare qualsiasi composizione sul lato server.
context.Products.FromSqlRaw("[dbo].[Ten Most Expensive Products]").AsEnumerable().FirstOrDefault();
I metodi FromSql possono essere specificati solo in radici di query
Comportamento precedente
Prima di EF Core 3.0, il metodo FromSql
poteva essere specificato in un punto qualsiasi nella query.
Nuovo comportamento
A partire da EF Core 3.0, i nuovi metodi FromSqlRaw
e FromSqlInterpolated
(che sostituisconoFromSql
) possono essere specificati solo per radici di query, ad esempio direttamente in DbSet<>
. Qualsiasi tentativo di specificarli altrove causerà un errore di compilazione.
Perché
La specifica di FromSql
in qualsiasi posizione diversa da un DbSet
non ha alcun significato aggiuntivo oppure valore aggiunto e può causare ambiguità in determinati scenari.
Soluzioni di prevenzione
Le chiamate di FromSql
devono essere spostate in modo da comparire direttamente nel DbSet
a cui si applicano.
Le query senza rilevamento delle modifiche non eseguono più la risoluzione delle identità
Comportamento precedente
Prima di EF Core 3.0, la stessa istanza di un'entità poteva essere usata per ogni occorrenza di un entità con tipo e ID specifici. Questo comportamento corrisponde a quello delle query con rilevamento delle modifiche. Ad esempio, la query seguente:
var results = context.Products.Include(e => e.Category).AsNoTracking().ToList();
restituisce la stessa istanza di Category
per ogni Product
associato alla categoria specificata.
Nuovo comportamento
A partire da EF Core 3.0, vengono create istanze di entità diverse quando un'entità con un determinato tipo e ID viene rilevata in posizioni diverse nel grafico restituito. La query precedente, ad esempio, ora restituirà una nuova istanza di Category
per ogni Product
anche quando due prodotti sono associati alla stessa categoria.
Perché
La risoluzione delle identità (ovvero il processo per determinare che un'entità ha lo stesso tipo e ID di un'entità rilevata in precedenza) aggiunge un ulteriore sovraccarico della memoria e delle prestazioni, il che va ad annullare il vantaggio derivante dall'uso delle query senza rilevamento delle modifiche. Inoltre, anche se in alcuni casi la risoluzione delle identità può essere utile, tuttavia non è necessaria se le entità devono essere serializzate e inviate a un client, come avviene comunemente con le query senza rilevamento delle modifiche.
Soluzioni di prevenzione
Se la risoluzione delle identità è necessaria, usare una query con rilevamento delle modifiche.
I valori di chiave temporanei non sono più impostati nelle istanze di entità
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0 i valori temporanei venivano assegnati a tutte le proprietà di chiave per cui veniva in seguito generato un valore reale dal database. In genere questi valori temporanei erano numeri negativi elevati.
Nuovo comportamento
A partire dalla versione 3.0, EF Core archivia il valore di chiave temporaneo come parte delle informazioni di rilevamento dell'entità e non modifica la proprietà di chiave.
Perché
Questa modifica è stata apportata per impedire che i valori di chiave temporanei diventino erroneamente permanenti quando un'entità rilevata in precedenza da un'istanza DbContext
viene spostata in un'altra istanza DbContext
.
Soluzioni di prevenzione
Le applicazioni che assegnano valori di chiave primaria in chiavi esterne per creare associazioni tra le entità possono dipendere dal comportamento precedente se le chiavi primarie vengono generate dall'archivio e appartengono a entità con stato Added
.
Questo può essere evitato:
- Non usando chiavi generate dall'archivio.
- Impostando le proprietà di navigazione in modo da creare relazioni anziché impostando valori di chiave esterna.
- Ottenendo i valori di chiave temporanei effettivi dalle informazioni di rilevamento dell'entità.
Ad esempio,
context.Entry(blog).Property(e => e.Id).CurrentValue
restituisce il valore temporaneo anche quandoblog.Id
non è stato impostato.
DetectChanges rispetta i valori di chiave generati dall'archivio
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0 un'entità non rilevata individuata da DetectChanges
veniva rilevata nello stato Added
e inserita come nuova riga quando veniva eseguita una chiamata a SaveChanges
.
Nuovo comportamento
A partire da EF Core 3.0, se un'entità usa valori di chiave generati e viene impostato un valore di chiave, l'entità viene rilevata nello stato Modified
.
Ciò significa che si presuppone l'esistenza di una riga per l'entità che viene aggiornata quando viene eseguita una chiamata a SaveChanges
.
Se il valore di chiave non viene impostato o se il tipo di entità non usa chiavi generate, la nuova entità viene rilevata come Added
come nelle versioni precedenti.
Perché
Questa modifica è stata apportata per rendere più semplice e coerente l'uso di grafici di entità disconnesse con chiavi generate dall'archivio.
Soluzioni di prevenzione
Questa modifica può interrompere un'applicazione se un tipo di entità è configurato per l'uso di chiavi generate ma i valori di chiave sono impostati in modo esplicito per le nuove istanze. La correzione consiste nel configurare in modo esplicito le proprietà di chiave per non usare valori generati. Ad esempio, con l'API Fluent:
modelBuilder
.Entity<Blog>()
.Property(e => e.Id)
.ValueGeneratedNever();
Oppure con annotazioni dei dati:
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
Le eliminazioni a catena vengono ora eseguite immediatamente per impostazione predefinita
Comportamento precedente
Nelle versioni precedenti alla versione 3.0, EF Core applicava azioni a catena (eliminazione delle entità dipendenti quando veniva eliminata un'entità di sicurezza obbligatoria o veniva recisa la relazione con un'entità di sicurezza obbligatoria) solo dopo la chiamata a SaveChanges.
Nuovo comportamento
A partire dalla versione 3.0, EF Core applica le azioni a catena non appena viene rilevata la condizione di attivazione.
Ad esempio, la chiamata a context.Remove()
per eliminare un'entità di sicurezza causa anche l'impostazione immediata di tutti i dipendenti obbligatori correlati rilevati su Deleted
.
Perché
Questa modifica è stata apportata per migliorare l'esperienza di data binding e scenari di controllo in cui è importante comprendere quali entità verranno eliminate prima SaveChanges
della chiamata.
Soluzioni di prevenzione
Il comportamento precedente può essere ripristinato tramite le impostazioni in context.ChangeTracker
.
Ad esempio:
context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;
Il caricamento eager delle entità correlate avviene ora in una singola query
Problema di rilevamento n. 18022
Comportamento precedente
Prima della 3.0, il caricamento eagerly degli spostamenti della raccolta tramite Include
operatori ha causato la generazione di più query nel database relazionale, una per ogni tipo di entità correlata.
Nuovo comportamento
A partire dalla versione 3.0, EF Core genera una singola query con joIN nei database relazionali.
Perché
L'esecuzione di più query per implementare una singola query LINQ ha causato numerosi problemi, tra cui prestazioni negative perché sono stati necessari più round trip del database e problemi di coerenza dei dati, in quanto ogni query potrebbe osservare uno stato diverso del database.
Soluzioni di prevenzione
Anche se tecnicamente non si tratta di una modifica che causa un'interruzione, potrebbe avere un notevole effetto sulle prestazioni dell'applicazione quando una singola query contiene un numero elevato di operatori nelle operazioni di Include
spostamento delle raccolte. Per altre informazioni e per la riscrittura delle query in modo più efficiente, vedere questo commento .
**
Semantica più chiara per DeleteBehavior.Restrict
Comportamento precedente
Prima della versione 3.0, DeleteBehavior.Restrict
creava chiavi esterne nel database con la semantica Restrict
, ma modificava anche la correzione interna in modo non ovvio.
Nuovo comportamento
A partire dalla versione 3.0, DeleteBehavior.Restrict
assicura che le chiavi esterne vengano create con la semantica Restrict
, ovvero non a cascata e con generazione di un'eccezione in caso di violazione di vincolo, senza influire sulla correzione interna di Entity Framework.
Perché
Questa modifica è stata apportata per migliorare l'esperienza di uso di DeleteBehavior
in modo intuitivo, senza effetti collaterali imprevisti.
Soluzioni di prevenzione
Il comportamento precedente può essere ripristinato tramite DeleteBehavior.ClientNoAction
.
I tipi di query vengono consolidati con tipi di entità
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0 i tipi di query erano uno strumento per eseguire query su dati che non definiscono una chiave primaria in modo strutturato. Veniva infatti usato un tipo di query per eseguire il mapping di tipi di entità senza chiavi (più probabilmente da una vista, ma anche da una tabella), mentre veniva usato un tipo di entità normale quando era disponibile una chiave (più probabilmente da una tabella, ma anche da una vista).
Nuovo comportamento
Un tipo di query diventa ora semplicemente un tipo di entità senza chiave primaria. I tipi di entità senza chiave hanno la stessa funzionalità dei tipi di query nelle versioni precedenti.
Perché
Questa modifica è stata apportata per ridurre la confusione riguardo lo scopo dei tipi di query. In particolare, si tratta di tipi di entità senza chiave che sono intrinsecamente di sola lettura per questo motivo ma non dovrebbero essere usati solo perché un tipo di entità deve essere di sola lettura. Analogamente, spesso vengono mappati alle viste solo perché le viste spesso non definiscono le chiavi.
Soluzioni di prevenzione
Le parti dell'API seguenti sono ora obsolete:
ModelBuilder.Query<>()
- È necessario chiamareModelBuilder.Entity<>().HasNoKey()
per contrassegnare un tipo di entità come tipo senza chiavi. Non ne viene eseguita la configurazione per convenzione per evitare una configurazione errata quando è prevista una chiave primaria che tuttavia non corrisponde alla convenzione.DbQuery<>
- UsareDbSet<>
.DbContext.Query<>()
- UsareDbContext.Set<>()
.IQueryTypeConfiguration<TQuery>
- UsareIEntityTypeConfiguration<TEntity>
.
Nota
A causa di un problema nella versione 3.x durante l'esecuzione di query su entità senza chiave con tutte le proprietà impostate su null
verrà null
restituito anziché un'entità, se questo problema è applicabile allo scenario, aggiungere anche la logica da gestire null
nei risultati.
L'API di configurazione per le relazioni di tipo di proprietà è stata modificata
Problema n. 12444Problema n. 9148Problema n. 14153
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0 la configurazione della relazione di proprietà veniva eseguita direttamente dopo la chiamata a OwnsOne
o OwnsMany
.
Nuovo comportamento
A partire da EF Core 3.0, è disponibile l'API Fluent per configurare una proprietà di navigazione per il proprietario usando WithOwner()
.
Ad esempio:
modelBuilder.Entity<Order>.OwnsOne(e => e.Details).WithOwner(e => e.Order);
La configurazione correlata alla relazione tra proprietario e elemento di proprietà deve essere ora concatenata dopo WithOwner()
in modo analogo a come vengono configurate altre relazioni.
La configurazione per il tipo di proprietà viene comunque concatenata dopo OwnsOne()/OwnsMany()
.
Ad esempio:
modelBuilder.Entity<Order>.OwnsOne(e => e.Details, eb =>
{
eb.WithOwner()
.HasForeignKey(e => e.AlternateId)
.HasConstraintName("FK_OrderDetails");
eb.ToTable("OrderDetails");
eb.HasKey(e => e.AlternateId);
eb.HasIndex(e => e.Id);
eb.HasOne(e => e.Customer).WithOne();
eb.HasData(
new OrderDetails
{
AlternateId = 1,
Id = -1
});
});
Inoltre, la chiamata a Entity()
, HasOne()
o Set()
con una destinazione di tipo di proprietà genera ora un'eccezione.
Perché
Questa modifica è stata apportata per creare una separazione più netta tra la configurazione del tipo di proprietà e la relazione con il tipo di proprietà.
Ciò consente di eliminare ambiguità e confusione su metodi come HasForeignKey
.
Soluzioni di prevenzione
Modificare la configurazione delle relazioni dei tipi di proprietà per usare la superficie della nuova API come illustrato nell'esempio precedente.
Le entità dipendenti che condividono la tabella con l'entità di sicurezza sono ora facoltative
Comportamento precedente
Si consideri il modello seguente:
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public OrderDetails Details { get; set; }
}
public class OrderDetails
{
public int Id { get; set; }
public string ShippingAddress { get; set; }
}
Prima di EF Core 3.0, se OrderDetails
è di proprietà di Order
o viene mappato in modo esplicito alla stessa tabella, era sempre necessaria un'istanza di OrderDetails
per l'aggiunta di un nuovo Order
.
Nuovo comportamento
A partire dalla versione 3.0, EF Core consente di aggiungere un Order
senza un OrderDetails
ed esegue il mapping di tutte le proprietà di OrderDetails
, tranne che la chiave primaria, a colonne che ammettono valori Null.
In fase di query, EF Core imposta OrderDetails
su null
se una delle relative proprietà obbligatorie non ha un valore o se non sono presenti proprietà obbligatorie oltre alla chiave primaria e tutte le proprietà sono null
.
Soluzioni di prevenzione
Se il modello include una tabella condivisa dipendente con tutte le colonne facoltative, ma è previsto che la navigazione che punta a essa non sia null
, l'applicazione deve essere modificata per gestire casi in cui la navigazione è null
. Se questo non è possibile, è consigliabile aggiungere una proprietà obbligatoria al tipo di entità o assegnare un valore non null
ad almeno una proprietà.
Tutte le entità che condividono una tabella con una colonna di token di concorrenza devono eseguirne il mapping a una proprietà
Comportamento precedente
Si consideri il modello seguente:
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public byte[] Version { get; set; }
public OrderDetails Details { get; set; }
}
public class OrderDetails
{
public int Id { get; set; }
public string ShippingAddress { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.Property(o => o.Version).IsRowVersion().HasColumnName("Version");
}
Prima di EF Core 3.0, se OrderDetails
è di proprietà di Order
o è mappato in modo esplicito alla stessa tabella, il solo aggiornamento di OrderDetails
non aggiornerà il valore Version
nel client e l'aggiornamento successivo avrà esito negativo.
Nuovo comportamento
A partire dalla versione 3.0, EF Core propaga il nuovo valore Version
a Order
se è proprietario di OrderDetails
. In caso contrario, viene generata un'eccezione durante la convalida del modello.
Perché
Questa modifica è stata apportata per evitare un valore del token di concorrenza non aggiornato quando viene aggiornata solo una delle entità mappate alla stessa tabella.
Soluzioni di prevenzione
Tutte le entità che condividono la tabella devono includere una proprietà mappata alla colonna del token di concorrenza. È possibile crearne una in stato shadow:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OrderDetails>()
.Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");
}
Non è possibile eseguire query sulle entità di proprietà senza il proprietario usando una query di rilevamento
Problema di rilevamento n. 18876
Comportamento precedente
Prima di EF Core 3.0, le entità di proprietà potrebbero essere sottoposte a query come qualsiasi altra navigazione.
context.People.Select(p => p.Address);
Nuovo comportamento
A partire dalla versione 3.0, EF Core genererà se una query di rilevamento proietta un'entità di proprietà senza il proprietario.
Perché
Le entità di proprietà non possono essere manipolate senza il proprietario, quindi nella maggior parte dei casi le query sono in questo modo un errore.
Soluzioni di prevenzione
Se l'entità di proprietà deve essere rilevata per essere modificata in un secondo momento, il proprietario deve essere incluso nella query.
In caso contrario, aggiungere una AsNoTracking()
chiamata:
context.People.Select(p => p.Address).AsNoTracking();
Per le proprietà ereditate da tipi senza mapping viene ora eseguito il mapping a una singola colonna per tutti i tipi derivati
Comportamento precedente
Si consideri il modello seguente:
public abstract class EntityBase
{
public int Id { get; set; }
}
public abstract class OrderBase : EntityBase
{
public int ShippingAddress { get; set; }
}
public class BulkOrder : OrderBase
{
}
public class Order : OrderBase
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Ignore<OrderBase>();
modelBuilder.Entity<EntityBase>();
modelBuilder.Entity<BulkOrder>();
modelBuilder.Entity<Order>();
}
Prima di EF Core 3.0, per la proprietà ShippingAddress
sarebbe stato eseguito il mapping a colonne separate per BulkOrder
e Order
per impostazione predefinita.
Nuovo comportamento
A partire dalla versione 3.0, EF Core crea solo una colonna per ShippingAddress
.
Perché
Il comportamento precedente era imprevisto.
Soluzioni di prevenzione
È ancora possibile eseguire il mapping esplicito della proprietà a una colonna separata per i tipi derivati:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Ignore<OrderBase>();
modelBuilder.Entity<EntityBase>();
modelBuilder.Entity<BulkOrder>()
.Property(o => o.ShippingAddress).HasColumnName("BulkShippingAddress");
modelBuilder.Entity<Order>()
.Property(o => o.ShippingAddress).HasColumnName("ShippingAddress");
}
La convenzione di proprietà di chiave esterna non ha più lo stesso nome della proprietà dell'entità di sicurezza
Comportamento precedente
Si consideri il modello seguente:
public class Customer
{
public int CustomerId { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
}
Nelle versioni precedenti a EF Core 3.0 veniva usata la proprietà CustomerId
per la chiave esterna per convenzione.
Tuttavia, se Order
è un tipo di proprietà, CustomerId
sarebbe la chiave primaria e ciò non è in genere auspicabile.
Nuovo comportamento
A partire da 3.0, EF Core non tenta di usare le proprietà per le chiavi esterne per convenzione se hanno lo stesso nome della proprietà dell'entità di sicurezza. Viene ancora eseguita la corrispondenza tra i criteri del nome del tipo dell'entità di sicurezza concatenato al nome della proprietà dell'entità di sicurezza e il nome di navigazione concatenato al nome della proprietà dell'entità di sicurezza. Ad esempio:
public class Customer
{
public int Id { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
}
public class Customer
{
public int Id { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public int BuyerId { get; set; }
public Customer Buyer { get; set; }
}
Perché
Questa modifica è stata apportata per evitare una definizione errata della proprietà di chiave primaria nel tipo di proprietà.
Soluzioni di prevenzione
Se la proprietà è stata progettata per essere la chiave esterna e di conseguenza parte della chiave primaria, configurarla in modo esplicito come chiave esterna.
La connessione di database viene ora chiusa se non viene più usata prima del completamento di TransactionScope
Comportamento precedente
Prima di EF Core 3.0, se il contesto apre la connessione all'interno di un TransactionScope
, la connessione rimane aperta mentre è attivo il TransactionScope
corrente.
using (new TransactionScope())
{
using (AdventureWorks context = new AdventureWorks())
{
context.ProductCategories.Add(new ProductCategory());
context.SaveChanges();
// Old behavior: Connection is still open at this point
var categories = context.ProductCategories().ToList();
}
}
Nuovo comportamento
A partire dalla versione 3.0, EF Core chiude la connessione non appena non viene più usata.
Perché
Questa modifica consente di usare più contesti nello stesso TransactionScope
. Il nuovo comportamento corrisponde anche a EF6.
Soluzioni di prevenzione
Se la connessione deve rimanere aperta, la chiamata esplicita di OpenConnection()
garantirà che EF Core non la chiuda prematuramente:
using (new TransactionScope())
{
using (AdventureWorks context = new AdventureWorks())
{
context.Database.OpenConnection();
context.ProductCategories.Add(new ProductCategory());
context.SaveChanges();
var categories = context.ProductCategories().ToList();
context.Database.CloseConnection();
}
}
Ogni proprietà usa la generazione di chiavi di tipo intero in memoria indipendenti
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0 veniva usato un unico generatore di valori condiviso per tutte le proprietà di chiavi di tipo intero in memoria.
Nuovo comportamento
A partire da EF Core 3.0, ogni proprietà di chiave di tipo intero riceve un generatore di valori quando viene usato il database in memoria. Inoltre, se il database viene eliminato, la generazione di chiavi viene reimpostata per tutte le tabelle.
Perché
Questa modifica è stata apportata per allineare maggiormente la generazione di chiavi in memoria alla generazione di chiavi del database reale e per migliorare la possibilità di isolare i test l'uno dall'altro quando viene usato il database in memoria.
Soluzioni di prevenzione
Ciò può interrompere un'applicazione che si basa sull'impostazione di valori di chiave in memoria specifici. È consigliabile non basare l'applicazione su valori di chiave specifici o eseguire l'aggiornamento per passare al nuovo comportamento.
I campi sottostanti vengono usati per impostazione predefinita
Comportamento precedente
Nelle versioni precedenti alla versione 3.0, anche se il campo sottostante di una proprietà era noto, per impostazione predefinita EF Core eseguiva la lettura e la scrittura del valore della proprietà usando i metodi getter e setter della proprietà. L'eccezione era costituita dall'esecuzione di query in cui il campo sottostante, se noto, veniva impostato direttamente.
Nuovo comportamento
A partire da EF Core 3.0, se il campo sottostante di una proprietà è noto, la lettura e la scrittura della proprietà vengono sempre eseguite usando il campo sottostante. Ciò potrebbe causare un'interruzione dell'applicazione se l'applicazione si basa su un comportamento aggiuntivo codificato nei metodi getter o setter.
Perché
Questa modifica è stata apportata per impedire a EF Core di attivare per errore la logica di business per impostazione predefinita quando si eseguono operazioni di database che interessano le entità.
Soluzioni di prevenzione
È possibile ripristinare il comportamento delle versioni precedenti alla versione 3.0 tramite la configurazione della modalità di accesso delle proprietà in ModelBuilder
.
Ad esempio:
modelBuilder.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);
Viene generata un'eccezione se vengono trovati più campi sottostanti compatibili
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0, se più campi soddisfacevano le regole di ricerca del campo sottostante di una proprietà, veniva selezionato un solo campo in base a un ordine di precedenza. Ciò poteva causare l'uso di un campo non corretto nei casi ambigui.
Nuovo comportamento
A partire da EF Core 3.0, se più campi corrispondono alla stessa proprietà, viene generata un'eccezione.
Perché
Questa modifica è stata apportata per evitare di usare automaticamente un campo rispetto a un altro quando un solo campo può essere quello corretto.
Soluzioni di prevenzione
Per le proprietà con campi sottostanti ambigui, il campo da usare deve essere specificato in modo esplicito. Ad esempio, con l'API Fluent:
modelBuilder
.Entity<Blog>()
.Property(e => e.Id)
.HasField("_id");
I nomi delle proprietà solo campo devono corrispondere al nome di campo
Comportamento precedente
Prima di EF Core 3.0, una proprietà potrebbe essere specificata da un valore stringa e se non è stata trovata alcuna proprietà con tale nome nel tipo .NET, EF Core tenterebbe di associarlo a un campo usando le regole delle convenzioni.
private class Blog
{
private int _id;
public string Name { get; set; }
}
modelBuilder
.Entity<Blog>()
.Property("Id");
Nuovo comportamento
A partire da EF Core 3.0, una proprietà solo campo deve corrispondere esattamente al nome del campo.
modelBuilder
.Entity<Blog>()
.Property("_id");
Perché
Questa modifica è stata introdotta per evitare di usare lo stesso campo per due proprietà con nome simile. Le regole di corrispondenza per le proprietà solo campo sono state anche uniformate a quelle per le proprietà mappate a proprietà CLR.
Soluzioni di prevenzione
Le proprietà solo campo devono avere lo stesso nome del campo a cui vengono mappate. In una versione futura di EF Core dopo la 3.0, si prevede di riabilitare in modo esplicito la configurazione di un nome di campo diverso dal nome della proprietà (vedere il problema 15307):
modelBuilder
.Entity<Blog>()
.Property("Id")
.HasField("_id");
AddDbContext/AddDbContextPool non chiamano più AddLogging e AddMemoryCache
Comportamento precedente
Prima di EF Core 3.0, chiamare AddDbContext
o AddDbContextPool
registrare anche i servizi di registrazione e memorizzazione nella cache della memoria con l'inserimento delle dipendenze tramite chiamate a AddLogging e AddMemoryCache.
Nuovo comportamento
A partire da EF Core 3.0, AddDbContext
e AddDbContextPool
non registreranno più questi servizi con inserimento delle dipendenze.
Perché
EF Core 3.0 non richiede che questi servizi siano inclusi nel contenitore di inserimento delle dipendenze dell'applicazione. Tuttavia, se ILoggerFactory
è registrato nel contenitore di inserimento delle dipendenze dell'applicazione, verrà ancora usato da EF Core.
Soluzioni di prevenzione
Se l'applicazione necessita di questi servizi, registrarli in modo esplicito con il contenitore di inserimento delle dipendenze usando AddLogging o AddMemoryCache.
AddEntityFramework* aggiunge IMemoryCache con un limite di dimensioni
Problema di rilevamento n. 12905
Comportamento precedente
Prima di EF Core 3.0, anche la chiamata AddEntityFramework*
dei metodi registrava i servizi di memorizzazione nella cache della memoria con l'inserimento delle dipendenze senza un limite di dimensioni.
Nuovo comportamento
A partire da EF Core 3.0, AddEntityFramework*
registrerà un servizio IMemoryCache con un limite di dimensioni. Se altri servizi aggiunti in seguito dipendono da IMemoryCache, possono raggiungere rapidamente il limite predefinito causando eccezioni o prestazioni ridotte.
Perché
L'uso di IMemoryCache senza un limite potrebbe comportare un utilizzo non controllato della memoria se è presente un bug nella logica di memorizzazione nella cache delle query o le query vengono generate in modo dinamico. La presenza di un limite predefinito riduce un potenziale attacco DoS.
Soluzioni di prevenzione
Nella maggior parte dei casi la chiamata AddEntityFramework*
non è necessaria se AddDbContext
o AddDbContextPool
viene chiamato anche . Pertanto, la mitigazione migliore consiste nel rimuovere la AddEntityFramework*
chiamata.
Se l'applicazione necessita di questi servizi, registrare un'implementazione IMemoryCache in modo esplicito con il contenitore DI in anticipo usando AddMemoryCache.
DbContext.Entry esegue ora un DetectChanges locale
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0 la chiamata a DbContext.Entry
causava il rilevamento delle modifiche per tutte le entità rilevate.
Ciò garantiva l'aggiornamento dello stato esposto in EntityEntry
.
Nuovo comportamento
A partire da EF Core 3.0, la chiamata a DbContext.Entry
causa ora solo il tentativo di rilevare le modifiche nell'entità specificata e in tutte le relative entità di sicurezza rilevate.
Ciò significa che le modifiche apportate altrove potrebbero non essere state rilevate tramite la chiamata al metodo e ciò potrebbe avere implicazioni sullo stato dell'applicazione.
Si noti che se ChangeTracker.AutoDetectChangesEnabled
è impostato su false
, verrà disabilitato anche questo tipo di rilevamento delle modifiche locali.
Gli altri metodi che causano il rilevamento delle modifiche, ad esempio ChangeTracker.Entries
e SaveChanges
, causano ancora un DetectChanges
completo di tutte le entità rilevate.
Perché
Questa modifica è stata apportata per migliorare le prestazioni predefinite dell'uso di context.Entry
.
Soluzioni di prevenzione
Chiamare ChangeTracker.DetectChanges()
in modo esplicito prima di chiamare Entry
per garantire il comportamento precedente alla versione 3.0.
Le chiavi matrice di byte e di stringhe non vengono generate dal client per impostazione predefinita
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0 le proprietà di chiave string
e byte[]
potevano essere usate senza impostare in modo esplicito un valore non Null.
In questi casi, il valore di chiave veniva generato nel client come GUID, serializzato in byte per byte[]
.
Nuovo comportamento
A partire da EF Core 3.0 viene generata un'eccezione che indica che non è stato impostato alcun valore di chiave.
Perché
Questa modifica è stata apportata poiché i valori string
/byte[]
generati dal client non risultano in genere utili e il comportamento predefinito rendeva complesso ragionare sui valori di chiave generati in un modo comune.
Soluzioni di prevenzione
È possibile ripristinare il comportamento precedente alla versione 3.0 specificando in modo esplicito che le proprietà di chiave devono usare i valori generati se non viene impostato alcun altro valore non Null. Ad esempio, con l'API Fluent:
modelBuilder
.Entity<Blog>()
.Property(e => e.Id)
.ValueGeneratedOnAdd();
Oppure con annotazioni dei dati:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
ILoggerFactory è ora un servizio con ambito
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0 ILoggerFactory
veniva registrato come servizio singleton.
Nuovo comportamento
A partire da EF Core 3.0, ILoggerFactory
viene registrato come servizio con ambito.
Perché
Questa modifica è stata apportata per consentire l'associazione di un logger a un'istanza DbContext
che abilita altre funzionalità e rimuove alcuni casi di comportamento anomalo, ad esempio un'esplosione dei provider di servizi interni.
Soluzioni di prevenzione
Questa modifica non dovrebbe influire sul codice dell'applicazione a meno che non vengano registrati e usati servizi personalizzati nel provider di servizi interno di EF Core.
Questo non è un comportamento comune.
In questi casi la maggior parte delle operazioni continuano a essere eseguite correttamente, ma qualsiasi servizio singleton dipendente da ILoggerFactory
dovrà essere modificato per ottenere ILoggerFactory
in modo diverso.
Se si verificano situazioni simili, inviare una segnalazione nello strumento di gestione dei problemi in GitHub relativo a EF Core per comunicare in che modo viene usato ILoggerFactory
per consentirci di comprendere meglio come evitare ulteriori interruzioni in futuro.
I proxy di caricamento lazy non presuppongono più che le proprietà di navigazione vengano caricate completamente
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0, quando DbContext
veniva eliminato non esisteva alcun metodo per scoprire se una determinata proprietà di navigazione in un'entità ottenuta da un contesto specifico veniva caricata completamente o meno.
I proxy presupponevano invece che venisse caricata una navigazione di riferimento se era presente un valore non Null e che venisse caricata una navigazione di raccolta se era presente un valore.
In questi casi il tentativo di eseguire un caricamento lazy non avrebbe avuto alcun esito.
Nuovo comportamento
A partire da Entity Framework Core 3.0, i proxy tengono traccia del caricamento o mancato caricamento di una proprietà di navigazione. Ciò significa che il tentativo di accedere a una proprietà di navigazione caricata dopo l'eliminazione del contesto non avrà mai alcun esito, anche quando la navigazione caricata è vuota o ha valore Null. Al contrario, il tentativo di accedere a una proprietà di navigazione non caricata genera un'eccezione se il contesto viene eliminato anche se la proprietà di navigazione è una raccolta non vuota. Se si verifica questa situazione significa che il codice dell'applicazione sta tentando di usare il caricamento lazy in un momento non valido e l'applicazione deve essere modificata in modo da non eseguire questa operazione.
Perché
Questa modifica è stata apportata per rendere coerente e corretto il comportamento durante un tentativo di caricamento lazy in un'istanza DbContext
eliminata.
Soluzioni di prevenzione
Aggiornare il codice dell'applicazione per fare in modo che non venga tentato il caricamento lazy con un contesto eliminato oppure specificare una configurazione in modo che non venga eseguita alcuna operazione come descritto nel messaggio di eccezione.
La creazione di un numero eccessivo di provider di servizi interni è ora un errore per impostazione predefinita
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0 veniva registrato un avviso per le applicazioni che creavano un numero eccessivo di provider di servizi interni.
Nuovo comportamento
A partire da EF Core 3.0, l'avviso viene considerato un errore e viene generata un'eccezione.
Perché
Questa modifica è stata apportata per gestire meglio il codice dell'applicazione tramite un'esposizione più esplicita di questa situazione di errore.
Soluzioni di prevenzione
L'azione più appropriata quando si verifica questo errore consiste nell'individuare la causa radice e nell'interrompere la creazione di numerosi provider di servizi interni.
È possibile tuttavia convertire nuovamente l'errore in avviso o ignorarlo tramite una configurazione in DbContextOptionsBuilder
.
Ad esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.ConfigureWarnings(w => w.Log(CoreEventId.ManyServiceProvidersCreatedWarning));
}
Nuovo comportamento per la chiamata di HasOne/HasMany con una singola stringa
Comportamento precedente
Prima di EF Core 3.0, il codice che chiama HasOne
o HasMany
con una singola stringa era interpretato in modo poco chiaro.
Ad esempio:
modelBuilder.Entity<Samurai>().HasOne("Entrance").WithOne();
Apparentemente, il codice mette in relazione Samurai
con un altro tipo di entità tramite la proprietà di navigazione Entrance
, che può essere privata.
In realtà, il codice tenta di creare una relazione con un tipo di entità denominato Entrance
senza proprietà di navigazione.
Nuovo comportamento
A partire da EF Core 3.0, il codice sopra riportato ora esegue quello che avrebbe dovuto fare in precedenza.
Perché
Il comportamento precedente era molto poco chiaro, soprattutto durante la lettura del codice di configurazione e la ricerca di errori.
Soluzioni di prevenzione
Questa modifica causerà problemi solo nelle applicazioni che configurano relazioni in modo esplicito usando stringhe per i nomi dei tipi e senza specificare in modo esplicito la proprietà di navigazione.
Non è uno scenario comune.
Il comportamento precedente può essere ottenuto passando esplicitamente null
per il nome della proprietà di navigazione.
Ad esempio:
modelBuilder.Entity<Samurai>().HasOne("Some.Entity.Type.Name", null).WithOne();
Il tipo restituito per diversi metodi asincroni è cambiato da Task a ValueTask
Comportamento precedente
I metodi asincroni seguenti in precedenza restituivano il tipo Task<T>
:
DbContext.FindAsync()
DbSet.FindAsync()
DbContext.AddAsync()
DbSet.AddAsync()
ValueGenerator.NextValueAsync()
(e classi derivate)
Nuovo comportamento
I metodi indicati in precedenza ora restituiscono il tipo ValueTask<T>
sullo stesso T
come in precedenza.
Perché
Questa modifica riduce il numero delle allocazioni di heap sostenute quando si richiamano questi metodi, con un miglioramento generale delle prestazioni.
Soluzioni di prevenzione
Le applicazioni semplicemente in attesa delle API precedenti devono solo essere ricompilate e non sono richieste modifiche del codice sorgente.
Per scenari di utilizzo più complessi (ad esempio, il passaggio del tipo Task
restituito a Task.WhenAny()
) è richiesto in genere che il tipo ValueTask<T>
restituito venga convertito in Task<T>
chiamando AsTask()
su di esso.
Si noti che in questo modo si annulla la riduzione delle allocazioni consentita da questa modifica.
L'annotazione Relational:TypeMapping è ora TypeMapping
Comportamento precedente
Il nome di annotazione delle annotazioni di mapping del tipo era "Relational:TypeMapping".
Nuovo comportamento
Il nome di annotazione delle annotazioni di mapping del tipo è ora "TypeMapping".
Perché
Il mapping dei tipi non viene più usato solo per i provider di database relazionali.
Soluzioni di prevenzione
Ciò causa un'interruzione solo nelle applicazioni che accedono al mapping dei tipi direttamente come annotazione. Questa situazione non è comune. L'azione più appropriata per risolvere il problema consiste nell'usare la superficie dell'API per accedere al mapping dei tipi anziché l'annotazione.
ToTable in un tipo derivato genera un'eccezione
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0, la chiamata a ToTable()
in un tipo derivato veniva ignorata poiché soltanto la strategia di mapping dell'ereditarietà era una tabella per gerarchia dove la chiamata non era valida.
Nuovo comportamento
A partire da EF Core 3.0 e in preparazione all'aggiunta del supporto per la tabella per tipo e per TPC in una versione successiva, la chiamata a ToTable()
in un tipo derivato genera un'eccezione per evitare una modifica del mapping imprevista in futuro.
Perché
Attualmente il mapping di un tipo derivato in una tabella diversa non è un'operazione valida. Questa modifica consente di evitare interruzioni future quando l'operazione diventerà un'operazione valida.
Soluzioni di prevenzione
Rimuovere qualsiasi tentativo di mapping di tipi derivati in altre tabelle.
ForSqlServerHasIndex sostituito con HasIndex
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0, ForSqlServerHasIndex().ForSqlServerInclude()
offriva un metodo per configurare le colonne usate con INCLUDE
.
Nuovo comportamento
A partire da EF Core 3.0, l'uso di Include
in un indice è supportato a livello relazionale.
Usare HasIndex().ForSqlServerInclude()
.
Perché
Questa modifica è stata apportata per consolidare l'API per gli indici con Include
in un'unica posizione per tutti i provider di database.
Soluzioni di prevenzione
Usare la nuova API, come illustrato in precedenza.
Modifiche dell'API dei metadati
Nuovo comportamento
Le proprietà seguenti sono state convertite in metodi di estensione:
IEntityType.QueryFilter
->GetQueryFilter()
IEntityType.DefiningQuery
->GetDefiningQuery()
IProperty.IsShadowProperty
->IsShadowProperty()
IProperty.BeforeSaveBehavior
->GetBeforeSaveBehavior()
IProperty.AfterSaveBehavior
->GetAfterSaveBehavior()
Perché
Questa modifica semplifica l'implementazione delle interfacce menzionate in precedenza.
Soluzioni di prevenzione
Usare i nuovi metodi di estensione.
Modifiche dell'API dei metadati specifiche del provider
Nuovo comportamento
I metodi di estensione specifici del provider verranno resi flat:
IProperty.Relational().ColumnName
->IProperty.GetColumnName()
IEntityType.SqlServer().IsMemoryOptimized
->IEntityType.IsMemoryOptimized()
PropertyBuilder.UseSqlServerIdentityColumn()
->PropertyBuilder.UseIdentityColumn()
Perché
Questa modifica semplifica l'implementazione dei metodi di estensione menzionati in precedenza.
Soluzioni di prevenzione
Usare i nuovi metodi di estensione.
EF Core non invia più pragma per l'imposizione della chiave esterna di SQLite
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0, EF Core inviava PRAGMA foreign_keys = 1
quando veniva aperta una connessione a SQLite.
Nuovo comportamento
A partire da EF Core 3.0, EF Core non invia più PRAGMA foreign_keys = 1
quando viene aperta una connessione a SQLite.
Perché
Questa modifica è stata apportata poiché EF Core usa SQLitePCLRaw.bundle_e_sqlite3
per impostazione predefinita. Ciò significa che l'imposizione della chiave esterna è abilitata per impostazione predefinita e non deve essere abilitata in modo esplicito ogni volta che viene aperta una connessione.
Soluzioni di prevenzione
Le chiavi esterne sono abilitate per impostazione predefinita in SQLitePCLRaw.bundle_e_sqlite3, usato per impostazione predefinita per EF Core.
Per gli altri casi, è possibile abilitare le chiavi esterne specificando Foreign Keys=True
nella stringa di connessione.
Microsoft.EntityFrameworkCore.Sqlite dipende ora da SQLitePCLRaw.bundle_e_sqlite3
Comportamento precedente
Nelle versioni precedenti a EF Core 3.0, EF Core usava SQLitePCLRaw.bundle_green
.
Nuovo comportamento
A partire da EF Core 3.0, EF Core usa SQLitePCLRaw.bundle_e_sqlite3
.
Perché
Questa modifica è stata apportata per rendere coerente la versione di SQLite usata in iOS con le altre piattaforme.
Soluzioni di prevenzione
Per usare la versione di SQLite nativa in iOS, configurare Microsoft.Data.Sqlite
per l'uso di un'aggregazione SQLitePCLRaw
diversa.
I valori Guid vengono ora archiviati come TEXT in SQLite
Comportamento precedente
I valori Guid in precedenza venivano archiviati come valori BLOB in SQLite.
Nuovo comportamento
I valori Guid vengono ora archiviati come TEXT.
Perché
Il formato binario dei valori Guid non è standardizzato. L'archiviazione dei valori come TEXT rende il database più compatibile con altre tecnologie.
Soluzioni di prevenzione
È possibile eseguire la migrazione dei database esistenti al nuovo formato eseguendo SQL nel modo seguente.
UPDATE MyTable
SET GuidColumn = hex(substr(GuidColumn, 4, 1)) ||
hex(substr(GuidColumn, 3, 1)) ||
hex(substr(GuidColumn, 2, 1)) ||
hex(substr(GuidColumn, 1, 1)) || '-' ||
hex(substr(GuidColumn, 6, 1)) ||
hex(substr(GuidColumn, 5, 1)) || '-' ||
hex(substr(GuidColumn, 8, 1)) ||
hex(substr(GuidColumn, 7, 1)) || '-' ||
hex(substr(GuidColumn, 9, 2)) || '-' ||
hex(substr(GuidColumn, 11, 6))
WHERE typeof(GuidColumn) == 'blob';
In EF Core è anche possibile continuare a usare il comportamento precedente configurando un convertitore di valori per queste proprietà.
modelBuilder
.Entity<MyEntity>()
.Property(e => e.GuidProperty)
.HasConversion(
g => g.ToByteArray(),
b => new Guid(b));
Microsoft.Data.Sqlite rimane in grado di leggere i valori Guid sia da colonne BLOB che TEXT. Tuttavia, poiché è stato modificato il formato predefinito per i parametri e le costanti, probabilmente sarà necessario intervenire per la maggior parte degli scenari che coinvolgono valori Guid.
I valori char vengono ora archiviati come testo in SQLite
Comportamento precedente
I valori char sono stati precedentemente archiviati come valori INTEGER in SQLite. Un valore char di A veniva ad esempio archiviato come valore intero 65.
Nuovo comportamento
I valori char vengono ora archiviati come testo.
Perché
L'archiviazione dei valori come testo è un'operazione più naturale e rende il database più compatibile con altre tecnologie.
Soluzioni di prevenzione
È possibile eseguire la migrazione dei database esistenti al nuovo formato eseguendo SQL nel modo seguente.
UPDATE MyTable
SET CharColumn = char(CharColumn)
WHERE typeof(CharColumn) = 'integer';
In EF Core è anche possibile continuare a usare il comportamento precedente configurando un convertitore di valori per queste proprietà.
modelBuilder
.Entity<MyEntity>()
.Property(e => e.CharProperty)
.HasConversion(
c => (long)c,
i => (char)i);
Microsoft.Data.Sqlite rimane comunque in grado di leggere i valori di caratteri presenti sia nelle colonne di valori interi sia in quelle di testo, quindi alcuni scenari potrebbero non richiedere alcuna azione.
Gli ID di migrazione vengono ora generati con il calendario delle impostazioni cultura inglese non dipendenti da paese/area geografica
Comportamento precedente
Gli ID di migrazione venivano inavvertitamente generati usando il calendario delle impostazioni cultura correnti.
Nuovo comportamento
Gli ID di migrazione ora vengono sempre generati con il calendario delle impostazioni cultura inglese non dipendenti da paese/area geografica (calendario gregoriano).
Perché
L'ordine delle migrazioni è importante quando si esegue l'aggiornamento del database o si risolvono i conflitti di unione. L'uso del calendario delle impostazioni cultura inglese non dipendenti da paese/area geografica evita i problemi che possono verificarsi quando i membri del team hanno calendari di sistema diversi.
Soluzioni di prevenzione
Questa modifica interessa gli utenti che usano un calendario non gregoriano in cui l'anno ha un'estensione superiore al calendario gregoriano (come il calendario buddista tailandese). Gli ID di migrazione esistenti dovranno essere aggiornati in modo che le nuove migrazioni vengano collocate dopo le migrazioni esistenti.
L'ID di migrazione è disponibile nell'attributo di migrazione presente nei file di progettazione delle migrazioni.
[DbContext(typeof(MyDbContext))]
-[Migration("25620318122820_MyMigration")]
+[Migration("20190318122820_MyMigration")]
partial class MyMigration
{
È necessario aggiornare anche la tabella della cronologia delle migrazioni.
UPDATE __EFMigrationsHistory
SET MigrationId = CONCAT(LEFT(MigrationId, 4) - 543, SUBSTRING(MigrationId, 4, 150))
Il metodo UseRowNumberForPaging è stato rimosso
Comportamento precedente
Prima di EF Core 3.0 si poteva usare UseRowNumberForPaging
per generare codice SQL per la suddivisione in pagine compatibile con SQL Server 2008.
Nuovo comportamento
A partire da EF Core 3.0, EF genererà solo codice SQL per la suddivisione in pagine compatibile solo con versioni successive di SQL Server.
Perché
Questa modifica è stata apportata perché SQL Server 2008 non è più un prodotto supportato e l'aggiornamento di questa funzionalità per interagire con le modifiche apportate per le query in EF Core 3.0 è un lavoro significativo.
Soluzioni di prevenzione
È consigliabile eseguire l'aggiornamento a una versione più recente di SQL Server o usare un livello di compatibilità superiore, in modo che il codice SQL generato sia supportato. Detto questo, se non è possibile procedere in questo modo, aggiungere un commento per il problema con indicazioni dettagliate. Microsoft potrebbe rivedere questa decisione in base ai commenti e suggerimenti.
Info/metadati dell'estensione rimossi da IDbContextOptionsExtension
Comportamento precedente
IDbContextOptionsExtension
conteneva metodi per fornire i metadati relativi all'estensione.
Nuovo comportamento
Questi metodi sono stati spostati in una nuova classe di base astratta DbContextOptionsExtensionInfo
, restituita da una nuova proprietà IDbContextOptionsExtension.Info
.
Perché
Nelle versioni dalla 2.0 alla 3.0 è stato necessario aggiungere o modificare questi metodi più volte. Suddividendoli in una nuova classe di base astratta sarà più facile apportare questo tipo di modifiche senza compromettere il funzionamento delle estensioni esistenti.
Soluzioni di prevenzione
Aggiornare le estensioni per seguire il nuovo modello.
Sono disponibili esempi nelle numerose implementazioni di IDbContextOptionsExtension
per diversi tipi di estensioni nel codice sorgente di EF Core.
LogQueryPossibleExceptionWithAggregateOperator è stato rinominato
Modifica
RelationalEventId.LogQueryPossibleExceptionWithAggregateOperator
è stato rinominato in RelationalEventId.LogQueryPossibleExceptionWithAggregateOperatorWarning
.
Perché
Allineamento del nome di questo evento di avviso con tutti gli altri eventi di avviso.
Soluzioni di prevenzione
Usare il nuovo nome. (Si noti che il numero di ID evento non è stato modificato.)
Chiarimenti per l'API per i nomi di vincolo di chiave esterna
Comportamento precedente
Prima di EF Core 3.0, si faceva riferimento ai nomi di vincolo di chiave esterna semplicemente con "Name". Ad esempio:
var constraintName = myForeignKey.Name;
Nuovo comportamento
A partire da EF Core 3.0, si fa ora riferimento ai nomi di vincolo di chiave esterna con "ConstraintName". Ad esempio:
var constraintName = myForeignKey.ConstraintName;
Perché
Questa modifica introduce coerenza per la denominazione in quest'area e chiarisce anche che si tratta del nome del vincolo di chiave esterna e non del nome della colonna o della proprietà per cui è definita la chiave esterna.
Soluzioni di prevenzione
Usare il nuovo nome.
IRelationalDatabaseCreator.HasTables/HasTablesAsync sono diventati pubblici
Comportamento precedente
Prima di EF Core 3.0 questi metodi erano protetti.
Nuovo comportamento
A partire da EF Core 3.0 questi metodi sono pubblici.
Perché
Questi metodi vengono usati da EF per determinare se un database viene creato, ma vuoto. Ciò risulta utile all'esterno di EF quando occorre determinare se applicare o meno le migrazioni.
Soluzioni di prevenzione
Modificare l'accessibilità di eventuali override.
Microsoft.EntityFrameworkCore.Design è ora un pacchetto DevelopmentDependency
Comportamento precedente
Prima di EF Core 3.0, Microsoft.EntityFrameworkCore.Design era un pacchetto NuGet normale ed era possibile fare riferimento al relativo assembly dai progetti dipendenti.
Nuovo comportamento
A partire da EF Core 3.0 è un pacchetto DevelopmentDependency. Ciò significa che la dipendenza non verrà propagata in modo transitivo in altri progetti e che non è più possibile, per impostazione predefinita, fare riferimento al relativo assembly.
Perché
Questo pacchetto è destinato solo all'uso in fase di progettazione. Le applicazioni distribuite non devono farvi riferimento. Questa raccomandazione è rafforzata dall'impostazione del pacchetto come DevelopmentDependency.
Soluzioni di prevenzione
Se è necessario fare riferimento a questo pacchetto per eseguire l'override del comportamento della fase di progettazione di EF Core, è possibile aggiornare i metadati dell'elemento PackageReference nel progetto.
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.0">
<PrivateAssets>all</PrivateAssets>
<!-- Remove IncludeAssets to allow compiling against the assembly -->
<!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
</PackageReference>
In presenza di riferimenti transitivi al pacchetto tramite Microsoft.EntityFrameworkCore.Tools, sarà necessario aggiungere un PackageReference esplicito al pacchetto per modificare i relativi metadati. Tale riferimento esplicito deve essere aggiunto a qualsiasi progetto in cui sono necessari i tipi del pacchetto.
Aggiornamento di SQLitePCL.raw alla versione 2.0.0
Comportamento precedente
Microsoft.EntityFrameworkCore.Sqlite dipendeva in precedenza dalla versione 1.1.12 di SQLitePCL.raw.
Nuovo comportamento
Il pacchetto è stato aggiornato a seconda della versione 2.0.0.
Perché
La versione 2.0.0 di SQLitePCL.raw è destinata a .NET Standard 2.0. Era in precedenza destinata a .NET Standard 1.1 e ciò richiedeva un notevole impegno di chiusura di pacchetti transitivi per il funzionamento.
Soluzioni di prevenzione
SQLitePCL.raw versione 2.0.0 include alcune modifiche che causano un'interruzione. Per informazioni dettagliate, vedere le note sulla versione.
NetTopologySuite aggiornato alla versione 2.0.0
Comportamento precedente
I pacchetti spaziali dipendevano in precedenza dalla versione 1.15.1 di NetTopologySuite.
Nuovo comportamento
Il pacchetto è stato aggiornato in modo da dipendere dalla versione 2.0.0.
Perché
La versione 2.0.0 di NetTopologySuite risolve vari problemi di usabilità riscontrati dagli utenti di EF Core.
Soluzioni di prevenzione
NetTopologySuite versione 2.0.0 include alcune modifiche che causano un'interruzione. Per informazioni dettagliate, vedere le note sulla versione.
Microsoft.Data.SqlClient viene usato invece di System.Data.SqlClient
Problema di rilevamento n. 15636
Comportamento precedente
Microsoft.EntityFrameworkCore.SqlServer in precedenza dipendeva da System.Data.SqlClient.
Nuovo comportamento
Il pacchetto è stato aggiornato in modo da dipendere da Microsoft.Data.SqlClient.
Perché
Microsoft.Data.SqlClient è il driver di accesso ai dati principale per SQL Server in futuro e System.Data.SqlClient non è più l'obiettivo dello sviluppo. Alcune funzionalità importanti, ad esempio Always Encrypted, sono disponibili solo in Microsoft.Data.SqlClient.
Soluzioni di prevenzione
Se il codice accetta una dipendenza diretta da System.Data.SqlClient, è necessario modificarlo in modo da fare riferimento a Microsoft.Data.SqlClient; poiché i due pacchetti mantengono un livello molto elevato di compatibilità delle API, questo dovrebbe essere solo un semplice pacchetto e una modifica dello spazio dei nomi.
Devono essere configurare più relazioni ambigue che fanno riferimento a se stesse
Comportamento precedente
Un tipo di entità con più proprietà di navigazione unidirezionale che fanno riferimento a se stesse e più chiavi esterne corrispondenti è stato erroneamente configurato come relazione singola. Ad esempio:
public class User
{
public Guid Id { get; set; }
public User CreatedBy { get; set; }
public User UpdatedBy { get; set; }
public Guid CreatedById { get; set; }
public Guid? UpdatedById { get; set; }
}
Nuovo comportamento
Questo scenario viene ora rilevato nella compilazione del modello e viene generata un'eccezione indicante che il modello è ambiguo.
Perché
Il modello risultante era ambiguo e sarà probabilmente errato in questo caso.
Soluzioni di prevenzione
Usare la configurazione completa della relazione. Ad esempio:
modelBuilder
.Entity<User>()
.HasOne(e => e.CreatedBy)
.WithMany();
modelBuilder
.Entity<User>()
.HasOne(e => e.UpdatedBy)
.WithMany();
DbFunction.Schema è null o una stringa vuota la configura nello schema predefinito del modello
Problema di rilevamento n. 12757
Comportamento precedente
Una funzione DbFunction configurata con lo schema come stringa vuota è stata considerata come funzione predefinita senza uno schema. Ad esempio, il codice seguente esegue il mapping DatePart
della funzione CLR alla DATEPART
funzione predefinita in SqlServer.
[DbFunction("DATEPART", Schema = "")]
public static int? DatePart(string datePartArg, DateTime? date) => throw new Exception();
Nuovo comportamento
Tutti i mapping DbFunction vengono considerati mappati alle funzioni definite dall'utente. Di conseguenza, il valore stringa vuoto inserisce la funzione all'interno dello schema predefinito per il modello. Che potrebbe essere lo schema configurato in modo esplicito tramite l'API modelBuilder.HasDefaultSchema()
Fluent o in dbo
caso contrario.
Perché
In precedenza lo schema vuoto era un modo per trattare tale funzione è predefinito, ma tale logica è applicabile solo per SqlServer in cui le funzioni predefinite non appartengono ad alcuno schema.
Soluzioni di prevenzione
Configurare manualmente la traduzione di DbFunction per eseguirne il mapping a una funzione predefinita.
modelBuilder
.HasDbFunction(typeof(MyContext).GetMethod(nameof(MyContext.DatePart)))
.HasTranslation(args => SqlFunctionExpression.Create("DatePart", args, typeof(int?), null));
EF Core 3.0 è destinato a .NET Standard 2.1 anziché a .NET Standard 2.0 ripristinato
EF Core 3.0 è destinato a .NET Standard 2.1, che è una modifica di rilievo che esclude le applicazioni .NET Framework. EF Core 3.1 ha ripristinato questa funzionalità e ha come destinazione di nuovo .NET Standard 2.0.
L'esecuzione di query viene registrata a livello di debug - Modifica annullata
Questa modifica è stata annullata perché la nuova configurazione in EF Core 3.0 consente all'applicazione di specificare il livello di log per qualsiasi evento. Ad esempio, per impostare la registrazione di SQL sul livello Debug
, configurare il livello in modo esplicito in OnConfiguring
o AddDbContext
:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(connectionString)
.ConfigureWarnings(c => c.Log((RelationalEventId.CommandExecuting, LogLevel.Debug)));