EF code first: dovrei inizializzare le proprietà di navigazione?

Avevo visto alcuni libri (ad es. Programmare il codice del framework di quadro prima Julia Lerman ) che definiscono le loro classi di dominio (POCO) senza inizializzazione delle proprietà di navigazione come:

public class User { public int Id { get; set; } public string UserName { get; set; } public virtual ICollection
Address { get; set; } public virtual License License { get; set; } }

alcuni altri libri o strumenti (es. Entity Framework Power Tools ) quando genera POCO inizializza le proprietà di navigazione della class, come:

 public class User { public User() { this.Addresses = new IList
(); this.License = new License(); } public int Id { get; set; } public string UserName { get; set; } public virtual ICollection
Addresses { get; set; } public virtual License License { get; set; } }

Q1: qual è il migliore? perché? Pro e contro?

Modificare:

 public class License { public License() { this.User = new User(); } public int Id { get; set; } public string Key { get; set; } public DateTime Expirtion { get; set; } public virtual User User { get; set; } } 

Q2: Nel secondo approccio ci sarebbe uno stack overflow se la class `License` ha anche un riferimento alla class` User`. Significa che dovremmo avere un riferimento unidirezionale. (?) Come dovremmo decidere quale delle proprietà di navigazione dovrebbe essere rimossa?

    Collezioni: non importa.

    C’è una netta differenza tra collezioni e riferimenti come proprietà di navigazione. Un riferimento è un’entity framework. Una raccolta contiene quadro. Ciò significa che l’inizializzazione di una raccolta non ha senso in termini di business logic: non definisce un’associazione tra entity framework. L’impostazione di un riferimento fa.

    Quindi è puramente una questione di preferenza se o non, o in che modo, inizializzare gli elenchi incorporati.

    Per quanto riguarda il “come”, alcune persone preferiscono l’inizializzazione pigra:

     private ICollection
    _addresses; public virtual ICollection
    Addresses { get { return this._addresses ?? (this._addresses = new HashSet
    ()); }

    Impedisce le eccezioni di riferimento null, quindi facilita il test dell’unità e la manipolazione della raccolta, ma impedisce anche l’inizializzazione non necessaria. Quest’ultimo può fare la differenza quando una class ha relativamente molte collezioni. Il rovescio della medaglia è che richiede un numero relativamente elevato di tubature, esp. se confrontato con le proprietà automatiche senza inizializzazione. Inoltre, l’avvento dell’operatore di propagazione null in C # ha reso meno urgente l’inizializzazione delle proprietà della raccolta.

    … a meno che non venga applicato il caricamento esplicito

    L’unica cosa è che l’inizializzazione delle raccolte rende difficile verificare se una raccolta è stata caricata da Entity Framework. Se una raccolta è inizializzata, una dichiarazione come …

     var users = context.Users.ToList(); 

    … creerà gli oggetti User con raccolte di Addresses vuoti, non null (caricamento pigro a parte). Controllare se la raccolta è caricata richiede un codice come …

     var user = users.First(); var isLoaded = context.Entry(user).Collection(c => c.Addresses).IsLoaded; 

    Se la raccolta non è inizializzata, verrà eseguito un semplice controllo null . Quindi, quando il caricamento esplicito selettivo è una parte importante della tua pratica di codifica, cioè …

     if (/*check collection isn't loaded*/) context.Entry(user).Collection(c => c.Addresses).Load(); 

    … potrebbe essere più conveniente non inizializzare le proprietà della raccolta.

    Proprietà di riferimento: no

    Le proprietà di riferimento sono quadro, quindi assegnare a esse un object vuoto è significativo .

    Peggio ancora, se li avvii nel costruttore, EF non li sovrascriverà quando materializzerà il tuo object o caricandolo lentamente. Avranno sempre i loro valori iniziali finché non li sostituirai triggersmente . Peggio ancora, potresti perfino finire per salvare quadro vuote nel database!

    E c’è un altro effetto: la correzione delle relazioni non si verificherà. La correzione delle relazioni è il processo mediante il quale EF collega tutte le quadro nel contesto tramite le loro proprietà di navigazione. Quando un User e una Licence vengono caricati separatamente, verrà comunque popolata User.License e viceversa. A meno che, naturalmente, se la License stata inizializzata nel costruttore. Questo vale anche per le associazioni 1: n. Se Address inizializzasse un User nel suo costruttore, User.Addresses non verrebbe popolato!

    Entity Framework core

    La correzione delle relazioni nel core di Entity Framework (2.1 al momento della scrittura) non è influenzata dalle proprietà di navigazione di riferimento inizializzate nei costruttori. Cioè, quando gli utenti e gli indirizzi vengono estratti dal database separatamente, le proprietà di navigazione vengono popolate.
    Tuttavia, il caricamento lento non sovrascrive le proprietà di navigazione di riferimento inizializzate. Quindi, in conclusione, anche in EF-core inizializzare le proprietà di navigazione di riferimento nei costruttori può causare problemi. Non farlo Non ha senso comunque,

    In tutti i miei progetti seguo la regola: “Le raccolte non devono essere nulle, sono vuote o hanno dei valori”.

    Il primo esempio è ansible quando la creazione di queste quadro è responsabilità del codice di terze parti (ad es. ORM) e si sta lavorando su un progetto a breve termine.

    Il secondo esempio è migliore, da allora

    • sei sicuro che l’entity framework abbia tutte le proprietà impostate
    • si evita la sciocca NullReferenceException
    • rendi più felici i consumatori del tuo codice

    Le persone che praticano il Domain-Driven Design espongono le raccolte come di sola lettura ed evitano i setter su di esse. (vedi Qual è la migliore pratica per le liste di sola lettura in NHibernate )

    Q1: qual è il migliore? perché? Pro e contro?

    È meglio esporre raccolte non nulle poiché si evitano ulteriori verifiche nel codice (ad es. Addresses ). È un buon contratto da avere nella base di codice. Ma va bene per me esporre riferimento nullable alla singola entity framework (es. License )

    Q2: Nel secondo approccio ci sarebbe uno stack overflow se la class License avesse anche un riferimento alla class User . Significa che dovremmo avere un riferimento unidirezionale. (?) Come dovremmo decidere quale delle proprietà di navigazione dovrebbe essere rimossa?

    Quando ho sviluppato un modello di mapping dei dati da solo, ho cercato di evitare riferimenti bidirezionali e ho avuto riferimenti molto raramente da bambino a genitore.

    Quando uso gli ORM è facile avere riferimenti bidirezionali.

    Quando è necessario creare un’entity framework test per i miei test unitari con set di riferimento bidirezionale, seguo i seguenti passi:

    1. Costruisco parent entity con la children collection emty children collection .
    2. Quindi aggiungo evey child con riferimento parent entity nella children collection .

    L’istanza di avere costruttore senza parametri nel tipo di License I renderebbe necessaria la proprietà user .

     public class License { public License(User user) { this.User = user; } public int Id { get; set; } public string Key { get; set; } public DateTime Expirtion { get; set; } public virtual User User { get; set; } } 

    È ridondante aggiungere un new elenco, poiché il POCO dipende dal caricamento pigro.

    Il caricamento lento è il processo mediante il quale un’ quadro o una raccolta di quadro viene caricata automaticamente dal database la prima volta che si accede a una proprietà che fa riferimento all’entity framework / alle quadro. Quando si utilizzano i tipi di quadro POCO, il caricamento lento viene ottenuto creando istanze di tipi proxy derivati ​​e quindi sovrascrivendo le proprietà virtuali per aggiungere il hook di caricamento.

    Se si rimuovesse il modificatore virtuale, si disattiverebbe il caricamento lento e in tal caso il proprio codice non funzionerebbe più (perché nulla inizializzerebbe l’elenco).

    Si noti che Lazy Loading è una funzionalità supportata dal framework di entity framework, se si crea la class al di fuori del contesto di un DbContext, quindi il codice dipendente soffrirebbe ovviamente di una NullReferenceException

    HTH

    Q1: qual è il migliore? perché? Pro e contro?

    La seconda variante quando le proprietà virtuali sono impostate all’interno di un costruttore di quadro ha un problema definito che è chiamato ” Chiamata di membri virtuali in un costruttore “.

    Per quanto riguarda la prima variante senza inizializzazione delle proprietà di navigazione, ci sono 2 situazioni a seconda di chi / cosa crea un object:

    1. Il framework Entity crea un object
    2. Il codice utente crea un object

    La prima variante è perfettamente valida quando Entity Framework crea un object, ma può fallire quando un utente del codice crea un object.

    La soluzione per garantire che un utente del codice crei sempre un object valido è utilizzare un metodo factory statico :

    1. Rendi protetto il costruttore predefinito. Entity Framework funziona bene con i costruttori protetti.

    2. Aggiungi un metodo factory statico che crea un object vuoto, ad esempio un object User , imposta tutte le proprietà, ad esempio Addresses e License , dopo la creazione e restituisce un object User completamente costruito

    In questo modo Entity Framework utilizza un costruttore predefinito protetto per creare un object valido dai dati ottenuti da alcune origini dati e il consumatore del codice utilizza un metodo factory statico per creare un object valido.

    Uso la risposta da questo Perché il mio Entity Framework Code Prima raccolta proxy nullo e perché non posso impostarla?

    Ha avuto problemi con l’inizializzazione del costruttore. L’unica ragione per cui lo faccio è semplificare il codice di test. Assicurarmi che la raccolta non sia mai nulla mi salva costantemente inizializzando nei test ecc

    Le altre risposte rispondono completamente alla domanda, ma vorrei aggiungere qualcosa poiché questa domanda è ancora pertinente e viene visualizzata nelle ricerche su google.

    Quando si utilizza la procedura guidata “codice primo modello dal database” in Visual Studio, tutte le raccolte vengono inizializzate in questo modo:

     public partial class SomeEntity { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public SomeEntity() { OtherEntities = new HashSet(); } public int Id { get; set; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public virtual ICollection OtherEntities { get; set; } } 

    Tendo a prendere l’output del wizard come fondamentalmente una raccomandazione ufficiale di Microsoft, quindi perché sto aggiungendo a questa domanda di cinque anni. Pertanto, inizializzerei tutte le raccolte come HashSet .

    E personalmente, penso che sarebbe piuttosto facile modificare quanto sopra per sfruttare gli inizializzatori della proprietà automatica del C # 6.0:

      public virtual ICollection OtherEntities { get; set; } = new HashSet();