2013-01-17 12 views
5

Mi è stato chiesto di produrre un report guidato da una query SQL piuttosto complessa su un database SQL Server. Dal momento che il sito della relazione è stata già utilizzando Entity Framework 4.1, ho pensato di tentare di scrivere la query utilizzando EF e LINQ:Esiste un modo per ottimizzare questa query LINQ alle entità?

var q = from r in ctx.Responses 
        .Where(x => ctx.Responses.Where(u => u.UserId == x.UserId).Count() >= VALID_RESPONSES) 
        .GroupBy(x => new { x.User.AwardCity, x.Category.Label, x.ResponseText }) 
     orderby r.FirstOrDefault().User.AwardCity, r.FirstOrDefault().Category.Label, r.Count() descending 
     select new 
     { 
      City = r.FirstOrDefault().User.AwardCity, 
      Category = r.FirstOrDefault().Category.Label, 
      Response = r.FirstOrDefault().ResponseText, 
      Votes = r.Count() 
     }; 

Questa query collima voti, ma solo da parte degli utenti che hanno presentato un certo numero di voti minimi richiesti.

Questo approccio è stato un disastro completo dal punto di vista delle prestazioni, quindi siamo passati a ADO.NET e la query è stata eseguita molto rapidamente. Ho guardato l'SQL generato da LINQ usando SQL Profiler, e sebbene sembrasse atroce come al solito, non ho visto alcun indizio su come ottimizzare l'istruzione LINQ per renderla più efficiente.

Ecco la versione TSQL diritta:

WITH ValidUsers(UserId) 
AS 
(
    SELECT UserId 
    FROM Responses 
    GROUP BY UserId 
    HAVING COUNT(*) >= 103 
) 
SELECT d.AwardCity 
    , c.Label 
    , r.ResponseText 
    , COUNT(*) AS Votes 
FROM ValidUsers u 
JOIN Responses r ON r.UserId = u.UserId 
JOIN Categories c ON r.CategoryId = c.CategoryId 
JOIN Demographics d ON r.UserId = d.Id 
GROUP BY d.AwardCity, c.Label, r.ResponseText 
ORDER BY d.AwardCity, s.SectionName, COUNT(*) DESC 

Quello che mi chiedo è: è questa query troppo complessa per EF e LINQ per gestire in modo efficiente o Ho perso un trucco?

+0

Sto indovinando tutti i FirstOrDefaults stanno causando esso. Hai provato ad aggiungere un 'let response = r.First()' prima del groupby? O scambiando il Select e l'OrderBy? Mi piace questo http://stackoverflow.com/a/5013740/736079 – jessehouwing

+2

Esiste una proprietà di navigazione come 'User.Responses'? –

+0

@jessehouwing usando let la risposta aiuta in modo considerevole, sebbene la versione LINQ sia ancora molto più lenta di ADO.NET. Se inserisci questo come risposta, lo involo almeno. Sto avendo problemi con la strategia di Jon Skeet di scambiare selezione e ordine, principalmente non riesco a capire come ottenere il conto con questo costrutto. –

risposta

4

Utilizzando un let per ridurre il numero di r.First() probabilmente migliorerà le prestazioni. Probabilmente non è ancora abbastanza.

var q = from r in ctx.Responses 
       .Where() 
       .GroupBy() 
    let response = r.First() 
    orderby response.User.AwardCity, response.Category.Label, r.Count() descending 
    select new 
    { 
     City = response.User.AwardCity, 
     Category = response.Category.Label, 
     Response = response.ResponseText, 
     Votes = r.Count() 
    }; 
+0

Segnalo come risposta perché il suo effetto è approssimativamente equivalente alla soluzione di navigazione proposta da @GertArnold, ma Gert non ha ancora pubblicato il suo commento come risposta (scusa Gert, ti inviterò in anticipo). Va notato che, anche con entrambe le ottimizzazioni applicate, ADO.NET è ancora più veloce, ma il LINQ rivisto è di ordini di grandezza più veloce di quanto non fosse. –

+1

È inoltre possibile aggiornare i commenti. – jessehouwing

1

Forse questo cambiamento a migliorare le prestazioni, la rimozione del sql nidificato conseguente selezionare nella clausola where

prima ottenere i voti di ciascun utente e metterli in un Dictionary

var userVotes = ctx.Responses.GroupBy(x => x.UserId) 
          .ToDictionary(a => a.Key.UserId, b => b.Count()); 

var cityQuery = ctx.Responses.ToList().Where(x => userVotes[x.UserId] >= VALID_RESPONSES) 
       .GroupBy(x => new { x.User.AwardCity, x.Category.Label, x.ResponseText }) 
       .Select(r => new 
         { 
          City = r.First().User.AwardCity, 
          Category = r.First().Category.Label, 
          Response = r.First().ResponseText, 
          Votes = r.Count() 
         }) 
       .OrderByDescending(r => r.City, r.Category, r.Votes()); 
+0

Ho già provato questo approccio. Ecco l'errore: LINQ to Entities non riconosce il metodo 'Int32 get_Item (Int32)' metodo, e questo metodo non può essere tradotto in un'espressione di archivio –

+0

Penso di essermi perso per aggiungere ToList() dopo ctx.Responses, risultante in ctx.Responses.ToList(). Where (.... –

+0

Penso che il problema principale è che il driver EF LINQ non può usare un riferimento a un dizionario esterno mentre sta costruendo l'SQL dell'entità. e ho incollato letteralmente la tua soluzione, tutto quello che ho fatto è stato correggere la sintassi del primo lambda di ToDictionary. –

Problemi correlati