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.

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.

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.

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:

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:

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:

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:

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