Sto usando ASP.NET 4.5, MVC5, C #, LINQ, EF6, SQL Server 2012 / SQL Azure.
Devo migliorare significativamente l'efficienza di una query complessa. In sostanza, lo scopo dell'attività è copiare un recordset "campione" con molti record figlio. Attualmente sto facendo questo tramite C # e LINQ. Ho il sospetto che sto interrogando il database all'interno di più blocchi Foreach, quindi sto causando molte chiamate al database. Mentre ogni query è piccola, il numero di chiamate non lo è. Potrebbe essere un 200+. Credo che chiamino questo problema "N + 1".
Il seguente layout dà un'idea delle relazioni e quindi delle query.
Table1-<Table1.1
-<Table1.2-<Table1.2.1
-<Table1.2.2-<Table1.2.2.1
-<Table1.2.2.2
Invece di usare "Foreach" per riportare "Table1.1" ecc, voglio riportare tutti i dati relativi in un colpo per minimizzare il numero di chiamate al DB. Capisco che ho bisogno di usare "Includi". Ho ottenuto fino a:
db.Table1.Where(r=>r.Id=myId).Include(x=>x.Table1.2).Include(x=>x.Table1.2)
Tuttavia non sono sicuro di come modificare questa istruzione per riportare i dati in "Table1.2.2.2". Questa è la mia domanda.
Grazie in anticipo.
MODIFICA 1
Ho trovato una risposta iniziale.
db.Table1.Include(x=>x.Table1.1)
.Include(x=>x.Table1.2)
.Include(x=>x.Table1.2.Select(y=>y.Table1.2.1)
Tuttavia, potrei non aver bisogno della linea di mezzo, quindi quanto segue potrebbe andare bene.
db.Table1.Include(x=>x.Table1.1)
.Include(x=>x.Table1.2.Select(y=>y.Table1.2.1)
Pensieri...
EDIT2
Devo anche scendere a 5 livelli. Sto scoprendo che questo recupera volte !! Indipendentemente dal fatto che EF sia confuso nella compilation o che il recupero sia troppo complicato o entrambi non sono sicuro. C'è forse un limite su quanti livelli si può usare "Includi"? Inoltre, non sono sicuro che specificando il percorso per i nipoti, i genitori vengano automaticamente recuperati o devi specificare i genitori separatamente?
C'è forse un limite su quanti livelli si può usare "Includi"?
C'è! Come ho spiegato qui sull'istruzione SQL generata -
SELECT
è la somma di tutte le colonne in tutte le tabelle coinvolte Questo è potenzialmente un enorme set di risultati (lungo e ampio) restituito dal database. A parte questo, è difficile per Query Optimizer del motore db trovare un buon piano di query. Il database avrà difficoltà a scricchiolare tutti i dati e non sorprende che il comando scada.
L'alternativa è caricare i dati in blocchi. Ma è più facile a dirsi che a farsi. In un certo senso, si caricano già dati in blocchi, ma questi blocchi sono troppo piccoli e le query troppe (sì, N + 1). I pezzi dovrebbero essere più grandi. Non esiste una strategia chiara su come farlo. Dipende dalla struttura della tabella e dal numero di dati. Ma lasciami tentare di indicarti la giusta direzione.
5 livelli in basso
Per brevità, diciamo che le tabelle e le associazioni sono A
< B
< C
< D
< E
("<" che significa 1: n). La query di root è qualcosa di simile
var query = As.Where(a => a.Property == value).ToList();
[Quindi non vuoi tutto As
, perché sarebbe facile: allora puoi anche caricare tutti i bambini.]
Supponiamo che tu possa Include
i Bs
senza problemi, ma ciò include anche il Cs
. Quindi la query diventa:
var query = As.Where(a => a.Property == value)
.Include(a => a.Bs).ToList();
E la Cs
, ecc. Dovrebbe essere caricata in blocchi di dati.
Una bella funzionalità di Entity Framework è che connette automaticamente tutte le entità caricate in un contesto, tramite un processo noto come risoluzione delle relazioni . Quindi, se si caricano separatamente le Cs
, le raccolte nei loro oggetti B
padre verranno popolate. Ciò semplifica il caricamento delle Cs
richieste:
var cs = Cs.Where(c => c.B.A.Property == value).ToList();
(supponendo che anche i riferimenti posteriori facciano parte del tuo modello)
No, se puoi tranquillamente includere la Ds
, abbiamo quasi finito:
var cs = Cs.Where(c => c.B.A.Property == value)
.Include(c => c.Ds).ToList();
E l'ultimo livello è caricato da:
var es = Es.Where(e => e.D.C.B.A.Property == value).ToList();
Questo livello di nidificazione (i punti) può sembrare spaventoso. Creerà una query con quattro join. Tuttavia, la grande differenza con 4 Incudes
è che ora vengono interrogate solo le colonne E
e le righe. Il risultato della query non esplode. E i motori di database sono ottimizzati per l'esecuzione di join.
Quindi questo ti dà alcune maniglie per giocare con i livelli Include
e le query separate finché non hai una configurazione che funzioni bene (abbastanza).
Un'ultima cosa: ricordati di disattivare il caricamento pigro. EF riempie automaticamente le raccolte, ma non le contrassegna come caricate. Se il caricamento lazy è abilitato, l'accesso alle raccolte attiverà comunque le query N + 1.