Detaljni pregled LINQ – Integrirani SQL upiti u .NET programskom jeziku


LINQ ili Language Integrated Query je sastavni dio  .NET programskog jezika prilagođen da izvršava upit nad bilo kojom vrstom izvora podataka na način kako se to radi sa SQL jezikom. LINQ čini skup operatora koji po mnogo čemu sliče SQL komandama. Pomoću LINQ operatora mnogostuko se skraćuje vrijeme sortiranja, grupiranja, filtriranja podataka koji se dosada morao implementirati ili koristiti biblioteke trećih lica. LINQ koristimo kako za memorijske rtko i za eksterne izvore podataka poput DB i XML. Skraćenica LINQ najbolje bi se mogla prevesti kao SQL osobine integrirane u programski jezik.

Uvod

U Posljednjih 20 godina razvoj programskih jezika došao je do stadija kada posjedujemo moćne objektno orijentisane programske jezike poput C++, C# i JAVA. Programeri, koristeći ove jezike danas koriste sve blagodati OOP poput klasa, objekata, nasljeđivanja, polimorfizma, iskorištavanje postojećeg koda. Možemo kazati da je era OOP  došla do svog vrhunca, uz prethodno programeri danas koriste programske jezike koji imaju sposobnost oslobađanja i kontrole curenja memorije u programskim jezicima. U posljednjih nekoliko godina postavlja se pitanje šta dalje i u kom pravcu razvijati programske jezike, na način da pomažu programerima u razvijanju softvera.

Jedan pravac u kojem se programski jezici razvijaju, a koji je posljednjih nekoliko godina sve više zastupljen je razvijanje IDE alata za automatsko generiranje koda tzv. Rapid Application Developmen (RAD).  Danas kad pogledamo softvere za razvoj aplikacija vidimo mnogo ugrađenih alata za generiranje raznih, uglavnom ponavljajući sekvenci izvornog koda. Danas se aplikacije razvijaju na način da imate osjećaj kako radite u nekom od klasičnih softvera za crtanje: povlačite stavke iz kutija sa alatima, dijagramski pišete klase i dodajete metode, članove, nasljeđivate klase itd.

Sagledavajući današnju tehnologiju i pravac u kome se razvija buduća tehnologija, svakako je smanjivanje kompleksnosti pristupa i integracije informacijama koja nije prirodno povezana se OPP. Naime, svaka aplikacije manipuliše određenim podacima koji su po prirodi stvari odvojeni od same aplikacije. Podaci kojim manipulišemo dolaze uglavnom iz dva glavna izvora, a to su relacijske baze podataka i XML.

Manipulacija sa podacima koji dolaze iz ova dva izvora programera više stvara svestranijim nego stručnijim, jer stvara uslov poznavanja drugih tehnologija osim primarne tehnologije razvoja aplikacija u određenom programskom jeziku. LINQ projekat se bazira na problemima manipulacije podataka prethodno opisanih.  Engleski izgovor ove kratice izgovara se kratko LINK.

LINQ kako i sam naziv govori je sastavni dio primarnog programskog jezika, koji sa sastoji od standardnih operatora upita  za manipulaciju, projekciju i filtriranje sa podacima na način sličan kako se to radi pomoću SQL. Izvor podataka nad kojim se vrše upiti ne ograničava se samo na eksterne izvore podataka, nego na generalno svaki izvor podataka kako eksterni tako i interni – aplikacijski izvor podataka. Ovo omogućuje da programer ostaje u svom primarnom programskom jeziku dok manipuliše podacima iz izvora podataka.

LINQ operatori mogu se primjeniti na svaki izvor podataka koji je izveden iz IEnumerable interfejsa. Ovo omogućava razvijanje komponenti trećih strana baziranih na LINQ-u preklapanjem standardnih operatora. Do sada takvih implementacija je urađeno, a najpoznatiji su LINQ to Amazon, LINQ  to Google i td. Važno je spomenuti da se na Open Source zajednicama poput Source Forge pokrenuto više desitina sličnih projekata.

Upoznavanje sa LINQ

Najbolji način kako se približiti LINQ je da se napiše jednostavan primjer napisan u izvornom kodu.

class Program
{
static void Main(string[] args)
{
   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };

//Konstrukcija LINQ upita
IEnumerable izraz = from g in gradovi where g.Length == 5 orderby g select g.ToUpper();

//izvršavanje upita na poljem stringova
foreach (string grad in izraz)
Console.WriteLine(grad);

}
}

Ako se ovaj program pokrene pritiskom na F5, dobijamo rezultat kao na sljedećoj slici.

image

Kao što smo naglasili svaki tip koji je izveden iz IEnumerable interfejsa podržava LINQ operatore. U našem primjeru koristili smo 3 standardna operatora upita: select, where, orderby. Isti primjer možemo napisati bez dotičnih operatora na sljedeći način:

var izraz = gradovi.Select(g => g.ToUpper())
.Where(g => g.Length == 5)
.OrderBy(g=>g);

Argumenti koji se pojavljuju u metodama standardnih upita zovem lambda izrazi (lambda expressions) , oni obezbjeđuju da se standardni operatori upita definišu posebno kao metode i spajaju lančano korištenjem tačka notacije (dot notation).

Izvršavanje LINQ upita

U prethodnom primjeru značajno je kazati da se evaluacija upita ne dešava u trenutku njegovog deklarisanja. Evaluacija LINQ upita se dešava u onom trenutku kada pristupamo varijabli odnosno u našem primjeru evaluacija upita vrši se u bloku koji je označen naredbom foreach. Ovakva odgođena (defered) evaluacija upita upit čini fleksibilnim i dozvoljava izvršavanje više puta sa jednom definicijom. Pretpostavimo sljedeći primjer:

class Program
{
static void Main(string[] args)
{
   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };

  //Konstrukcija LINQ upita
  var izraz = from g in gradovi where g.Length == 5 orderby g select g.ToUpper();

  //izvršavanje upita na poljem stringova
  foreach (string grad in izraz)
    Console.WriteLine(grad);

   Console.WriteLine("-------- Drugo izvršavanje upita----");

   //Modifikacija polja
   gradovi[1] = "Brčko";

   //ponovno izvršavanje upita na poljem stringova
   foreach (string grad in izraz)
    Console.WriteLine(grad);

   Console.ReadLine();
 }
}

Ako gornji primjer kompajliramo i pokrenemo rezultat se dobije kao na sljedećoj slici.

image

Upit se evaluira svaki put kada smo iterirali varijablu izraz. Međutim, odgođeno izvršavanje LINQ upita može dovesti u zabludu, posebno ako se ne poznaje dovoljno način na koji se izvršava LINQ upit. Kao dokaz uzmimo sljedeći primjer:

class Program
{
static void Main(string[] args)
{
   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };

   string prvoSlovo = "S";

   //Konstrukcija LINQ upita
   var izraz = from g in gradovi where g.StartsWith(prvoSlovo) select g;

   string ispisNaKOnzolu = "Gradovi koji počinju s prvim slovom '" + prvoSlovo + "'\n";

   //promijenimo varijablu prvoSLovo
   prvoSlovo = "B";

   Console.WriteLine(ispisNaKOnzolu);

  foreach (string grad in izraz)
   Console.WriteLine(grad);

   Console.ReadLine();
}
}

U primjeru imamo definisan LINQ upit sa varijablom prvoSlovo, i gdje smo formirali LINQ upit kada je dotična varijabla imala vrijednost „S“. U normalnim uslovima, onako kako smo naučili da se varijable inicijaliziraju i po logici stvari, LINQ upit bi trebao vratiti sve gradove koji počinju sa prvim slovom S. Medjutim, rezultat upita je vratio gradove koji počinju slovom B, jer smo vrijednos slova B, pridružili vrajiabli prije same evaluacije LINQ upita, što potvrđuje i pokretanje primjera i sljedeća slika.

image

To upravo pokazuje način i vrijeme kada se LINQ upit generiše i evaluira nad izvorom podataka.

LINQ standardni operatori upita

Prethodno smo vidjeli upotrebu nekoliko operatora upita. U ovom poglavlju pozabavit ćemo se više oko ovih operatora, te vidjeti na koji ih način koristiti efikasno u programima. Razvijajući aplikacije, programer  konstantno rješava određene probleme manipulacije sa podacima, riješava i definiše algoritme I pomoćne programe. U tu svrhu koriste se određene preinstalirane biblioteke kao i biblioteke od trećih lica. Nažalost, nismo uvijek u prilici da sa bibliotekama koje koristimo imamo riješene sve probleme.

LINQ operatori upita mnoge naše probleme kojim smo svakodnevno okruženi tokom razvoja aplikacija, mogu zaista efektivno riješiti u samo nekoliko linija koda. Kao što smo kazali LINQ ne predstavlja manipulaciju samo sa eksternim izvorima podataka, jer se mogu koristiti I kako smo ranije kazali sa memorijskim izvorima podataka. U narednom tekstu upoznat ćemo se sa operatorima upita te kroz primjere primjene pokazati njihovu jednostavnost, efikasnost i lakoću.

Operatori sortiranja (orderby, reverse, descending)

Već smo u  prvom dijelu vidjeli upotrebu sortiranja podataka. Prednosti korištenja ovog operatora nad kolekcijama koje nemaju implementiranu ovu mogućnost su vrlo korisne. Uzmimo iz prethodnog primjera polje stringova i primjenimo operator sortiranja. Npr sortirajmo uzlaznim i slizanim redom gradove u BiH:

//sortiranje niza abecedno
var g1 = gradovi.OrderBy(g=>g);
var g2 = gradovi.OrderByDescending(g=>g);

Međutim, sortiranje možemo izvršiti po nekom drugom kriteriju npr.  po broju slova u nazivu.

//sortiranje niza po broju slova u nazivu
var gs1 = gradovi.OrderBy(g=>g.Length);
var gs2 = gradovi.OrderByDescending(g=>g.Length);

Kriterije možemo lančano slagati i sa tačka notacijom. U tom slučaju koristimo ThenBy operator poslije OrderBy operatora. Kombinirajmo prethodna dva primjera i napišimo sljedeći primjer:

//sortiranje niza po broju slova u nazivu i abecedno
var g = gradovi.OrderBy(g => g.Length).ThenBy(g => g);

Reverse operator koristimo kada želimo sortirani niz obrnuti. Sljedeći primjer prikazuje upotrebu operatora Reverse i OrderbyDescending, da bi prikazali suštinsku razliku između ova dva operatora.

//suštinske razlike operatora sortiranja
var g1 = gradovi.OrderBy(g => g.Length);
var g2 = gradovi.OrderByDescending(g=>g.Length);
var g3 = gradovi.OrderBy(g => g.Length).Reverse();

Rezultat prethodnih upita prikazan je na sljedećoj slici:

image

Reverse operator za razliku od OrderBy operatora  oslanja se samo na poredak podataka koji je dobijenih iz izvora podataka.

GroupBy operator

Skup standardni operatora upita čini i operator grupiranja GroupBy, koji uspostavlja podjelu nad sekvencijalnim vrijednostima baziranih na funkcijama ekstrakcije. Ovaj operator vraća skup IGrouping podataka za svaki različiti vrijednosni kriterij. Svakako da je IGrouping izvedeni interfejs od IEnumerable koji dodatno sadrži kriterij koji je korišten za ekstrakciju podataka iz izvora. Jednostavan primjer upotrebe GroupBy operatora možemo predstaviti na sljedećem primjeru:

class Program
{
static void Main(string[] args)
{
   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };

   //Grupiranje po dužini riječi
   var grupeGradova = gradovi.GroupBy(g => g.Length);
   foreach (IGrouping grupe in grupeGradova)
     {
        Console.WriteLine("Dužine riječi u nazivima gradova od {0} slova", grupe.Key);
        Console.WriteLine("-----------------------------------------------");

        foreach (string grad in grupe)
           Console.WriteLine("  {0}",grad);
      }

   Console.ReadLine();
 }
}

Poslije pokretanja ovog programa rezultat je prikazan na sljedećoj slici:

image

Agregacijski operatori

Agregacijski operatori koji su podržani u LINQ definišemo na sličan način, kao I prethodne operatore. Napišimo primjer upotrebe agregacijskog operatora Agregate. Ovaj operator vrši određenu kalkulaciju na sekvencom podataka. Operator vrši operacije koristeći  lambda izraze nad svakojm sekvenco podataka. Sljedeći primjer izračunava broj karaktera korištenih u cijelom nizu:

   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };
   int brojSlova = gradovi.Aggregate(0, (c, s) => c + s.Length);
   Console.WriteLine("Broj slova svih gradova u nizu iznosi: {0}",brojSlova);

Rezultat pokretanja programa:

image

Agregate operator propisuje  Count  operator i 4 numerička agregacijska operatora(Min, Max, Sum, i Average) minimum, maksimum, suma i srednja vrijednost respektivno. Ovi se numerički operatori procesuiraju nad sekvencama podataka bilo kojeg numeričkog tipa podataka: int, double, decimal i sl. Sljedeći primjer prokazuje upotrebu nekoliko pomenutih operatora:

int[] brojevi = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
int ukupno = brojevi.Sum();
int sumaKvadrata = brojevi.Sum(x=>x*x);
Console.WriteLine("Suma članova niza: {0}",ukupno);
Console.WriteLine("Suma kvadrata članova niza: {0}", sumaKvadrata);

Rezultat pokretanja programa prikazan je na sljedećoj slici:

image

Sintaksa upita u LINQ

U prethodnim paragrafima vidjeli smo načine kako možemo formirati upit nad izvorom podataka. Takodjer se može primjetiti da svaki upit možemo formirati na dva, u suštini ista, a sintaktički različita načina. Naime svaki LINQ upit formiran Lambda izrazom, takodjer  možemo formirati tačka notacijom (Dot Notaton). Ovakav fleksibilam pristup definisanju upita u LINQ rezultat su proširenja koja se pojavljuju u verziji C#3.0, a koje smo spominjali u prethodnim blog postovima postovima. Npr. definišimo LINQ upit tačka notacijom. Imamo:

var izraz= nazivi.Where(s=>s.Lenght==6)
                     .OrderBy(s=>s)
                     .Select(s=>s.ToUpper());

Prethodni upit  možemo formirati Lambda izrazom na sljedeći način:

var izraz= from s in imena
              where s.Lenght ==6
              orderby s
              select s.ToUpper();

Na kraju ovog članaka pobrojat ćemo sve operatore koji se mogu pojaviti u LINQ upitima:

OPERATOR

Opis

Where

Restriktivni operator.

Select / SelectMany

Operator projekcije.

Take/Skip

TakeWhile/SkipWhile

Parcijalni operator baziran na poziciji ili uslovnoj funkciji.

Join/GroupJoin

Operator spajanja u odnosu na zadani uslov.

Concat

Operator spajanja.

OrderBy/ThenBy/

OrderByDescending

ThenByDescending

Sortiranje u uzlaznom, silaznom smijeru u odnosu na zadani uslov.

Reverse

Operator sortiranja sekvence u suprotnom smijeru

GroupBy

Operator grupiranja u odnosu na zadani uslov.

Distinct

Operator uklanjanja duplikata elemenata u skupu.

Union/Intersect

Operatr koji vraća uniju ili podskup zadanih skupova elemenata.

Except

Operator koji vraća komplement zadanog skupa.

ToSequence

Operator konverzije u IEnumerable

ToArray/ToList

Operator konverzije u List

ToDictionary/ToLookup

Operator konverzije u Dictionary ili LookUp u odnosu na zadani ključ.

OfType/Cast

Operator konverzije u Ienumerableu odnosu na filtrirane elemente ili konverzije u tip argumenta.

EqualAll

Operator jednakosti koji vraća jedenake uparene elemente.

First/FirstOrDefault/

Last/LastOrDefault/

Single/SingleOrDefault

Operator vraćanja početne/zadnje/jednog elementa u odnosu na zadanu funkciju.

ElementAt/

ElementAtOrDefault

Operator vraćanja elementa određene pozicije.

DefaultIfEmpty

Operator zamjene prazne vrijednosti sa podrazumijevanom.

Range

Generatorski operator vraćanja broja u opsegu.

Repeat

Generatorski operator vraćanja višestrukih pojavljivanja elementa zadane vrijednosti.

Empty

Generatorski operator vraćanja prazne sekvence.

Any/All

Kvantifikator provjere egzistencije ili univerzalnosti funkcije izraza.

Contains

Kvantifikator provjere postojanja datog elementa.

Count/LongCount

Agregacijski operatori brojanja elemenata.

Sum/Max/Min/Average

Agregacijski oparatori kao opcione funkcije selektora.

Aggregate

Agregacijski oparator , vidi dio III

References

1. The LINQ Project .NET Language Integrated Query May 2006 Don Box, Architect, Microsoft Corporation and Anders Hejlsberg, Technical Fellow, Microsoft Corporation
2. http://forums.microsoft.com/msdn/showforum.aspx?forumid=123&siteid=1
3. http://weblogs.asp.net/scottgu/
4. http://microsoft.com

About Bahrudin Hrnjica

PhD in Mechanical Engineering, Microsoft MVP for .NET. Likes .NET, Math, Mechanical Engineering, Evolutionary Algorithms, Blogging.

Posted on 26/10/2010, in .NET, C# and tagged , , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s