2012-06-20 11 views
5

Ho il seguente codice, che un comportamento anomalo:Come risolvere super slow interrogazione EF/LINQ esegue più istruzioni SQL

TPM_USER user = UserManager.GetUser(context, UserId); 
var tasks = (from t in user.TPM_TASK 
      where t.STAGEID > 0 && t.STAGEID != 3 && t.TPM_PROJECTVERSION.STAGEID <= 10 
      orderby t.DUEDATE, t.PROJECTID 
      select t); 

La prima linea, UserManager.GetUser semplicemente non una semplice ricerca nel database per ottenere il corretto Record TPM_USER. Tuttavia, la seconda riga causa tutti i tipi di caos SQL.

Prima di tutto, esegue qui due istruzioni SQL. Il primo afferra ogni singola riga in TPM_TASK che è collegato a tale utente, che è talvolta decine di migliaia di righe:

SELECT 
-- Columns 
FROM TPMDBO.TPM_USERTASKS "Extent1" 
INNER JOIN TPMDBO.TPM_TASK "Extent2" ON "Extent1".TASKID = "Extent2".TASKID 
WHERE "Extent1".USERID = :EntityKeyValue1 

Questa query richiede circa 18 secondi utenti con un sacco di compiti. Mi aspetto che la clausola WHERE contenga anche i filtri STAGEID, che rimuoverebbero la maggior parte delle righe.

Avanti, sembra per eseguire una nuova query per ogni TPM_PROJECTVERSION coppia nella lista di cui sopra:

SELECT 
-- Columns 
FROM TPMDBO.TPM_PROJECTVERSION "Extent1" 
WHERE ("Extent1".PROJECTID = :EntityKeyValue1) AND ("Extent1".VERSIONID = :EntityKeyValue2) 

Anche se questa query è veloce, è eseguito centinaia di volte se l'utente ha compiti in un sacco di progetti.

La query vorrei generare sarebbe simile:

SELECT 
-- Columns 
FROM TPMDBO.TPM_USERTASKS "Extent1" 
INNER JOIN TPMDBO.TPM_TASK "Extent2" ON "Extent1".TASKID = "Extent2".TASKID 
INNER JOIN TPMDBO.TPM_PROJECTVERSION "Extent3" ON "Extent2".PROJECTID = "Extent3".PROJECTID AND "Extent2".VERSIONID = "Extent3".VERSIONID 
WHERE "Extent1".USERID = 5 and "Extent2".STAGEID > 0 and "Extent2".STAGEID <> 3 and "Extent3".STAGEID <= 10 

La query precedente verrà eseguito in circa 1 secondo. In genere, è possibile specificare che JOIN utilizza il metodo Include. Tuttavia, questo non sembra funzionare sulle proprietà. In altre parole, non posso fare:

from t in user.TPM_TASK.Include("TPM_PROJECTVERSION") 

Esiste un modo per ottimizzare questa istruzione LINQ? Sto usando .NET4 e Oracle come DB di back-end.

Soluzione:

Questa soluzione si basa sui suggerimenti di Kirk al di sotto, e opera da context.TPM_USERTASK non possono essere interrogati direttamente:

var tasks = (from t in context.TPM_TASK.Include("TPM_PROJECTVERSION") 
      where t.TPM_USER.Any(y => y.USERID == UserId) && 
      t.STAGEID > 0 && t.STAGEID != 3 && t.TPM_PROJECTVERSION.STAGEID <= 10 
      orderby t.DUEDATE, t.PROJECTID 
      select t); 

E fa risultato in una nidificato SELECT piuttosto che l'esecuzione di query TPM_USERTASK direttamente, ma sembra abbastanza efficiente nessuno-meno.

risposta

4

Sì, si sta abbattendo un utente specifico e quindi si fa riferimento alla relazione TPM_TASK. Che stia abbattendo ogni compito collegato a quell'utente è esattamente quello che dovrebbe fare. Non c'è una traduzione SQL ORM quando lo fai in questo modo. Stai ottenendo un utente, quindi acquisendo tutte le sue attività in memoria e quindi eseguendo alcuni filtri sul lato client. Tutto questo viene fatto usando il caricamento lento, quindi l'SQL sarà eccezionalmente inefficiente in quanto non è in grado di raggruppare nulla.

Invece, riscrivere la query per accedere direttamente contro TPM_TASK e filtro contro l'utente:

var tasks = (from t in context.TPM_TASK 
     where t.USERID == user.UserId && t.STAGEID > 0 && t.STAGEID != 3 && t.TPM_PROJECTVERSION.STAGEID <= 10 
     orderby t.DUEDATE, t.PROJECTID 
     select t); 

Si noti come stiamo controllando t.USERID == user.UserId.Ciò produce lo stesso effetto di user.TPM_TASK ma ora tutto il sollevamento pesante viene eseguito dal database piuttosto che dalla memoria.

+0

Sfortunatamente, questa idea non funzionerà. 'TPM_TASK' non ha la proprietà' USERID'. Gli utenti si riferiscono alle attività tramite la tabella 'TPM_USERTASK', che non è possibile eseguire query direttamente poiché viene utilizzata in una relazione molti-a-molti. Forse farei meglio a creare una vista nel database o qualcosa del genere? –

+0

Si può certamente scrivere la query per andare contro 'TPM_USERTASK' pure. Non sono sicuro di come sia impostato esattamente lo schema, ma qualcosa sulla falsariga di 'where t.TPM_TASK.Any (y => y.USERID == tUSER)' o semplicemente usare 'join' per inserire molti a molti. Sicuramente non devi scherzare con le visualizzazioni per raggiungere questo obiettivo. –

+0

Ok, scherzi con quell'idea .. Ti farò sapere in pochi minuti .. –