2010-12-31 4 views
7

ho questo oggetto grafico:LINQ to NHibernate ThenFetch più proprietà

// Lots of stuff omitted for brevity; these are all virtual properties and there 
// are other properties which aren't shown on all classes. 
class A { 
    B b; 
    C c; 
    DateTime timestamp; 
} 
class B { 
    X x; 
    Y y; 
} 
class X { 
    int id; 
} 
class C { } 
class Y { } 

or to put it more simply, 
a = { 
    b: { 
     x { id: int }, 
     y: { } 
    }, 
    c: { }, 
    timestamp: DateTime 
}  

Ora sto facendo una query in cui ho intenzione di restituire un elenco di A s e ho bisogno tutti i loro B s , C s, X s e Y s. Vado anche a raggrupparli per B in una ricerca.

ILookup<B, A> GetData(List<int> ids) { 
    using (ISession session = OpenSession()) { 
     var query = from a in session.Query<A>() 
        where ids.Contains(a.b.x.id) 
        orderby A.timestamp descending 
        select a; 

     query = query 
      .Fetch(a => a.b) 
      .ThenFetch(b => b.x) 
      .Fetch(a => a.b) 
      .ThenFetch(b => b.y) 
      .Fetch(a => a.c); 

     return query.ToLookup(a => a.b); 
    } 
} 

Un paio di cose da notare:

  1. Questo è un rapporto in cui deve essere restituito tutti i dati - i risultati illimitati non è un problema.
  2. Sto facendo il raggruppamento utilizzando ToLookup perché l'utilizzo di group by sembra essere più complicato quando sono necessari tutti i valori effettivi: è necessario interrogare il database per i gruppi e quindi per i loro valori effettivi.

La mia domanda è come specificare correttamente la strategia di recupero. Il modo in cui l'ho fatto è l'unico modo che ho trovato per questo a correre (avendo recuperato tutte le bx e dai valori) - ma produce SQL che sembra sbagliato:

select /* snipped - every mapped field from a0, b1, x2, b3, y4, c5 - but not b6 */ 
from  [A] a0 
     left outer join [B] b1 
      on a0.B_id = b1.BId 
     left outer join [X] x2 
      on b1.X_id = x2.XId 
     left outer join [B] b3 
      on a0.B_id = b3.BId 
     left outer join [Y] y4 
      on b3.Y_id = y4.YId 
     left outer join [C] c5 
      on a0.C_id = c5.CId, 
     [B] b6 
where a0.B_id = b6.BId 
     and (b6.X_id in (1, 2, 3, 4, 5)) 
order by a0.timestamp desc 

Come si può vedere si sta facendo il valore per a.b 3 volte - b1 e b3 per il recupero e b6 per la clausola where.

  1. Suppongo che questo abbia un impatto negativo sulle prestazioni del DB - sono corretto?
  2. C'è un modo per modificare le mie chiamate .Fetch in modo che recuperi solo una volta a.b?
  3. È un buon approccio al mio problema?

risposta

4

Se si eseguono più recuperi di proprietà uno-a-molti in una query, si ottiene un prodotto cartesiano. NHibernate non gestisce questo - AFAIK, è stato fatto deliberatamente per farlo comportarsi come un vero join SQL. HQL fa la stessa cosa

Non è necessario eseguire tutti i recuperi in un'unica soluzione. Dividi la query ed esegui ciascun recupero/join uno-a-molti in una query separata. Ciascuno memorizzerà nella cache i propri dati nella sessione e connetterà correttamente tutti i riferimenti agli oggetti. (Nota: non ho mai provato questo con LINQ, ma funziona in HQL, e il principio è lo stesso)

Fuori della parte superiore della mia testa, che potrebbe essere simile a questa:

ILookup<B, A> GetData(List<int> ids) { 
using (ISession session = OpenSession()) { 
    var query = from a in session.Query<A>() 
       where ids.Contains(a.b.x.id) 
       orderby A.timestamp descending 
       select a; 

    query 
     .Fetch(a => a.b) 
     .ThenFetch(b => b.x) 
     .ToList(); 
    query 
     .Fetch(a => a.b) 
     .ThenFetch(b => b.y) 
     .Fetch(a => a.c) 
     .ToList(); 

    return query.ToLookup(a => a.b); 
} 

Ci è un'ulteriore ottimizzazione che potresti fare, usa il metodo ToFuture() invece di ToList() ... Non sono sicuro di come funzioni con i metodi LINQ e ToLookup, ma non dovrebbe essere troppo difficile per essere corretto. ToFuture() accoderà le query ed eseguirà tutte in una volta invece di fare connessioni di database separate per ognuna.

+1

Prima di tutto, questi sono tutti molti a uno come si può vedere dalla mappatura (c'è solo una B in una A, e non una lista di essi), quindi l'intera query caricherà solo 4 entità. In secondo luogo, quello che stai suggerendo è esattamente l'opposto di quello che vorrei - invece di ridurre la complessità della query l'hai aumentato dividendolo in più query. La query ideale avrà i seguenti join: 'da [A] join esterno sinistro [B] in ...left outer join [X] on ... left outer join [Y] on ... '. – configurator

+1

Il mio commento precedente sembra duro; Non volevo impedirti di cercare di aiutare. Apprezzo il tuo sforzo, anche se non sembra come dal mio precedente commento. – configurator

+1

Ho verificato il comportamento suggerito riguardo alle entità correlate al precaricamento. Usando NHibernate Profiler posso vedere che sono recuperati solo una volta, quindi posso confermare che ciò che dice bdrajer è vero sulla memorizzazione nella cache dei dati nella sessione. –