2011-09-30 21 views
23

Vedere questa riga di codice. Questa è un'invocazione di una stored procedure, che restituisce un ObjectResult<long?>. Al fine di estrarre i valori lunghi ho aggiunto il Select:.NET Entity Framework - IEnumerable VS. IQueryable

dbContext.FindCoursesWithKeywords(keywords).Select(l => l.Value); 

Sulla base di questo intellisense SELECT restituisce IEnumerable<long>.

Non sono sicuro se l'ho letto da qualche parte o forse mi sono abituato a questa ipotesi - ho sempre pensato che quando l'API EF restituisce un IEnumerable (e non IQueryable) significa che i risultati sono stati materializzati. Significa che sono stati estratti dal database.

Ho scoperto oggi che ho sbagliato (o forse è un bug?). Ho continuato a ottenere l'errore

"nuova transazione non è consentito, perché ci sono altri thread esecuzione nella sessione"

In sostanza, questo errore si dice che si sta cercando di salvare le modifiche, mentre la lettore db sta ancora leggendo i record.

Alla fine ho risolto da (quello che ho considerato un colpo lungo) e ha aggiunto ToArray() chiamata a materializzarsi il ...

Così IEnumerable<long> - la linea di fondo - devo aspettarmi IEnumerable risultati da EF per contenere i risultati che porto si è ancora materializzato? Se sì, c'è un modo per sapere se uno IEnumerable è stato materializzato o no?

Grazie e scusa se questa è una di quelle domande 'duhhh' ... :)

+0

non sono sicuro se c'è qualcosa specificamente documentata per EF, ma per Linq in generale, un '' IEnumerable non è altro "materializzato" che un '' IQueryable - entrambi sono generalmente accettato da usare esecuzione differita. –

+0

@Damien_The_Unbeliever: Tuttavia, penso che ObjectResult verrà sempre eseguito quando viene chiamata la funzione ... (in questo caso FindCoursesWithKeywords) –

risposta

27

IQueryable viene utilizzato quando si utilizza Linq-to-entities = si sta creando query LINQ dichiarativa nell'applicazione che verrà interpretata dal provider LINQ come SQL ed eseguita sul server. Una volta che la query è stata eseguita (iterata) diventerà IEnumerable e gli oggetti verranno materializzati come necessario per iterazione = non immediatamente.

Una volta che si chiama stored procedure, non si sta utilizzando Linq-to-entities perché nella domanda non è stata creata alcuna query dichiarativa. La query/SQL esiste già sul server di database e la stai solo invocando. Questo restituirà IEnumerable ma di nuovo non materializzerà immediatamente tutti i risultati. I risultati verranno materializzati come iterati. Questo è il principio del cursore del database/o del lettore di dati .NET quando si chiede esplicitamente di prelevare un oggetto.

Quindi, se si chiama qualcosa di simile:

foreach (var keyword in dbContext.FindCoursesWithKeywords(keywords) 
           .Select(l => l.Value)) 
{ 
    ... 
} 

stai recuperando corsi uno per uno (btw perché per caricare tutto il corso, se siete interessati solo a parole chiave.?). Fino a quando non completi o interrompi il ciclo, il tuo lettore di dati viene aperto per recuperare i record.

Se invece chiama questo:

foreach (var keyword in dbContext.FindCoursesWithKeywords(keywords) 
           .ToList() // or ToArray 
           .Select(l => l.Value)) 
{ 
    ... 
} 

Sarà forzare query per materializzarsi immediatamente tutti i risultati e Loop si esibiranno per la raccolta in memoria invece di lettore di database aperto.

Differenza tra IEnumerable e IQueryable non è nel modo in cui i dati vengono prelevati perché IQueryable è IEnumerable. La differenza sta nel backing construct (qualcosa deve implementare queste interfacce).

+0

Ciao, grazie per questa spiegazione!Quindi, poiché questo è un processo memorizzato, non restituirà un IQueryable, in quanto non supporta tutte le funzionalità di una query, giusto? (btw, sto recuperando corsi non parole chiave :) :) – justabuzz

10

Lavorare su un IEnumerable<T> significa che tutte le ulteriori operazioni che accadrà in codice C#, cioè LINQ-to-oggetti. Ciò non significa che la query sia già stata eseguita.

Una volta degradato a linq-to-objects, tutti i dati rimasti a questo punto devono essere recuperati dal database e inviati a .net. Questo può degradare drasticamente le prestazioni (ad esempio, gli indici di database non saranno usati da linq-to-objects), ma d'altra parte linq-to-objects è più flessibile, poiché può eseguire codice C# arbitrario invece di essere limitato da cosa il tuo provider linq può tradurre in SQL.

A IEnumerable<T> può essere una query differita o dati già materializzati. Gli operatori standard di linq in genere vengono differiti e /ToList() sono sempre materializzati.

+0

Grazie per questa distinzione! Quindi è qualcosa di cui dovrei essere a conoscenza? Ci sono implicazioni o trucchi? Saluti! – justabuzz

+0

Il trucco più ovvio è che la degenerazione in "IEnumerable " può ridurre drasticamente le prestazioni. Immagina di degradarti all'inizio della query, quindi è probabile che l'intera tabella debba essere recuperata invece di filtrare usando SQL sul server usando gli indici sul database per accelerare. Quindi nel punto in cui si degrada a "IEnumerable " si desidera mantenere il minor numero possibile di elementi. – CodesInChaos

+0

Non sono sicuro che sia corretto. Ulteriore lettura ho fatto + l'altra risposta qui dire una cosa simile - IEnumerable potrebbe anche non materializzarsi fino a più tardi quando lo si itera. Se capisco che questo è giusto perché quando si esegue un processo memorizzato si ottiene un IQueryable completo, perché questo non è un oggetto interrogabile completo. Ha senso? :) – justabuzz

-5

IEnumerable: LINQ to Object e LINQ to XML.

IQueryable: LINQ to SQL

+0

IQueryable è un tipo che non è stato ancora eseguito. Fondamentalmente è la "query". http://msdn.microsoft.com/en-us/library/system.linq.iqueryable(v=vs.100).ASPX Virutally non ha nulla a che fare con LINQ to SQL o Linq su Anything. È un'interfaccia per fornire l'accesso a qualsiasi tipo di fornitore di dati. – Tony

0

IEnumerable non sarà idratare fino materializzato. Se si chiama una stored procedure, penserei che non sia necessario alcun ulteriore filtro, voglio dire che si inviano parametri a una stored procedure per produrre il sottoinsieme desiderato di dati restituiti. IEnumerable legato a una stored procedure va bene. Tuttavia, se stai recuperando l'intero contenuto di una tabella, filtrando nell'applicazione dovresti avere una strategia. Come, non ToList() the IEnumerable della tabella, si materializzeranno tutte le righe. A volte ciò causerà un'eccezione di memoria insufficiente. Inoltre perché consumare memoria senza ragione. Utilizza IQueryable rispetto al contesto, in questo modo puoi filtrare la tabella nell'origine dati e non nell'applicazione. In risposta, devi materializzarlo. IEnumerable è un'interfaccia, solo materializzandola "inizializza" il suo tipo e produce qualcosa nella mia comprensione.