Artykuł został rozbity na wiele części, poniżej spis treści:
I. Budowa zapytania
II. Podstawy analizy drzewa wyrażeń
III. Właściwa analiza drzewa wyrażeń
IV. Pobieranie i zwrot danych
Pobieranie i zwrot danych
W poprzednich częściach omówiliśmy sobie podstawy konstrukcji naszego własnego dostawcy do Linq. Powinnyśmy już wiedzieć, że filary opierają się na implementacji dwóch interfejsów IQueryable oraz IQueryProvider. Ich implementacja rozkłada się w zasadzie na dwie części. Jedna to odpowiednia interpretacja dostarczonego już drzewa wyrażeń pochodzącego z zapytania Linq. Pomysł jak je poprawnie zanalizować i przetworzyć, np. na zapytanie SQL (w ciągu znaków) dałem w poprzedniej części.
Jeżeli założę, że ten etap mamy zrobiony to do kompletu nie pozostaje już nic innego jak poprawny zwrot danych zgodny z oczekiwanym formatem. Tym zagadnieniem zajmę się w tej części.
Do tej pory stworzyliśmy parę klas i ich metod, które miały generyczny parametr. Ten parametr naogół wiązał się typem zwracanego obiektu. W drugiej części tego artykułu przedstawiłem wam tymczasowy fragment kodu:
protected object getResult(string sqlQuery)
{
Customer cust = new Customer();
//tu jeszcze nic nie robimy
//Customer ze względu na bieżacy stan przykładu
//jest tutaj hardcode'owanym typem zwrotnym
return cust;
}
W żaden sposób nie spełnia on warunku, aby zwrócić generyczny typ o jaki go poprosimy w naszym "jakimkolwiek" zapytaniu. W omawianym przykładzie był to Customer, wcale przecież tak nie musi być. Ba.. nawet jeśli operujemy na typie Customer i nasze proste zapytanie:
var result = (from c in q
where c.Age > 10
select c);
Nie wszyscy pewnie wiedzą, ale w świecie Microsoftu istnieje wyraźne rozdzielenie pomiędzy rokiem kalendarzowym (CY) a rokiem fiskalnym (FY). Tym czym operujemy realizując nasze cele, od małych do tych globalnych na które patrzą inwestorzy z Wall Street, zamyka się właśnie w roku fiskalnym.
Lata fiskalne w MS rozpoczynają się 1 lipca danego roku i kończą się ostatniego czerwca roku następnego. Właśnie zakończyliśmy FY08, teraz zaczyna się FY09.
Wiem, że niektórzy z was są świadomi, bo z takich miłych i sympatycznych choć niestandardowych życzeń jakie ostatnio dostałem na maila były życzenia noworoczne z okazji nowego roku fiskalnego :)
W każdym razie FY08 się skończył i Microsoft opublikował swoje wyniki finansowe w zamkniętym już roku. Dostępne są tutaj:
http://www.microsoft.com/msft/earnings/FY08/earn_rel_q4_08.mspx. Rok udało się zamknąć z wynikiem $60.42B, czyli po naszemu 60 miliardów dolarów. Rok wcześniej osiągneliśmy wynik $51B, czyli krótki mówiąc mój pracodawca ciągle rośnie.
Jaki to ma wpływ na mnie i na moją pracę? Bezpośrednio pewnie nijak. Pośrednio tyle, że firma raczej dalej będzie agresywnie inwestować niż ciąć koszta - więc prace pewnie jeszcze też będę miał. Polski Microsoft także bardzo dynamicznie się rozwija, tak jak patrzę po ilości osób w biurze teraz, a ilu nas było w 2005 roku kiedy zaczynałem (i ilu osób kompletnie nie rozpoznaje, kiedy wcześniej miałem przynajmniej mgliste wyobrażenie jak się osoba nazywa, czym zajmuje, albo przynajmniej w którym jest dziale).
W każdym razie, swoje drobniejsze cele, które w jakiś tam sposób mam nadzieję na ten wynik się przekładają też zrealizowałem, co więcej muszę przyznać, że uwielbiam pracę z partnerami ISV w Polsce i mam nadzieję, że to co udało się w tym roku z mojej strony zapoczątkować w następnych latach będę skutecznie rozwijał, aby poziom współpracy także rósł. W tym roku, który się skończył większość swojej energii poświęcałem na to aby przekazywać wiedzę technologiczną partnerom w kontekście konkretnych projektów albo wizji projektów. Dużo z tych inicjatyw w tym momencie po waszej stronie jest cały czas w developmencie. Część już jest dostępna na rynku, albo lada moment będzie, więć jedno z bonusowych założeń jakie sam sobie obiecałem w tym nadchodzącym roku to zadbać o to, aby nie tylko te rozwiązania i aplikacje udało się skończyć i wprowadzić na rynek, ale i żeby były jak najlepiej widoczne.
Napewno będę się też starał pomóc wam w kompatybilności waszych aplikacji z najnowszymi produktami Microsoft jak: Windows Vista, Windows Server 2008 czy Office System. Więc jeśli potrzebujecie takiej pomocy to nie czekajcie aż do was dotrę, skontaktujcie się ze mną pierwsi.
Kolejne z działań jakie planuję na przyszły rok to oczywiście dalsza praca u podstaw w przekazywaniu wiedzy i informacji na temat najnowszych i niedługo nadchodzących technologii i produktów Microsoft.
Po wakacjach będzie MTS (Microsoft Technology Summit) 2008. Będę na tej konferencji, napewno będę opowiadał o platformie Dynamics, z perspektywy dewelopera. Co więcej, nie będę zdradzał. Zapraszam do śledzenia strony konferencji, wiadomości są przekazywane na bieżąco.
Generalnie FY09 zapowiada się bardzo interesująco już z tych informacji, którymi dysponuję w tym momencie. Praktyka zweryfikuje. Na ten moment jednak powoli się szykuję do wakacji i krótkiego odpoczynku na doładowanie baterii.
Od końca sierpnia zaś już coraz bardziej z pełną parą zabiorę się wykonywanie planów na FY09 i codzienną współpracę z partnerami ISV. Howgh!
Artykuł został rozbity na wiele części, poniżej spis treści:
I. Budowa zapytania
II. Podstawy analizy drzewa wyrażeń
III. Właściwa analiza drzewa wyrażeń
IV. Pobieranie i zwrot danych
Właściwa analiza drzewa wyrażeń
W poprzedniej części wprowadziłem temat drzewa wyrażeń zwracanego poprzez uniwersalny typ Expression. Wierzę jednak, że zawarte tam informacje poprzez ich chwilową niekompletność mogą wprowadzić więcej zamieszania niż wyjaśnienia. Ta część powinna umożliwić wam napisanie translatora uwzględniającego chociażby najprostsze scenariusze wykorzystania zapytań Linq.
Nasze status quo na ten moment to stworzona klasa QueryTranslator, której zadaniem jest przetworzenie drzewa wyrażeń na ciąg znaków zgodny z językiem SQL. Aby ułatwić zabawę poprzednio omówioną wersję trochę zrefaktoryzowałem (uwielbiam te kalki językowe J). Aktualny jej stan to:
internal class QueryTranslator<T>
{
QueryExecutionPlan queryExecPlan;
public QueryTranslator()
{ … }
public QueryExecutionPlan Translate(Expression expression)
{
queryExecPlan = new QueryExecutionPlan();
queryExecPlan.ReturnType = typeof(T);
queryExecPlan.QueryType = typeof(Query<T>);
Expression returnExpression =expression;
do
{
returnExpression = analyzeExpressionTree(returnExpression);
} while (returnExpression != null);
return queryExecPlan;
}
private Expression analyzeExpressionTree(Expression expression)
{
if (expression == null)
return null;
Expression ret = null;
switch (expression.NodeType)
{
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.And:
…
case WSZYSTKIE_ExpressionType:
…
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
case ExpressionType.TypeAs:
case ExpressionType.TypeIs:
case ExpressionType.UnaryPlus:
//tu ma być realny kod analizujący wyrażenie
break;
default:
throw new NotImplementedException("QueryExecutionPlan does not support used type of expression: " + expression.NodeType.ToString());
}
return null;
}
}
Aby nie wydłużać powyższego fragmentu kodu wyciąłem komplet przypadków do switcha, pozostawiając tylko parę, aby pokazać wam szerszy obraz. W realnym kodzie są wszystkie i wszystkie w ten czy inny sposób musimy uwzględnić. Patrząc po ExpressionType możemy zobaczyć wszystkie rodzaje zapytań jakie wspiera składnia Linq.
W poprzedniej części obiecałem przyjrzeć się dokładniej, co ExpressionType zawiera i jakie to ma odniesienie do wszelakich klas pochodnych od Expression. Pełny opis wszystkich wyliczeń tego typu jest dostępny na MSDN:
http://msdn.microsoft.com/en-us/library/bb361179.aspx
Konfrontując ich liczbę z liczbą klas dziedziczących po Expression widać, że przy analizie wypadałoby zrobić pewną agregację. Tak dla przykładu patrząc po opisie UnaryExpression na MSDN, ten typ wyrażenia zwracany jest w przypadku ExpressionType o wartościach:
Negate, NegateChecked, Not, Convert, ConvertChecked, ArrayLength, Quote, TypeAs. Przeglądając wszystkie typy Expression i wartości numeratora, jakie wspierają warto powyższego switcha pogrupować tak, aby odpowiednie typy ExpressionType były obsługiwane przez jednego wyspacjelizowanego case’a. Gotowy przykład klasy, która odpowiednio analizuje takie drzewo jest dostępny czy na blogu Matt’a Warrena, czy też w artykule na Code guru napisanym przez Jacka Matulewskiego. Klasa ExpressionVisitor opisana tam zawiera dosyć znaczącą liczbę niezbyt przejrzystego kodu, więc aby umożliwić sobie lekkie wprowadzenie do poprawnej analizy drzewa wyrażeń zachęcam na początek przy prostych przykładach zapytań implementować poszczególne typy ExpressionType jakie lądują, np. w powyższej metodzie analyzeExpressionTree. W przypadku naszego prostego zapytania:
var result = (from c in Customers
where c.Age > 10
select c);
drzewo powinno wyglądać następująco:
Luźno z takiego drzewa można spróbować złożyć „select * from Customers c where c.Age>10”. Gdybyśmy do naszego kodu Linq dodali First(). To zanim wystąpił by nasz Call(Where) wystąpiłby Call(First). W SQL przełożyć to możemy na „select top 1 * from Customers c where c.Age>10”
Gdybym nasze zapytanie zmodyfikował tak, aby nie zwracać całego Customer:
var result = (from c in Customers
where c.Age > 10
select c.FirstName).First();
Wtedy ścieżka wyglądała by tak:
1) Call (First)
2) Call (Select)
3) Call (Where)
.. a dalej już jak na powyższym rysunku.
Poprawna interpretacja MethodCallExpression dla metody „Select” powinna nas doprowadzić do oczywistej interpretacji czyli: „select c.FirstName from customers c where c.Age>10”
Przekładanie powyższego drzewa na ciąg znaków z kodem SQL najlepiej przetwarzać na początku przez StringBuilder (tak jak to ma miejsce w przypadku ExpressionVisitor).
Ja powyżej dałem początek własnemu obiektowi przejściowemu QueryExecutionPlan, który ma mi być pomocny przy przekładaniu mojego Linq na dedykowana składnię SQL dla SQL Servera (T-SQL), Oracle'a (PL-SQL) czy też zestaw poleceń do wykonania dla własnej prostej bazy plikowej. Tutaj trochę zdradzam finał, który mam nadzieję niedługo opublikować czyli taki dostawca do Linq, który poprzez prostą konfigurację umożliwia komunikację z różnym fizycznym źródłem danych (poprzez wspólny metamodel).
Czyli krótko mówiąc idziemy powoli po bebechach w stronę EntityFramework (aloha Syzyfie, wymyślmy kolejne koło :>)
Jakby, ktoś chciał sobie przyspieszyć to jak wspomniałem przykłady alternatywne przykłady są dostępne w sieci. Na początku jednak polecam taką lekką drogę przez mekę aby zobaczyć co kryje się w pierwszym Expression jakie przekazujemy i jak dalej to wygląda gdy zagłebimy się w osadzone podwyrażenia.
To się może przydać przy optymalizacji generowanych zapytań jako odpowiedź dla co poniektórych, którzy ze składni LINQ są zadowoleni, ale może nie do końca z generowanych przez nie SQLi. UnitTesting przy takim projekcie moim zdaniem to jedno wielkie MUST-BE.
Podsumowując: jeśli założymy, że powyższe już mamy zrobione to jedyne co pozostaje to wykonać polecenie, odebrać wynik i zformatować go zgodnie z typem jaki zarządaliśmy w zapytaniu Linq. To już ponownie pozwolę sobie przesunąć na kolejną część.
Nie wiem czy wiecie, ale za oceanem trwa teraz E3 2008 czyli Electronic Entertainment Expo 2008. To chyba największa konferencja w branży gdzie marketing dostawców platform (konsol) oraz wydawców gier puchnie w szwach od coraz to nowszych zapowiedzi, kontrolowanych przecieków i tak dalej. Dziś konferencja prasowa Microsoftu i od razu siejemy zamęt i zniszczenie, a na górze wisienka. :)
Wiele z intrygujących filmików znajdziecie na stronie www.gametrailers.com. Rozsądny komentarz na bieżąco też publikują koledzy z Polygamii. Jest nieźle. Z tego co sam na ten moment zdąrzyłem zobaczyć to:
* krótki filmik z gameplayem do Fallout 3, po którym jeszcze bardziej zapragnąłem tej gry.
* robiący wrażenie cooperative w Resident Evil 5 (po Xbox Live)
* Project Origin, który na mnie akurat wielkiego wrażenia nie zrobił, ale może dla twardych fanów F.E.A.R będzie to gratka.
* Więcej informacji na temat Lord of the Rings:Conquest, do którego sam jestem nastawiony z pewnym dystansem. Filozofia Starwars:Battlefront w świecie Tolkiena. Zabawa z mordowanie hobbitów grając siłami Saurona. No nie wiem.. Zawsze wolałem grać Aragorna i bronić bosostopych :)
* Do tego jeszcze nie dotarłem, ale Polygamia już rozkminiła, że Final Fantasy XIII będzie na Xbox'a! Ten nius dla coniektórych powinien być jednym z najbardziej wgniatających. To zbicie solidnego argumentu dla Sony. Zresztą poczytajcie komentarze na Polygamii. Wieje grobową atmosferą i zabójczym milczeniem od miłośników Sonyi.
* Fable 2 - gra wg Petera Molynuex już jest skończona. Zapowiada się ciekawie zwłaszcza gdy do rozgrywki zachęca sam Peter Molyneux. Jeden z magików sceny prezenterskiej w branży elektronicznej rozrywki.
* Lego:Batman - no po prostu chyba już mnie żadne Lego w wersji gry video nie zdziwi.
* Hydrophobia - tutaj nie mam pojęcia co to będzie, ale krótki filmik pokazuje, że twórcy gry odpowiadają na techniczne wyzwanie twórcom Bioshock jeżeli chodzi o efekty związane z wodą w grze (od caustic lighting po przez pixel/vertex shadery na powierzchni.. no kurcze trochę się tam dzieje przez te parę sekund). Pytanie tylko jak się w to będzie grało.
* XBox 360 będzie miał nowy dashboard (pełna historia na 1up.com http://www.1up.com/do/newsStory?cId=3168709). Oto zrzut ekranu:
Trochę mi to Wii przypomina na tyle na ile zapamiętałem z salonu znajomego. Zobaczymy :)
Cały czas się coś dzieje. Konferencja prasowa Microsoftu rozpoczęła się o 10:30AM PST czyli de facto 3h temu w stosunku do tego co teraz piszę.
Informacje na bieżąco na pewno znajdziecie tutaj: http://e3expo.com/.
Heheh.. no to dzisiaj chyba pospałem :>
Artykuł został rozbity na wiele części, poniżej spis treści:
I. Budowa zapytania
II. Podstawy analizy drzewa wyrażeń
III. Właściwa analiza drzewa wyrażeń
IV. Pobieranie i zwrot danych
Podstawy analizy drzewa wyrażeń
Kontynuując zabawę w własnego dostawcę do Linq chciałem rozwinąć kwestię analizy drzewa wyrażeń i tłumaczenia go na konkretne polecenia (np. SQL). W poprzedniej części wspomniałem o jednej z metod interfejsu IQueryProvider, czyli o BasicQueryProvider.Execute oraz BasicQueryProvider.Execute<T> w naszej implementacji dostawcy.
Stworzony już prosty test rozbudujemy o próbę realnego wykonania zapytania, chociażby poprzez odwołanie do pierwszego znalezionego rekordu:
Query<Customer> q = new Query<Customer>();
var result = (from c in q
where c.Age > 10
select c).First();
Console.WriteLine(result.ToString());
Śledząc wywołania w debugerze (lub rozpoczętą praktyką wyjątków) odkryjemy, że następnymi metodami do poprawnej implementacji są właśnie dwie odmiany Execute().
Dla ułatwienia na ten moment wersję z szablonem zrobimy tak:
public TRes Execute<TRes>(Expression expression)
{
return (TRes)this.Execute(expression);
}
I skupimy się na tej drugiej. Nim ją jednak rozdmucham przyjrzyjmy się parametrowi, czyli klasie Expression. Obiekt tej klasy wydaje się mieć niewiele właściwości jak na drzewo wyrażeń:
public abstract class Expression
{
protected Expression(ExpressionType nodeType, Type type);
public ExpressionType NodeType { get; }
public Type Type { get; }
//dużo statycznych metod, o których za moment
}
Definicja klasy zawiera o wiele więcej elementów statycznych pomocnych w konstrukcji pełnego drzewa. Nie będę wymieniał wszystkich (parę kliknięć w Visual Studio zrobi to za mnie lepiej) dla przykładu tylko nadmienię parę tak, aby pokazać wam, czego można się spodziewać:
public static BinaryExpression Add(Expression left, Expression right);
public static BinaryExpression And(Expression left, Expression right);
public static MemberAssignment Bind(MemberInfo member, Expression expression);
public static MethodCallExpression Call(Expression instance, MethodInfo method);
Bez tłumaczenia za bardzo, o co chodzi w powyższym można się domyśleć, że z pełną pulą tych metod można sobie spokojnie zagnieździć całkiem skomplikowane drzewo wyrażeń reprezentujące nasze zapytanie. Problem tylko gdzie ta informacja jest skoro obiekt klasy Expression posiada tak niewiele.
Odpo