Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
I tipi di riferimento nullable (NRT) di C# consentono di annotare i tipi di riferimento, indicando se è valido per loro contenere null o meno. Se non si ha familiarità con questa funzionalità, è consigliabile acquisire familiarità con questa funzionalità leggendo la documentazione di C#. I tipi riferimento nullable sono abilitati per impostazione predefinita nei nuovi modelli di progetto, ma rimangono disabilitati nei progetti esistenti, a meno che non venga esplicitamente scelto.
Questa pagina presenta il supporto di EF Core per i tipi riferimento nullable e descrive le procedure consigliate per usarle.
Proprietà obbligatorie e facoltative
La pagina Proprietà obbligatorie e facoltative è la documentazione principale sulle proprietà obbligatorie e facoltative e la loro interazione con i tipi di riferimento nullable. È consigliabile iniziare leggendo prima la pagina.
Nota
Prestare attenzione quando si abilitano i tipi riferimento nullable in un progetto esistente: le proprietà del tipo di riferimento configurate in precedenza come facoltative verranno ora configurate come obbligatorie, a meno che non siano annotate in modo esplicito come nullable. Quando si gestisce uno schema di database relazionale, è possibile che vengano generate migrazioni che modificano la possibilità di avere valori nulli nella colonna del database.
Proprietà non annullabili e inizializzazione
Quando i tipi di riferimento nullable sono abilitati, il compilatore C# genera avvisi per qualsiasi proprietà non nullable non inizializzate, poiché queste conterrebbero null. Di conseguenza, non è possibile usare il metodo comune di scrittura dei tipi di entità:
public class Customer
{
public int Id { get; set; }
// Generates CS8618, uninitialized non-nullable property:
public string Name { get; set; }
}
Se si usa C# 11 o versione successiva, i membri necessari forniscono la soluzione perfetta a questo problema:
public required string Name { get; set; }
Il compilatore garantisce ora che quando il codice crea un'istanza di customer, inizializza sempre la relativa proprietà Name. Poiché la colonna di database mappata alla proprietà è non annullabile, tutte le istanze caricate da EF contengono sempre un Nome non nullo.
Se si utilizza una versione meno recente di C#, il binding del costruttore è una tecnica alternativa per assicurarsi che le proprietà non annullabili siano inizializzate:
public class CustomerWithConstructorBinding
{
public int Id { get; set; }
public string Name { get; set; }
public CustomerWithConstructorBinding(string name)
{
Name = name;
}
}
Sfortunatamente, in alcuni scenari l'associazione di costruttori non è un'opzione; le proprietà di navigazione, ad esempio, non possono essere inizializzate in questo modo. In questi casi, è sufficiente inizializzare la proprietà su null con l'aiuto dell'operatore null-forgiving (ma vedere di seguito per altri dettagli):
public Product Product { get; set; } = null!;
Proprietà di navigazione necessarie
Le proprietà di navigazione obbligatorie presentano difficoltà aggiuntive: anche se un'entità dipendente esisterà sempre per un determinato elemento principale, potrebbe essere o meno caricata da una particolare query, a seconda delle esigenze in quel punto del programma (vedere i diversi modelli per il caricamento dei dati). Allo stesso tempo, potrebbe essere indesiderato rendere annullabili queste proprietà, poiché ciò forza l'accesso ad esse per verificare la presenza di null, anche quando la navigazione è nota essere carica e pertanto non può essere null.
Questo non è necessariamente un problema! Se una dipendenza richiesta viene caricata correttamente (ad esempio tramite Include), è garantito che l'accesso alla sua proprietà di navigazione restituisca sempre un valore diverso da Null. D'altra parte, l'applicazione può scegliere di verificare se la relazione è caricata controllando se la navigazione è null. In questi casi, è ragionevole rendere nullable la navigazione. Ciò significa che sono necessari spostamenti dall'oggetto dipendente al principale.
- Deve essere non annullabile se è considerato un errore del programmatore accedere a una navigazione quando non è caricata.
- Deve essere nullable se è accettabile che il codice dell'applicazione controlli la navigazione per determinare se la relazione viene caricata o meno.
Se si vuole un approccio più rigoroso, è possibile avere una proprietà non nullable con un campo sottostante nullable:
private Address? _shippingAddress;
public Address ShippingAddress
{
set => _shippingAddress = value;
get => _shippingAddress
?? throw new InvalidOperationException("Uninitialized property: " + nameof(ShippingAddress));
}
Finché la navigazione è correttamente caricata, la dipendenza sarà accessibile tramite la proprietà. Se, tuttavia, si accede alla proprietà senza prima caricare correttamente l'entità correlata, viene generata un'eccezione InvalidOperationException perché il contratto API è stato usato in modo non corretto.
Nota
Le navigazioni della raccolta, che contengono riferimenti a più entità correlate, devono essere sempre non annullabili. Una raccolta vuota significa che non esistono entità correlate, ma l'elenco stesso non deve mai essere null.
DbContext e DbSet
Con EF, è pratica comune avere proprietà DbSet non inizializzate sui tipi di contesto:
public class MyContext : DbContext
{
public DbSet<Customer> Customers { get; set;}
}
Anche se questo genera in genere un avviso del compilatore, EF Core 7.0 e versioni successive eliminano questo avviso, poiché EF inizializza automaticamente queste proprietà tramite reflection.
Nella versione precedente di EF Core è possibile risolvere questo problema nel modo seguente:
public class MyContext : DbContext
{
public DbSet<Customer> Customers => Set<Customer>();
}
Un'altra strategia consiste nell'usare proprietà automatiche non annullabili, ma per inizializzarle con null, usando l'operatore null-forgiving (!) per sopprimere l'avviso del compilatore. Il costruttore di base DbContext garantisce che tutte le proprietà DbSet vengano inizializzate e null non verranno mai osservate su di esse.
Esplorazione e inclusione di relazioni annullabili
Quando si gestiscono relazioni facoltative, è possibile riscontrare avvisi del compilatore in cui un'eccezione di riferimento effettiva null sarebbe impossibile. Durante la conversione e l'esecuzione delle query LINQ, EF Core garantisce che, se non esiste un'entità correlata facoltativa, qualsiasi navigazione verrà semplicemente ignorata, anziché generare un'eccezione. Tuttavia, il compilatore non è a conoscenza di questa garanzia di EF Core e genera avvisi come se la query LINQ fosse stata eseguita in memoria, con LINQ to Objects. Di conseguenza, è necessario usare l'operatore null-forgiving (!) per informare il compilatore che un valore effettivo null non è possibile:
var order = await context.Orders
.Where(o => o.OptionalInfo!.SomeProperty == "foo")
.ToListAsync();
Si verifica un problema simile quando si includono più livelli di relazioni tra gli spostamenti facoltativi:
var order = await context.Orders
.Include(o => o.OptionalInfo!)
.ThenInclude(op => op.ExtraAdditionalInfo)
.SingleAsync();
Se esegui spesso questa operazione e i tipi di entità in questione sono prevalentemente (o esclusivamente) usati nelle query di EF Core, potresti considerare di rendere le proprietà di navigazione non annullabili e di configurarle come facoltative usando l'API Fluent o le Data Annotations. In questo modo verranno rimossi tutti gli avvisi del compilatore mantenendo la relazione opzionale; tuttavia, se le entità vengono attraversate all'esterno di EF Core, è possibile osservare i valori null anche se le proprietà sono annotate come non annullabili.
Limitazioni nelle versioni precedenti
Prima di EF Core 6.0, sono state applicate le limitazioni seguenti:
- La superficie di API pubblica non è stata annotata per la gestione dei valori null (l'API pubblica era ignorante ai valori null), rendendo talvolta l'uso scomodo quando la funzionalità NRT è attivata. Ciò include in particolare gli operatori LINQ asincroni esposti da EF Core, ad esempio FirstOrDefaultAsync. L'API pubblica è completamente annotata per la nullabilità a partire da EF Core 6.0.
- Il reverse engineering non supportava i tipi di riferimento nullable C# 8 (NRT): EF Core generava sempre codice C# che presupponeva che la funzionalità fosse disattivata. Ad esempio, le colonne di testo che ammettono i valori Null sono state sottoposte a scaffolding come proprietà con tipo
string, nonstring?, con l'API Fluent o le Data Annotations usate per configurare se una proprietà è necessaria o meno. Se si utilizza una versione precedente di EF Core, è comunque possibile modificare il codice generato automaticamente e sostituire tali elementi con le annotazioni di nullabilità di C#.