niedziela, 26 października 2008

LINQ Decomposed

LINQ (Language Integrated Query), to bardzo przydatne rozszerzenie języków z platformy .NET o "generyczne operacje na kolekcjach". Chodzi mniej więcej o to, że wprowadzono do języka programowania elementy języka zapytań (formuły LINQ). Zamiast jes skrupulatnie opisywać odsyłam w tym momencie do msdn:linq.

Wygląda całkiem fajnie - proste operacja agregowania, selekcji i projekcji w samym języku i to całkiem elegancko, szybko i jeszcze type-safe - no tak przecież powinno być już dawno.
Na tym niespodzianki się jednak nie kończą, okazuje się bowiem, że jest to również pewien rodzaj vendor-dependent middleware! Formuła LINQ może zostać przetłumaczona na zapytanie do bazy danych. Jak to możliwe? Scenariusz mamy podzielony na dwie części:

Cz.1
Sama formuła LINQ w czasie kompilacji jest tłumaczona na natywne zapytanie, czyli zapytanie zadane obiektem (coś na kształt db4o:tutorial). W trakcie kompilacji formuły zostaną przepisane mniej więcej w ten sposób.


Dim formula_query = From emp In db_context.Employees _
Where emp.Country = "USA" _
Select emp.FirstName, emp.LastName

Dim native_query = db_context.Employees. _
Where(Function(empl) empl.Country = "USA"). _
Select(Function(empl) New With {.FirstName = empl.FirstName, .LastName = empl.LastName})


Obiektem zapytania jest w przypadku pracy z samymi kolekcjami jest IEnumerable a w przypadku źródła danych jej specjalizacji IQueryable (a właściwie specjalizacji IQueryable, dostarczonej przez dostawce sterownika)

Cz. 2
Zapytanie jest realizowane leniwie czyli dopiero wtedy gdy poprosimy obiekt zapytania o enumerator dzieje się cała akcja, która opiera się na mechanizmie przepisywania drzew składniowych. W trakcie wykonania silnik dostawcy dostaje AST, który sobie jakoś tam analizuje i to co może zrobić to np. wygenerować zapytanie sql w postaci stringa i posłać go do bazy, odczytać a potem utworzyć kolekcję tych obiektów i zwrócić programiście (no ale oczywiście może o wiele wiele więcej). Napisałem prosty programik na podstawe tego blog:msdn:linq (copy-paste programming), który zamiast robić cokolwiek, to po prostu wypisuje info o każdym węźle. Troche bardziej user-friendly wyjaśnienie jest tutaj codeguru.


var q = from el in context.Data where el +1 > 5 select el;


Call Data.Where(el => ((el + 1) > 5))
Constant Data
Quote el => ((el + 1) > 5)
Lambda el => ((el + 1) > 5)
GreaterThan ((el + 1) > 5)
Add (el + 1)
Parameter el
Constant 1
Constant 5


Pare przykładów.

Dim q = (From order In db_context.Orders Join detail In db_context.Order_Details _
On detail.OrderID Equals order.OrderID _
Select order.ShipName, detail.Quantity).Take(10)

przepisane jest na zapytanie:

SELECT TOP (10) [t0].[ShipName], [t1].[Quantity]
FROM [dbo].[Orders] AS [t0]
INNER JOIN [dbo].[Order Details] AS [t1] ON [t0].[OrderID] = [t1].[OrderID]

późne wiązanie

Dim q = From el In db_context.Employees _
Select el.FirstName, el.LastName, el.Country, el.City
q = From el In q Where el.Country = "USA"
q = From el In q Where el.City = "Seattle"

przepisane jest na zapytanie:

SELECT [t0].[FirstName], [t0].[LastName], [t0].[Country], [t0].[City]
FROM [dbo].[Employees] AS [t0]
WHERE ([t0].[City] = @p0) AND ([t0].[Country] = @p1)

Warto zauważyć, że w przypadku pracy z kolekcjami silnik jest już w samym standardzie LINQ i zachowuje się dosyć inteligentnie wykorzystując yield. Możemy sobie zdefiniować mniej lub bardziej skomplikowaną "transformacje" (tak to nazwijmy) kolekcji w postaci obiektu, a wykonanie jej nie będzie tworzyło stosów pośrednich podczas obróbki, co znaczy tyle, żedla każdego elementu wykona wszystkie kroki transformacji od początku do końca (można powiedzieć, że tworzymy wrażenie składania funkcji i foldowania.

Jeszcze na koniec pare wniosków i faktów:
  • Wygląda to wszystko całkiem fajnie, bo dostajemy bardzo użyteczne rozszerzenie języka programowania. Osobiście bardzo często to wykorzystuje i co ciekawsze nie jako middleware, ale właśnie do pracy z kolekcjami i wcześniej gdy naiwnie pisałem jakieś tam zapętlające się pętle tak teraz dostaje bardzo czytelny i efektywny kod.
  • LINQ nie jest córą Microsoftu, tylko urodziła się jako koncepcja akademicka, która przechodzi tez do innych języków ruby:rinq, php, js:jslinq, i nawet rozaważa się aby do JAVA wstawić jakieś LINQ, zwłaszcza gdy mają zostać dodane domknięcia (closures) w JAVA 1.7
  • Zarzutem dla LINQ jest kiepska wydajność. Jako, że zapytanie jest generowanie w czasie wykonania w istocie robi to pewien narzut, który z powodzeniem można było zrobić w czasie projektowania, no ale ze swego doświadczenia mogę stwierdzić, że dopóki nie będzie się uzywało formuły linq w pętli nie stanowi to aż tak dużego narzutu
  • Koncepcja linq doskonale nadaje się do rozszerzenia jej do PLINQ (Parallel LINQ) o tym będzie później
  • Microsoft zrobił coś co robił już bardzo często, czyli stworzył naprawdę ładny kawałek oprogramowania, a potem zaprezentował jego jeden mały fragment. Warto zauważyć , że przecież w czasie wykonania dostajemy AST pewnych struktur języka, możemy je analizować i jeszcze dodatkowo mieć wypływ na to co wypluwają - no to przecież nie trywialny mechanizm, a jak wiemy może być całkiem użyteczny również.

Brak komentarzy: