Właściwy Linq, gdzie klauzule

134

Piszę sporo linq w moim codziennym życiu, ale przeważnie są to proste stwierdzenia. Zauważyłem, że używając klauzul where, istnieje wiele sposobów ich zapisywania i każdy z nich daje takie same wyniki, o ile wiem. Na przykład;

from x in Collection
  where x.Age == 10
  where x.Name == "Fido"
  where x.Fat == true
  select x;

Wydaje się to równoważne przynajmniej w zakresie wyników:

from x in Collection
  where x.Age == 10 &&
        x.Name == "Fido" &&
        x.Fat == true
  select x;

Czy naprawdę istnieje różnica inna niż składnia? Jeśli tak, jaki jest preferowany styl i dlaczego?

AR
źródło

Odpowiedzi:

76

Drugi byłby bardziej wydajny, ponieważ ma tylko jeden predykat do oszacowania dla każdego elementu w kolekcji, gdzie tak jak w pierwszym, najpierw stosuje pierwszy predykat do wszystkich elementów, a wynik (który jest zawężony w tym miejscu) to używany dla drugiego predykatu i tak dalej. Wyniki są zawężane przy każdym przejeździe, ale nadal obejmuje wiele przejazdów.

Również łańcuchowanie (pierwsza metoda) będzie działać tylko wtedy, gdy wykonujesz ORAZ swoje predykaty. Coś takiego x.Age == 10 || x.Fat == truenie zadziała z Twoją pierwszą metodą.

Bala R
źródło
1
Warunki łańcucha ORing są w pewnym stopniu możliwe przy użyciu tego rozszerzenia: albahari.com/nutshell/predicatebuilder.aspx
jahu
143

EDYCJA: LINQ to Objects nie zachowuje się tak, jak się spodziewałem. Być może zainteresuje Cię wpis na blogu , który właśnie o tym napisałem ...


Różnią się tym, co zostanie nazwane - pierwszy jest odpowiednikiem:

Collection.Where(x => x.Age == 10)
          .Where(x => x.Name == "Fido")
          .Where(x => x.Fat == true)

gdzie to ostatnie jest równoważne z:

Collection.Where(x => x.Age == 10 && 
                      x.Name == "Fido" &&
                      x.Fat == true)

Teraz, jaka to faktycznie różnica, zależy od implementacji metody Wherecall. Jeśli jest to dostawca oparty na języku SQL, spodziewałbym się, że w końcu utworzą ten sam SQL. Jeśli znajduje się w LINQ to Objects, druga będzie miała mniej poziomów pośrednich (będą zaangażowane tylko dwie iteratory zamiast czterech). To, czy te poziomy pośrednictwa są istotne z punktu widzenia szybkości, to inna sprawa.

Zwykle whereużyłbym kilku klauzul, jeśli czuję, że reprezentują one znacząco różne warunki (np. Jedna dotyczy jednej części obiektu, a druga jest całkowicie oddzielna) i jednej whereklauzuli, gdy różne warunki są ściśle powiązane (np. Określona wartość jest większe niż minimum i mniejsze niż maksimum). Zasadniczo warto rozważyć czytelność przed jakąkolwiek drobną różnicą wydajności.

Jon Skeet
źródło
1
@JonSkeet Może się mylę, ale po krótkim przeglądzie implementacji Linq Where, nie jestem tego pewien. Zagnieżdżone Where są połączone metodą statyczną „CombinePredicates”. Kolekcja jest iterowana tylko raz przez pojedynczy iterator z połączonym predykatem. Oczywiście połączenie funkcji func ma wpływ na wydajność, ale jest bardzo ograniczony. Nic ci nie jest ?
Cybermaxs
@Cybermaxs: Nie wiesz co dokładnie? Nigdy nie sugerowałem, że kolekcja będzie powtarzana więcej niż raz.
Jon Skeet
@JonSkeet tak, oczywiście, ale na końcu wszystkie predykaty są łączone i tylko jeden iterator jest używany. Spójrz naEnumerable.WhereSelectEnumerableIterator.
Cybermaxs
Strona, do której utworzyłeś łącze, jest teraz niedostępna. Czy możesz zaktualizować link, jeśli artykuł jest nadal dostępny gdzie indziej? Dzięki.
Asad Saeeduddin
2
@Asad: Zaktualizowano. (Mój blog został przeniesiony.)
Jon Skeet
13

Pierwsza z nich zostanie wdrożona:

Collection.Where(x => x.Age == 10)
          .Where(x => x.Name == "Fido") // applied to the result of the previous
          .Where(x => x.Fat == true)    // applied to the result of the previous

W przeciwieństwie do znacznie prostszego (i prawdopodobnie znacznie szybszego):

// all in one fell swoop
Collection.Where(x => x.Age == 10 && x.Name == "Fido" && x.Fat == true)
user7116
źródło
6
„Dużo szybciej”? Nie wiemy jeszcze nawet, która implementacja LINQ jest zaangażowana, więc trudno jest przypisać do niej jakiekolwiek implikacje dotyczące wydajności.
Jon Skeet
W ogólnym przypadku ta ostatnia wymaga tylko 1 pętli. Dostawca może spłaszczyć pierwszy przykład, ale nie jest to wymagane.
user7116
2
Rzeczywiście ... ale twierdzisz, że to drugie jest znacznie szybsze. Nie jest wcale jasne, czy w ogóle będzie znacznie szybsze - w końcu znaczenie różnicy w wydajności będzie zależeć od tego, jak zostanie wykorzystany.
Jon Skeet
1
@Jon: nie ma niezgody. Jak zauważyłeś, rzeczywistość może być taka, że ​​dostawca LINQ idzie i wykonuje przydatne transformacje optymalizacji do wyrażenia. Ale biorąc pod uwagę, że druga wymaga tylko jednej pętli i korzysta z boolowskiego zwarcia, trudno jest zrozumieć, dlaczego nie powinien być oznaczony jako „znacznie szybszy” w kategoriach ogólnych. Jeśli OP ma tylko 5 elementów, moja uwaga jest dyskusyjna.
user7116
11

kiedy biegnę

from c in Customers
where c.CustomerID == 1
where c.CustomerID == 2
where c.CustomerID == 3
select c

i

from c in Customers
where c.CustomerID == 1 &&
c.CustomerID == 2 &&
c.CustomerID == 3
select c customer table in linqpad

w mojej tabeli Customer wyświetla to samo zapytanie sql

-- Region Parameters
DECLARE @p0 Int = 1
DECLARE @p1 Int = 2
DECLARE @p2 Int = 3
-- EndRegion
SELECT [t0].[CustomerID], [t0].[CustomerName]
FROM [Customers] AS [t0]
WHERE ([t0].[CustomerID] = @p0) AND ([t0].[CustomerID] = @p1) AND ([t0].[CustomerID] = @p2)

więc w tłumaczeniu na sql nie ma różnicy i już widzieliście w innych odpowiedziach jak zostaną one przekonwertowane na wyrażenia lambda

Muhammad Adeel Zahid
źródło
ok, to chcesz powiedzieć, że jeśli użyję któregokolwiek z nich, nie będzie to miało żadnego wpływu na wydajność?
Bimal Das
WHERE klauzule są w rzeczywistości połączone. Więc nie ma znaczenia, jak to napiszesz. Nie ma różnicy w wydajności.
hastrb
3

Patrząc pod maskę, te dwie instrukcje zostaną przekształcone w różne reprezentacje zapytań. W zależności od QueryProvidero Collection, to może być zoptymalizowane z dala lub nie.

Gdy jest to wywołanie linq-to-object, wiele klauzul where prowadzi do łańcucha IEnumerables, które odczytują się od siebie. Użycie formularza z jedną klauzulą ​​pomoże w tym przypadku.

Gdy dostawca bazowy przetłumaczy to na instrukcję SQL, istnieje duże prawdopodobieństwo, że oba warianty utworzą tę samą instrukcję.

David Schmitt
źródło