2011-09-30 15 views
23

Proszę zobaczyć ten wiersz kodu. Jest to wywołanie procedury składowanej, która zwraca wartość ObjectResult<long?>. W celu wydobycia długie wartości dodałem Wybierz:.NET Entity Framework - IEnumerable VS. IQueryable

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

podstawie intellisense Wybierz ten zwraca IEnumerable<long>.

Nie jestem pewien, czy go gdzieś przeczytałem, czy po prostu przywykłem do tego założenia - zawsze uważałem, że kiedy EF API zwraca wartość IEnumerable (a nie IQueryable), oznacza to, że wyniki zostały zmaterializowane. To znaczy, że zostały wyciągnięte z bazy danych.

Dowiedziałem się dzisiaj, że się myliłem (a może to błąd?). Ciągle otrzymuję błąd

„Nowa transakcja nie jest dozwolone, ponieważ istnieją inne wątki uruchomione w sesji”

Zasadniczo, ten błąd mówi ci, że jesteś stara, aby zapisać zmiany, podczas gdy Czytnik db wciąż czyta rekordy.

Ostatecznie rozwiązano go (co uważałem za długi strzał) i dodano ToArray() wezwanie do urzeczywistnienia IEnumerable<long> ...

SO - dolnej linii - należy się spodziewać IEnumerable wyniki EF zawierać wynika, że ​​Haven zmaterializował się jeszcze? Jeśli tak, czy istnieje sposób, aby się dowiedzieć, czy zmaterializował się IEnumerable?

Dzięki i przepraszam, jeśli jest to jeden z tych „duhhh” pytania ... :)

+0

Nie jestem pewien, czy jest coś specjalnie udokumentowanego dla EF, ale dla Linq ogólnie rzecz biorąc, 'IEnumerable ' nie jest już "zmaterializowany" niż 'IQueryable ' - oba są zwykle przyjmowane do odroczonego wykonania. –

+0

@Damien_The_Unbeliever: Jednak myślę, ObjectResult będzie zawsze wykonać, gdy funkcja jest wywoływana ... (w tym przypadku FindCoursesWithKeywords) –

Odpowiedz

27

IQueryable jest używany, gdy używasz Linq-do-encji = budujesz deklarację LINQ w swojej aplikacji, które będzie interpretowane przez dostawcę LINQ jako SQL i wykonywane na serwerze. Gdy zapytanie zostanie wykonane (iterowane), zmieni się w IEnumerable, a obiekty zostaną zmaterializowane zgodnie z potrzebą iteracji = nie natychmiast.

Po wywołaniu procedury składowanej nie używa się Linq-to-entity, ponieważ w aplikacji nie ma wbudowanego zapytania deklaratywnego. Kwerenda/SQL już istnieje na serwerze bazy danych i właśnie go wywołujesz. To zwróci IEnumerable, ale znowu nie zmaterializuje wszystkich wyników natychmiast. Wyniki zostaną zmaterializowane w postaci iteracji. Jest to zasada czytnika bazy danych/.NET, gdy jawnie żądasz pobrania obiektu.

Więc jeśli nazwać coś takiego:

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

Jesteś pobierania kursów pojedynczo (btw dlaczego załadować cały kurs, jeśli jesteś zainteresowany tylko słów kluczowych.?). Dopóki nie ukończysz ani nie przerwiesz pętli, twój czytnik danych jest otwarty, aby pobrać rekordy.

Jeśli zamiast nazwać:

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

będzie wymusić zapytania niezwłocznie zmaterializować wszystkie wyniki i pętla wykona na gromadzenie w pamięci zamiast otwartego czytelnika bazy danych.

Różnica między IEnumerable i IQueryable nie jest w sposób w jaki sposób dane są pobierane z powodu IQueryable jest IEnumerable. Różnica polega na konstrukcji wspierającej (coś musi implementować te interfejsy).

+0

Cześć, dziękuję za wyjaśnienie!Ponieważ jest to przechowywany proc, nie zwróci on IQueryable, ponieważ nie obsługuje wszystkich funkcji, które wykonuje zapytanie, prawda? (przy okazji, przysyłam kursy nie zawierające słów kluczowych) :) – justabuzz

10

Praca na IEnumerable<T> oznacza, że ​​wszystkie dalsze operacje nastąpi w kod C#, to znaczy LINQ-obiekty. Nie oznacza to, że zapytanie zostało już wykonane.

Po degradacji do linq-to-object wszystkie dane pozostawione w tym miejscu muszą zostać pobrane z bazy danych i wysłane do .net. Może to drastycznie obniżyć wydajność (na przykład indeksy baz danych nie będą używane przez linq-do-obiektów), ale z drugiej strony linq-to-objects jest bardziej elastyczny, ponieważ może wykonywać dowolny kod C# zamiast być ograniczany przez co Twój dostawca linq może przetłumaczyć na SQL.

A IEnumerable<T> może być zarówno odroczoną kwerendą, jak i już zmaterializowanymi danymi. Standardowe operatory linq zwykle są odroczone, a ToArray()/ToList() są zawsze zmaterializowane.

+0

Dzięki za to wyróżnienie! Czy jest to coś, o czym powinienem wiedzieć? Czy są jakieś implikacje lub gróźb? Twoje zdrowie! – justabuzz

+0

Najbardziej oczywistym problemem jest to, że degeneracja do 'IEnumerable ' może drastycznie zmniejszyć wydajność. Wyobraź sobie, że degradujesz na samym początku zapytania, wtedy najprawdopodobniej cała tabela musi zostać pobrana zamiast filtrowania przy użyciu SQL na serwerze przy użyciu indeksów w bazie danych dla przyspieszenia. Więc w punkcie, w którym ulegasz degradacji do 'IEnumerable ' chcesz pozostać jak najmniej elementów. – CodesInChaos

+0

Nie jestem pewien, czy to prawda. Dalsze czytanie zrobiłem + inna odpowiedź tutaj mówią podobną rzecz - IEnumerable może również nie zmaterializować się do później kiedy iterujesz to. Jeśli rozumiem, że tak jest, to dlatego, że po uruchomieniu przechowywanego procesu otrzymujesz pełne IQueryable, ponieważ nie jest to obiekt pełnoprawny. Ma sens? :) – justabuzz

-5

IEnumerable: LINQ do obiektu i LINQ do XML.

IQueryable: LINQ SQL

+0

IQueryable to typ, który nie został jeszcze wykonany. Zasadniczo jest to "zapytanie". http://msdn.microsoft.com/en-us/library/system.linq.iqueryable(v=vs.100).ASPX Z wirtualizacją nie ma nic wspólnego z LINQ do SQL ani Linq do niczego. Jest to interfejs zapewniający dostęp do dowolnego dostawcy danych. – Tony

0

IEnumerable nie uwodnienia aż do skutku. W przypadku wywoływania procedury składowanej myślę, że nie jest wymagany żaden dodatkowy filtr, tzn. Wysyłam parametry do procedury składowanej w celu wygenerowania żądanego podzbioru zwróconych danych. IEnumerable związany z procedurą przechowywaną jest w porządku. Jednak jeśli pobierasz całą zawartość tabeli, a następnie filtrujesz ją w aplikacji, powinieneś mieć strategię. Podobnie jak nie używaj ToList() IEnumerable of the table, zmaterializujesz wszystkie wiersze. Czasami spowoduje to wyjątek braku pamięci. Poza tym, dlaczego konsumować pamięć bez powodu. Używaj IQueryable w kontekście, w ten sposób możesz filtrować tabelę w źródle danych, a nie w aplikacji. W odpowiedzi musisz ją zmaterializować. IEnumerable to interfejs, który tylko materializuje, "zainicjuje" jego typ i wytworzy coś w moim rozumieniu.

Powiązane problemy