W ostatnim wpisie z tej serii chciałem przedstawić najważniejszą cechę linq, mianowicie niezależność od źródła danych.
Jak pisałem wcześniej Linq jest połączeniem 5 elementów:
- extension methods
- yield
- Func, Predicate, Action
- wyrażenia lambda
- IQueryable i Expression Trees
We wszystkich poprzednich wpisach pracowaliśmy na kolekcjach, które przechowywane były w pamięci, jednak linq ma dużo większe możliwości obsługuje przeróżne źródła danych. Jak? Zacznijmy od przykładu. Załóżmy, że mamy bazę danych o takim prostym, schemacie.
Jak widać w bazie mamy 4 proste tabelki, i chcemy przechowywać informację o klientach, zamówieniach i pozycjach na nich oraz o towarach. W tym przykładzie, skorzystam z Entity Framowrka, który umożliwia nam w łatwy sposób odczytanie danych z bazy. Załóżmy teraz, że chcemy wyświetlić wszystkich klientów, których imiona zaczynają się na R.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Program { private static void Main(string[] args) { using (var context = new LinqEntities()) { foreach (var client in context.Clients.Where(client => client.Name.StartsWith("R"))) { Console.WriteLine(client.Id); } } } } |
Wygląda to właściwie tak samo jak, wtedy gdy odwoływaliśmy się do obiektów w będących przechowywanych w pamięci a nie w bazie danych. Metoda where wygląda tak jakbyśmy mogli ją zastosować do zwykłej listy obiektów. To jest właśnie największa zaleta linq, niezależnie od źródła danych linq udostępnia nam te same metody operacji na danych. Ok, ale przecież aby uzyskać dane z bazy należy napisać zapytanie SQL. W tym przypadku mogło by to być takie zapytanie.
1 2 3 4 |
SELECT Id, Name, Taxid from Clients where Name LIKE 'R%' |
Linq musi, więc jakoś zamienić kod z poprzedniego przykładu na takie właśnie zapytanie, ale jak to właściwie wygląda? Zobaczmy co nam podpowiada intellisense visual studio gdy klikniemy w metodę Where.
Okazuje się, że nie działamy już na interfejsie IEnumerable tylko IQueryable oraz nie przekazujemy już Func tylko Expression<Func> chociaż na pierwszy rzut, oka wszystko wygląda podobnie.
Jaka jest różnica pomiędzy IEnumerable a IQueryable? Taka, że korzystając z pierwszego interfejsu działamy na obiektach w pamięci operacyjnej, a w drugim przypadku obiekty przechowywane są gdzieś w ‘jakimś’ źródle danych.
Co oznacza w takim razie tak groźnie wyglądająca deklaracja parametru Expression<Func<Clients,bool>>
?
Linq, aby przetłumaczyć kod zawarty w metodzie where na (w tym przypadku) zapytanie sql, korzysta z tak zwanych drzew wyrażeń (expression trees). Parametr w metodzie where to właśnie element, takiego drzewa. Spróbuję przedstawić idę tego na prostym przykładzie. Spróbujmy zapisać te same delegaty raz za pomocą Expression a raz za pomocą Func i zobaczmy jaka jest różnica.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Program { private static void Main(string[] args) { Func<int, bool> isLessThanFive = i => i < 5; Expression<Func<int, bool>> isLessThanFiveExp = i => i < 5; Console.WriteLine(isLessThanFive(3)); Console.WriteLine(isLessThanFive.ToString()); Console.WriteLine(isLessThanFiveExp.Compile()(3)); Console.WriteLine(isLessThanFiveExp.ToString()); } } |
Oraz wynik działania tego programu.
Okazuje się, że wyrażenia zapisanego do Expression nie da się bezpośrednio wywołać należy najpierw wywołać metodę Compile. Gdy wypiszemy, takie wyrażenie na konsoli okazuje się, że wyświetli się nam kod wyrażenie lambda, która zapisaliśmy w zmiennej. Expression można więc rozumieć jako sposób zapisania delegata i służy on do tego aby takiego delegata zamienić w naszym przypadku na część kwerendy SQL.
Aby do źródła danych można było się odwołać korzystając z linq, musi zostać zaimplementowany interfejs IQueryable i IQueryProvider tylko gdzie? W naszym przypadku zapewnia to wszystko Entity Framework. Właśnie ten drugi interfejs jest pewnego rodzaju tłumaczem potrafi on przetłumaczyć to co zapisaliśmy w Expression na zapytanie SQL.
Podsumowując, linq jest abstrakcją nad źródłem danych czyli zbiorem metod, które możemy wykonywać niezależnie od tego czy odwołujemy się do relacyjnej bazy danych, bazy NoSQL, pliku XML czy WebServisu o ile oczywiście, ktoś był tak miły i dostarczył implementację IQueryable i IQueryProvider, aby to wszystko było możliwe;)
PS. Bazę wykorzystam w kolejnych wpisach…
2 Komentarze
mariusz
29 sierpnia 2013 na 07:40 (UTC 2) Link do tego komentarza
temat ciekawy, ale – niestety – ledwie “liźnięty”
graf
29 sierpnia 2013 na 08:41 (UTC 2) Link do tego komentarza
Zgadza się, na ten temat można pisać długo, a moim celem było zwięzłe pokazanie do czego służą te mechanizmy. Tak, aby ktoś kto nigdy nie używał linq miał pojęcie co siedzi w środku a nie koniecznie był w stanie napisać własnego prowidera. Jeśli jednak ktoś chce napisać własnego prowidera do linq polecam serie wpisów serie wpisów