2013-08-08 16 views
5

Se chiamo lo GetFoo().Count() da un metodo esterno alla mia classe DB, Entity Framework 5 esegue una query SELECT piuttosto inefficiente anziché un COUNT. Dalla lettura di alcune altre domande come this, vedo che questo è il comportamento previsto.Framework Entity - COUNT anziché SELECT

public IEnumerable<DbItems> GetFoo() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")); 
} 

Ho quindi aggiunto un metodo di conteggio per la mia classe DB, che esegue correttamente una query COUNT:

public int GetFooCount() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")).Count(); 
} 

di salvarmi da specificare query più volte, mi piacerebbe cambiare questo al seguente Tuttavia, questo esegue nuovamente un SELECT, anche se è all'interno della classe DB. Perché è questo - e come posso evitarlo?

public int GetFooCount() 
{ 
    return this.GetFoo().Count(); 
} 
+1

Context.Items.Count (d => d.Foo == "bar"); non ti serve la chiamata Where :) –

risposta

14

dal GetFoo() restituisce un IEnumerable<DbItems>, la query viene eseguita come SELECT, allora Count viene applicato alla raccolta di oggetti e non si prevede che l'SQL.

Una possibilità restituisce un IQueryable<DbItems> invece:

public IQueryable<DbItems> GetFoo() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")); 
} 

Ma che potrebbe cambiare il comportamento di altri chiamanti che si aspettano il colection da caricare (con IQueryable sarà pigro-caricato). In particolare, metodi che aggiungono le chiamate .Where che non possono essere convertite in SQL.Sfortunatamente non lo saprai al momento della compilazione, quindi saranno necessari test approfonditi.

vorrei invece creare un nuovo metodo diche restituisce un IQueryable:

public IQueryable<DbItems> GetFooQuery() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")); 
} 

in modo che le usi esistenti non sono interessati. Se si voleva riutilizzare quel codice si potrebbe cambiare GetFoo a:

public IEnumerable<DbItems> GetFoo() 
{ 
    return GetFooQuery().AsEnumerable(); 
} 
13

Per capire questo comportamento è necessario comprendere differenza tra IEnumerable<T> e IQueryable<T> estensioni. Il primo funziona con Linq to Objects, che è una query in memoria. Queste query non sono tradotte in SQL, perché questo è semplice codice .NET. Quindi, se avete qualche valore IEnumerable<T>, e si sta eseguendo Count() questo richiama il metodo Enumerable.Count estensione, che è qualcosa di simile:

public static int Count<TSource>(this IEnumerable<TSource> source) 
{ 
    int num = 0; 
    foreach(var item in source) 
     num++; 

    return num; 
} 

Ma c'è storia completamente diversa con IQueryable<T> estensioni. Questi metodi sono tradotti dal provider LINQ sottostante (EF nel tuo caso) a qualcosa di diverso dal codice .NET. Per esempio. a SQL. E questa traduzione si verifica quando si esegue una query. Viene analizzata tutta la query e viene generato SQL piacevole (beh, non sempre bello). Questo SQL viene eseguito nel database e il risultato viene restituito come risultato dell'esecuzione della query.

Quindi, il metodo restituisce IEnumerable<T> - ciò significa che si sta utilizzando il metodo Enumerable.Count() che dovrebbe essere eseguito in memoria. Così seguente interrogazione è tradotto da EF in SQL

context.Items.Where(d => d.Foo.equals("bar")) // translated into SELECT WHERE 

eseguita, e quindi conteggio di elementi calcolati in memoria con il metodo di cui sopra. Ma se si vuole cambiare il tipo di ritorno a IQueryable<T>, allora tutto cambia

public IQueryable<DbItems> GetFoo() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")); 
} 

Ora Queryable<T>.Count() viene eseguito. Ciò significa che la query continua a essere completata (beh, in realtà Count() è l'operatore che forza l'esecuzione della query, ma Count() diventa parte di questa query). E EF traduce

context.Items.Where(d => d.Foo.equals("bar")).Count() 

in query SQL che viene eseguita sul lato server.

+0

Ciao! Ho provato a fare scherzi con. AsQueryable(). Count() e .AsEnumerable(). Count() e le statistiche di performance che sto ottenendo sono le stesse. Es. - In MySql Workbench "Seleziona numero (*) da mele" ~ 1 ms - In EF linq "dbContext.apples.Count()" ~ 800 ms – Ross

+1

@Ross Penso che dovresti creare una nuova domanda con tutte le informazioni richieste. Per esempio. se è la tua prima query, EF impiega del tempo per edm –