2012-04-07 20 views
6

Sto lavorando a un pezzo di codice, scritto da un collega, che si interfaccia con un'applicazione CRM utilizzata dalla nostra azienda. Ci sono due query LINQ to Entities in questo pezzo di codice che vengono eseguite più volte nella nostra applicazione, e mi è stato chiesto di ottimizzarle perché una di esse è molto lenta.La query LINQ to Entities richiede molto tempo per la compilazione, SQL corre veloce

Queste sono le domande:

prima interrogazione, questo uno compila praticamente all'istante. Si ottiene informazioni relative dal database CRM, filtrando da un elenco di ID relazione data dall'applicazione:

from relation in context.ADRELATION 
where ((relationIds.Contains(relation.FIDADRELATION)) && (relation.FLDELETED != -1)) 
join addressTable in context.ADDRESS on relation.FIDADDRESS equals addressTable.FIDADDRESS 
    into temporaryAddressTable 
from address in temporaryAddressTable.DefaultIfEmpty() 
join mailAddressTable in context.ADDRESS on relation.FIDMAILADDRESS equals 
    mailAddressTable.FIDADDRESS into temporaryMailAddressTable 
from mailAddress in temporaryMailAddressTable.DefaultIfEmpty() 
select new { Relation = relation, Address = address, MailAddress = mailAddress }; 

La seconda query, che dura circa 4-5 secondi per compilare, e prende informazioni sulle persone da il database (ancora una volta filtrata da un elenco di ID):

from role in context.ROLE 
join relationTable in context.ADRELATION on role.FIDADRELATION equals relationTable.FIDADRELATION into temporaryRelationTable 
from relation in temporaryRelationTable.DefaultIfEmpty() 
join personTable in context.PERSON on role.FIDPERS equals personTable.FIDPERS into temporaryPersonTable 
from person in temporaryPersonTable.DefaultIfEmpty() 
join nationalityTable in context.TBNATION on person.FIDTBNATION equals nationalityTable.FIDTBNATION into temporaryNationalities 
from nationality in temporaryNationalities.DefaultIfEmpty() 
join titelTable in context.TBTITLE on person.FIDTBTITLE equals titelTable.FIDTBTITLE into temporaryTitles 
from title in temporaryTitles.DefaultIfEmpty() 
join suffixTable in context.TBSUFFIX on person.FIDTBSUFFIX equals suffixTable.FIDTBSUFFIX into temporarySuffixes 
from suffix in temporarySuffixes.DefaultIfEmpty() 
where ((rolIds.Contains(role.FIDROLE)) && (relation.FLDELETED != -1)) 
select new { Role = role, Person = person, relation = relation, Nationality = nationality, Title = title.FTXTBTITLE, Suffix = suffix.FTXTBSUFFIX }; 

ho installato SQL Profiler e preso lo SQL da entrambe le query, poi corse in SQL Server Management Studio. Entrambe le query sono state eseguite molto velocemente, anche con un numero di ID ampio (~ 1000). Quindi il problema sembra essere nella compilazione della query LINQ.

Ho provato a utilizzare una query compilata, ma poiché questi possono contenere solo parametri primitivi, ho dovuto rimuovere la parte con il filtro e applicarla dopo la chiamata a Invoke(), quindi non sono sicuro se quello aiuta molto. Inoltre, poiché questo codice viene eseguito in un'operazione di servizio WCF, non sono sicuro che la query compilata continui a esistere anche nelle chiamate successive.

Infine, ciò che ho provato è stato selezionare solo una singola colonna nella seconda query. Sebbene questo ovviamente non mi dia le informazioni di cui ho bisogno, ho pensato che sarebbe più veloce delle ~ 200 colonne che stiamo selezionando ora. In questo caso, ci sono voluti ancora 4-5 secondi.

Non sono affatto un guru LINQ, quindi riesco a malapena a seguire questo codice (ho la sensazione che non sia scritto in modo ottimale, ma non riesco a metterci il dito sopra). Qualcuno potrebbe darmi un suggerimento sul motivo per cui questo problema potrebbe verificarsi?

L'unica soluzione che mi rimane è selezionare manualmente tutte le informazioni invece di unire tutte queste tabelle. Allora finirei con circa 5-6 domande. Non male, immagino, ma dato che non ho a che fare con SQL orribilmente inefficiente (o almeno un livello accettabile di inefficienza), speravo di impedirlo.

Grazie in anticipo, spero di aver chiarito le cose. In caso contrario, non esitate a chiedere e fornirò ulteriori dettagli.


Edit: ho finito per l'aggiunta di associazioni sul mio quadro entità (il database di destinazione non ha avuto le chiavi esterne specificate) e riscrivere la query nel seguente modo:

context.ROLE.Where(role => rolIds.Contains(role.FIDROLE) && role.Relation.FLDELETED != -1) 
      .Select(role => new 
          { 
           ContactId = role.FIDROLE, 
           Person = role.Person, 
           Nationality = role.Person.Nationality.FTXTBNATION, 
           Title = role.Person.Title.FTXTBTITLE, 
           Suffix = role.Person.Suffix.FTXTBSUFFIX 
          }); 

sembra molto più leggibile ed è anche più veloce.

Grazie per i suggerimenti, manterrò sicuramente quello sul rendere più query compilate per diversi numeri di argomenti in mente!

risposta

1

La risposta di Gabriels è corretta: utilizzare una query compilata.

Sembra che lo si stia compilando di nuovo per ogni richiesta WCF che ovviamente vanifica lo scopo dell'inizializzazione di una sola volta. Invece, metti la query compilata in un campo statico.

Edit:

fare questo: Inviare carico massimo per il vostro servizio e mettere in pausa il debugger per 10 volte. Guarda lo stack delle chiamate. Si è fermato più spesso nel codice L2S o nel codice ADO.NET? Questo ti dirà se il problema è ancora con L2S o con SQL Server.

Quindi, sistemiamo il filtro. Dobbiamo reinserirlo nella query compilata. Questo è possibile solo trasformando questo:

rolIds.Contains(role.FIDROLE) 

a questo:

role.FIDROLE == rolIds_0 || role.FIDROLE == rolIds_1 || ... 

Hai bisogno di un nuova query compilata per ogni cardinalità di rolIds. Questo è brutto, ma è necessario farlo compilare. Nel mio progetto, ho automatizzato questo compito ma qui puoi fare una soluzione una tantum.

Immagino che la maggior parte delle query avrà pochissimi ID di ruolo in modo da poter materializzare 10 query compilate per cardinalità 1-10 e se la cardinalità supera 10 si ricade al filtro lato client.

+0

Come ho detto sulla risposta di Gabriel GM, li ho messi in un campo statico. Cercherò di nuovo solo per essere sicuro al 110%, suppongo. Funzionerà comunque, dal momento che potrò solo mettere la parte sulla clausola where nella query compilata? –

+0

Ho aggiunto più cose. – usr

+1

Oh, ottima idea della cosa cardinalità! Hai ragione, la maggior parte delle richieste avrà meno di 10 id di ruolo. Sicuramente qualcosa da guardare. Grazie! –

0

Se si decide di mantenere la query all'interno del codice, è possibile compilarlo. Devi ancora compilare la query una volta quando esegui la tua app, ma tutte le chiamate successive useranno quella query già compilata. Puoi dare un'occhiata alla guida MSDN qui: http://msdn.microsoft.com/en-us/library/bb399335.aspx.

Un'altra opzione sarebbe quella di utilizzare una stored procedure e chiamare la procedura dal codice. Quindi nessun tempo di compilazione.

+0

Ho provato a utilizzare una query compilata, ma ci sono voluti 4 secondi ogni volta che ho chiamato l'operazione di servizio. Ho la sensazione che non sia stato tenuto tra le richieste (l'ho reso statico). Inoltre, non posso utilizzare una stored procedure, poiché la società che rende il pacchetto CRM smetterà di fornire supporto se tocchiamo il loro database se non leggendo da esso:/ Potrebbe essere utile se ho riscritto la query per utilizzare le proprietà di navigazione anziché si unisce? –