2010-06-29 7 views
22

Ho un obbligo di caricare un oggetto complesso chiamato Nodo ... bene il suo non è quel complesso ... sembra che segue: -eager loading Utilizzando Fluent NHibernate/NHibernate & Automapping

Un Nodo ha un riferimento EntityType che ha una uno a molti con Proprietà che a sua volta ha una uno a molti con PorpertyListValue

public class Node 
{ 
    public virtual int Id 
    { 
     get; 
     set; 
    } 

    public virtual string Name 
    { 
     get; 
     set; 
    } 

    public virtual EntityType Etype 
    { 
     get; 
     set; 
    } 

} 


public class EntityType 
{ 
    public virtual int Id 
    { 
     get; 
     set; 
    } 

    public virtual string Name 
    { 
     get; 
     set; 
    } 

    public virtual IList<Property> Properties 
    { 
     get; 
     protected set; 
    } 

    public EntityType() 
    { 
     Properties = new List<Property>(); 
    } 
} 

public class Property 
{ 
    public virtual int Id 
    { 
     get; 
     set; 
    } 

    public virtual string Name 
    { 
     get; 
     set; 
    }   

    public virtual EntityType EntityType 
    { 
     get; 
     set; 
    } 

    public virtual IList<PropertyListValue> ListValues 
    { 
     get; 
     protected set; 
    } 

    public virtual string DefaultValue 
    { 
     get; 
     set; 
    } 

    public Property() 
    { 
     ListValues = new List<PropertyListValue>(); 
    } 
} 


public class PropertyListValue 
{ 
    public virtual int Id 
    { 
     get; 
     set; 
    } 

    public virtual Property Property 
    { 
     get; 
     set; 
    } 

    public virtual string Value 
    { 
     get; 
     set; 
    } 

    protected PropertyListValue() 
    { 
    } 
} 

Quello che un cercando di fare è caricare l'oggetto Node con tutto il bambino oggetti tutti in una volta. Nessun carico pigro. Il motivo è che ho migliaia di oggetti Node nel database e devo inviarli via cavo usando il servizio WCF. Ho incontrato il problema SQL N + 1 delle classi. Sto usando Fluent NHibernate con Automapping e NHibernate Profiler mi ha suggerito di utilizzare FetchMode.Eager per caricare gli oggetti interi in una sola volta. Sto usando il seguente qyuery

 Session.CreateCriteria(typeof (Node)) 
      .SetFetchMode("Etype", FetchMode.Join) 
      .SetFetchMode("Etype.Properties", FetchMode.Join) 
      .SetFetchMode("Etype.Properties.ListValues", FetchMode.Join) 

O usando NHibernate LINQ

 Session.Linq<NodeType>() 
     .Expand("Etype") 
     .Expand("Etype.Properties") 
     .Expand("Etype.Properties.ListValues") 

Quando eseguo qualsiasi query precedente, entrambi generano una stessa query singola con tutti i esterno sinistro si unisce, che è ciò che Ho bisogno. Tuttavia, per qualche motivo, il ritorno di IList dalla query non viene caricato proprietà negli oggetti. Infatti il ​​conteggio dei nodi restituiti è uguale al numero di righe della query, in modo che i nodi sono oggetti repeated.Moreover, le proprietà all'interno di ogni nodo vengono ripetuti, e così i Listvalues.

Quindi vorrei sapere come modificare la query precedente per restituire tutti i nodi unici con le proprietà ei valori di elenco al loro interno.

Grazie Nabeel

+0

su google ho scoperto DistinctRootEntityResultTransformer ma che risolve solo il problema per gli oggetti radice. Sto ancora ottenendo duplicati nelle raccolte figlio. Ogni oggetto radice nella lista restituita ha qualche strano pasticcio prodotto cartesiano nel bambino collezioni con più istanze della stessa entità. Qualche idea? In attesa di Nabeel – nabeelfarid

+1

Penso di aver trovato la soluzione, ma mi piacerebbe sapere se è la corretta. Le collezioni bambino (EType.Properties, Etype.Properties.ListValues) oggetto principale all'interno (nodo) sono IList. E ho letto nella documentazione che IList può contenere duplicati, quindi se cambio IList a IImpostare/ ICollection, quindi la query non carica istanze duplicate all'interno le collezioni bambino. Ma questa soluzione richiede un sacco di refactoring. Vorrei sapere se c'è un modo per ottenere lo stesso utilizzando IList per bambini collezioni? attesa, Nabeel – nabeelfarid

+1

Ho lo stesso problema (utilizzando Fetchmode.Eager). Sono abbastanza deluso in NHibernate per questo. Preferirei avere un errore rispetto ai dati errati. – UpTheCreek

risposta

13

ho dato fuori io stesso. La chiave è di usare SetResultTransformer() passaggio di un oggetto di DistinctRootEntityResultTransformer come parametro.Quindi la domanda ora sembra come segue

Session.CreateCriteria(typeof (Node)) 
    .SetFetchMode("Etype", FetchMode.Join) 
    .SetFetchMode("Etype.Properties", FetchMode.Join) 
    .SetFetchMode("Etype.Properties.ListValues", FetchMode.Join) 
    .SetResultTransformer(new DistinctRootEntityResultTransformer()); 

ho trovato la risposta alle mie domande attraverso questi link:

http://www.mailinglistarchive.com/html/[email protected]/2010-05/msg00512.html

http://ayende.com/Blog/archive/2010/01/16/eagerly-loading-entity-associations-efficiently-with-nhibernate.aspx

+4

+1, ma Wow questo è brutto. Questo sembra un tale casino - Non sono impressionato da Nibernate per questo. Perché dovremmo avere bisogno di trasformare il risultato? Sembra che l'NH non stia facendo il suo lavoro correttamente. – UpTheCreek

21

ogni definizione deve avere lazy loading off

nel nodo Mappa:

Map(x => x.EntityType).Not.LazyLoad(); 

in EnityType Mappa:

Map(x => x.Properties).Not.LazyLoad(); 

e così via ..

Inoltre, vedere NHibernate Eager loading multi-level child objects per una volta eager loading

Aggiunto:

Ulteriori informazioni su Sql N + 1:

http://nhprof.com/Learn/Alerts/SelectNPlusOne

+2

Grazie per la risposta Tim, Beh, non voglio impostare il .Not.LazyLoad() nel mapping perché diventerà un comportamento predefinito, e nella mia applicazione ho un servizio wcf che deve passare i dati a il client e voglio caricare tutti i dati contemporaneamente in un'unica query per evitare lo scenario SQL N + 1 (http://nhprof.com/Learn/Alerts/SelectNPlusOne). Il resto dell'applicazione non richiede il carico eccessivo. Quindi qualche idea come posso affrontare questo scenario? – nabeelfarid

+1

Anche la mia comprensione è che. Non. LazyLoad non risolve il problema SQL N + 1. Fro mNhibernate profiler Ho notato che sebbene carichi tutto l'intero grafo di oggetti in un colpo solo, genera ancora più di una query, una query per ogni riferimento/oggetto hasmany, che non voglio perché ho thosands di nodi con hundered di entitytypes e proprietà e non voglio che vengano generate migliaia di query uniche. Nabeel – nabeelfarid

+0

ho pensato si voleva mappato in questo modo. Ho aggiunto un collegamento a un'altra domanda che mostra il caricamento avido in una particolare istanza. Penso che questo ti aiuterà a produrre un join. In caso contrario, è possibile cercare di raddrizzare una stored procedure e mapparla come query con nome. Vedere il codice dei poster originali in http://stackoverflow.com/questions/1637862/fluent-nhibernate-and-stored-procedures per un esempio di quello –

4

SetResultTransformer con DistinctRootEntityResultTransformer funziona solo per oggetto principale, ma Le collezioni IList saranno moltiplicate.

+0

questo è corretto. Si deve usare IImpostare o ICollection – nabeelfarid

+0

Come pensate di utilizzare IImpostare o ICollection? –

8

ho finito con qualcosa di simile:

HasMany(x => x.YourList).KeyColumn("ColumnName").Inverse().Not.LazyLoad().Fetch.Join() 

Basta fare in modo di selezionare il soggetto come questo, per evitare duplicazioni a causa del join:

session.CreateCriteria(typeof(T)).SetResultTransformer(Transformers.DistinctRootEntity).List<T>(); 
+0

Puoi fare questo con i riferimenti troppo? – aggietech