2010-11-01 4 views
39

Sto utilizzando DBContext e ho due classi le cui proprietà sono tutte virtuali. Posso vedere nel debugger che sto ottenendo un oggetto proxy quando interrogo il contesto. Tuttavia, una proprietà di raccolta è ancora nullo quando provo ad aggiungerla. Pensavo che il proxy avrebbe assicurato che la raccolta fosse inizializzata.Perché il mio Entity Framework Code Prima raccolta proxy nullo e perché non posso impostarla?

Perché il mio oggetto Poco può essere utilizzato al di fuori del suo contesto di dati, ho aggiunto un controllo per la raccolta essere nullo nel costruttore e creare, se necessario:

public class DanceStyle 
{ 
    public DanceStyle() 
    { 
     if (DanceEvents == null) 
     { 
      DanceEvents = new Collection<DanceEvent>(); 
     } 
    } 
    ... 
    public virtual ICollection<DanceEvent> DanceEvents { get; set; } 
} 

che funziona al di fuori del contesto dati, ma se Ricevo un oggetto usando una query, sebbene il test sia vero, quando provo ad impostarlo, ottengo la seguente eccezione: 'La proprietà' DanceEvents 'sul tipo' DanceStyle_B6089AE40D178593955F1328A70EAA3D8F0F01DDE9F9FBD615F60A34F9178B94 'non può essere impostata perché la raccolta è già impostata su EntityCollection. '

Posso vedere che è null e non posso aggiungerlo, ma nemmeno posso impostarlo su una raccolta perché il proxy dice che è già impostato. Quindi non posso usarlo. Non ho capito bene.

Qui è la classe DanceEvent:

public class DanceEvent 
{ 
    public DanceEvent() 
    { 
     if (DanceStyles == null) 
     { 
      DanceStyles = new Collection<DanceStyle>(); 
     } 
    } 
    ... 
    public virtual ICollection<DanceStyle> DanceStyles { get; set; } 
} 

ho omesso le altre proprietà tipo valore dal codice precedente. Non ho altri mapping per quelle classi nella classe di contesto.

risposta

13

ho trovato la soluzione a questo problema qui: Code First adding to collections? How to use Code First with repositories?

ho rimosso 'virtuale' da tutte le proprietà ad eccezione di collezioni e oggetti caricati pigri, cioè, tutti i tipi nativi.

Ma ancora non capisco come si possa finire con la situazione in cui si dispone di una raccolta nullo che non è possibile utilizzare e non è possibile impostarla su una raccolta valida.

Ho anche trovato this answer from Rowan Miller su un forum MSDN

Ciao,

Se fate tutte le proprietà virtuale allora EF genererà classi proxy in fase di runtime che deriva dal POCO classificato, questi proxy consentono EF per scoprire i cambiamenti in tempo reale piuttosto che dover acquisire i valori originali del tuo oggetto e quindi cercare le modifiche quando salvi (questo è ovviamente vantaggioso per prestazioni e memoria, ma la differenza sarà trascurabile a meno che tu non abbia un gran numero di entità caricate in memoria). Questi sono conosciuti come "proxy di monitoraggio delle modifiche", se si rendono virtuali le proprietà di navigazione, viene comunque generato un proxy ma è molto più semplice e include solo una logica per eseguire il caricamento lento quando si accede a una proprietà di navigazione.

Poiché il codice originale stava generando proxy di rilevamento delle modifiche, EF sostituiva la proprietà della raccolta con un tipo di raccolta speciale per aiutarlo a scoprire le modifiche. Poiché si tenta di impostare la raccolta su un semplice elenco nel costruttore, si ottiene l'eccezione.

A meno che non si riscontrino problemi di prestazioni, seguirei il suggerimento di Terrence e rimuovere semplicemente "virtuale" dalle proprietà di non navigazione.

~ Rowan

così sembra che io ho solo il problema con un pieno 'delega il rilevamento delle modifiche' se tutte le mie proprietà sono virtuali. Ma dato ciò, perché non posso ancora usare la proprietà virtuale sul proxy di tracciamento delle modifiche? Questo codice fa saltare in aria sulla linea tre, perché ds2.DanceEvents è nullo e non può essere impostato nel costruttore:

DanceStyle ds2 = ctx.DanceStyles.Where(ds => ds.DanceStyleId == 1).Single(); 
DanceEvent evt = CreateDanceEvent(); 
ds2.DanceEvents.Add(evt); 

Sono ancora confuso, anche se il mio codice è ora al lavoro a causa della correzione sopra.

47

Come correttamente osservato nella risposta alla propria domanda, rimuovere la parola chiave "virtuale" dalle proprietà della raccolta aggira il problema, impedendo a Entity Framework di creare un proxy di rilevamento delle modifiche. Tuttavia, questa non è una soluzione per molte persone, perché i proxy di monitoraggio delle modifiche possono essere davvero convenienti e possono aiutare a prevenire i problemi quando si dimentica di rilevare le modifiche nei punti giusti del codice.

Un approccio migliore sarebbe quello di modificare le classi POCO, in modo che esse istanziano le proprietà della raccolta nel loro access point, piuttosto che nel costruttore. Ecco la classe POCO, modificato per consentire la creazione di proxy rilevamento delle modifiche:

public class DanceEvent 
{ 
    private ICollection<DanceStyle> _danceStyles; 
    public virtual ICollection<DanceStyle> DanceStyles 
    { 
     get { return _danceStyles ?? (_danceStyles = new Collection<DanceStyle>()); } 
     protected set { _danceStyles = value; } 
    } 
} 

Nel codice sopra la proprietà della raccolta non è più automatico, ma piuttosto ha un campo di supporto. È meglio se lasci il setter protetto, impedendo a qualsiasi codice (diverso dal proxy) di modificare successivamente queste proprietà. Noterai che il costruttore non era più necessario ed è stato rimosso.

+0

Questo è un altro modo per farlo ma non spiega il mio commento: "Funziona al di fuori del contesto dati, ma se recupero un oggetto usando una query, anche se il test è vero, quando provo ad impostarlo, io ottieni la seguente eccezione: 'La proprietà' DanceEvents 'sul tipo' DanceStyle_B6089AE40D178593955F1328A70EAA3D8F0F01DDE9F9FBD615F60A34F9178B94 'non può essere impostata perché la raccolta è già impostata su EntityCollection.' Posso vedere che è nullo e non posso aggiungerlo, ma nemmeno posso impostarlo su una raccolta perché il proxy dice che è già impostato, quindi non posso usarlo, sono confuso. " –

+0

Non riesco a riprodurre ciò che stai descrivendo. Nella mia esperienza, quando l'entità viene creata un'istanza come proxy (o come risultato del fatto che viene restituita da una query o se si utilizza il metodo DbSet.Create), le sue proprietà di raccolta vengono create un'istanza con gli oggetti EntityCollection. Non dovresti mai dover impostare queste proprietà - basta aggiungere/rimuovere entità da esse. – Pando

+0

È possibile che il comportamento sia cambiato da quando ho scritto la mia domanda 2 anni fa. –

3

vecchia questione ...

Poco classe:

public partial class MyPOCO 
{ 
    public MyPOCO() 
    { 
     this.MyPocoSub = new HashSet<MyPocoSub>(); 
    } 

    //VIRTUAL 
    public virtual ICollection<MyPocoSub> MyPocoSub { get; set; } 
} 

e codice proxy:

public override ICollection<MyPocoSubSet> MyPocoSubSets 
    { 
     get 
     { 
      ICollection<MyPocoSubSet> myPocoSubSets = base.MyPocoSubSets; 
      if (!this.ef_proxy_interceptorForMyPocoSubSets(this, myPocoSubSets)) 
      { 
       return base.MyPocoSubSets; 
      } 
      return myPocoSubSets; 
     } 
     set 
     { 
      if (value != this.RelationshipManager.GetRelatedEnd("WindowsFormsApplication.Models.MyPocoSubSet_MyPOCO", "MyPocoSubSet_MyPOCO_Source")) 
      { 
       // EXCEPTION 
       throw new InvalidOperationException("The property 'MyPocoSubSets' on type 'MyPOCO_A78FCE6C6A890855C68B368B750864E3136B589F9023C7B1D90BF7C83FD291AC' cannot be set because the collection is already set to an EntityCollection."); 
      } 
      base.MyPocoSubSets = value; 
     } 
    } 

Come si può vedere che eccezione sollevata in classe proxy nel ExtityFramework 5. Questo significa quel comportamento esiste ancora.