Основни оператори
Тайната на LINQ e,
всъщност, в използваните оператори. Както и в SQL
без операторите where, group by, join (inner,
outer…)
възможностите биха били доста ограничени. Нека да разгледаме
подробно с какви оператори разполагаме в LINQ и как се
работи с тях:
where
Where оператора служи за
филтриране на елементите във входната колекция по зададени критерии аналогично в
T-SQL
заявките. Предимството тук е, че се използва C#
синтаксис, за задаване на критериите, а вътрешните класове на LINQ to
SQL се грижат за коректния превод към T-SQL.
Това ни позволява да изпишем критериите по същият начин, по който бихме ги
изписали и в if условие.
Ако вземем примерът по-горе и добавим още едно условие, което да връща
елементите започващи с
“G” и дължина по-голяма от 4 символа, то ще се получи следното:
var
result =
from
s
in
contacts
where
s.StartsWith("G")
&&
s.Lenght > 4
select s;
|
Възможно е използването на разширяващия метод
Where< TSource > дефиниран за
IEnumerable<TSource>. Така че следващия код е
аналогичен като действия, но използва разширяващи методи и ламбда изрази:
var
result = contacts.Where<string>(s => s.StartsWith("G") && s.Length >
4);
|
Както виждаме where
клаузата доближава C# и T-SQL
изключително много.
orderby
Точно така! Вече не трябва да имплементираме
IComparer интерфейса, за да сортираме колекция по
зададен критерий. Операторът orderby
позволява сортиране по подадени критерии аналогично на T-SQL.
В следващият пример освен филтриране на входната колекция извършваме и
сортиране:
var
result =
from
s
in
contacts
where
s.StartsWith("G")
&&
s.Lenght > 4
orderby s ascending
select s;
|
По този начин като резултата се сортира във възходящ ред.
За сортирането имаме нужда от допълнителни оператори, които да
указват посоката на сортиране:
·
ascending – указва
сортиране във възходящ ред (A-Z, 1-5), като това е
посоката по подразбиране. Ако не се укаже посоката в оператора
orderby
се използва тази посока.
·
descending –
низходящо сортиране (Z-A, 5-1)
Най-хубавото нещо тук е възможността да използваме оператора
orderby независимо от where. По този начин може само да
сортираме колекцията, без да я филтрираме. В примера по-долу сортираме цялата
колекция в низходящ ред и я връщаме като резултат:
var
result =
from
s
in
contacts
orderby s ascending
select s;
|
Възможно е също подреждане по няколко критерия, като критериите
се отделят със запетая.
group
Тази функционалност – групирането – също е взаимствана от
T-SQL. По своята същност, обаче, тя е малко по-различна от
предходните два оператора и се нуждае от различна имплементация в LINQ.
При групирането се създават два вида колекции:
·
Колекция от групи
·
Колекции от елементи за всяка група
По тази причина при обхождането на резултата трябва да го
реализираме с помощта на два вложени цикъла. Нека да разгледаме примера, който
групира данни от точка Групиране с LINQ и C# 3.0:
var
result =
from
e
in
employees
group e
by
e.Department
into
g
select g;
|
Нека да разгледаме синтаксисът на командата: след оператора
group указваме кои елементи да се
групират, последвани от ключовата дума by, след която се указва критерят по който
ще се групират елементите и след тях оператора
into
указващ променливата за групата.
Обърнете внимание, че на последния ред връщаме като резултат
групата - g, а не елемента
–
e, както в остналите примери. Това
е необходимо понеже трябва да върнем колекция от групи, което споменахме в
началото. Самите елементи се намират в колекция в дадена група.
Нека да разгледаме кода за обхождане на резултата, понеже и той
си има своята специфика:
foreach (IGrouping<string,
Employee>
group
in
result) {
Console.WriteLine("Group:{0}",
group.Key);
foreach (Employee
empl
in
group) {
Console.WriteLine("
{0} {1}", empl.Name, empl.Salary);
}
Console.WriteLine();
}
|
Кодът се състои от два вложени цикъла – по един за всеки тип
колекция- един за групите и един за елементите. Интересната част тук е типа на
групата – това
generic интерфейса
IGrouping<TKey, TElement> - като първият тип указва типа
на критерия, по който се групира, а вторият – типа на елементите в групата. Този
интерфейс дефинира свойство
Key, от което може да
вземем стойноста на критерият за групиране за контретната група. Разбира се може
да заменим този тип с вълшебният оператор var.
Вътрешният цикъл обхожда елементите, които се съдържат в
инстанцията на IGrouping<TKey, TElement>. Това е възможно, защото
IGrouping<TKey, TElement> наследява интерфейсите
IEnumerable<TElement>
и IEnumerable
като всъщност предствалява изброяем тип.
select
На този оператор следва да му се обърне малко повечко внимание,
понеже той указва какво всъщност да се върне като резултат от операцията. До
момента връщахме само един тип елементи в заявките, като този тип беше
предварително дефиниран. С помощта на select
оператора и анонимните типове може да върнем анонимен
тип създаден от свойствата на един или повече елементи. Това става по следният
начин:
var
result =
from
e
in
employees
select
new {
EmployeeName = e.Name, Department = e.Department };
|
По този начин може да върнем като резултат нов тип образуван от
свойствата на елемнети от няколко колекции. В следващата точка ще покажем как
select оператора играе важна роля при
свързванията (join).
join
join оператора служи за обединяване/свързване на резултатите на
две колекции, като свързването се осъществява по поле, което съдържа общи данни
(т.нар ключ). За да опишем какви са възможностите в LINQ
нека да разгледаме в T-SQL теорията какви видове
свързвания съществуват:
·
inner join –
вътрешно свързване – при това свързване за всеки елемент от първата колекция се
намира само един съответстващ ред във втората колекция. Елементите, които нямат
съотвестващ елемент в насрещната колекция се изключват от резултата.
·
outer join – външно
свързване – при този вид свързване се връщат всички елементи от едната колекция
и само съответстващите елементи. Съществуват следните подвидове:
o
left outer join –
връщат се всички елементи от лявата (първата) колекция и само съответстващите
елементи от дясната (втората) колекция.
o
right outer join –
връщат се всички елементи от дясната (втората) колекция и само съответстващите
елементи от лявата (първата) колекция.
o
full outer join –
връщат се всички елементи от двете колекции като съответстващите елементи се
засичат.
·
cross join – пълна
комбинация. При този вид свързване за всеки елемент от двете колекции се
изпълнява пълно комбиниране на насрещните елементи. Например ако в първата
колекция имаме 10 елемента, а 5 във втората то като резултат ще получим 50
елемента. Този вид свързване не е много често използван.
В LINQ са реализирани само най-често
употребяваните видове свързвания:
·
inner join – или в
документацията на LINQ просто
join
·
left/right outer join –
или в документацията на LINQ group join.
Наречен е така, заради начина по-който се връща резултата – под
формата на група по подобие на group
оператора. Всъщност е реализиран само left join,
но сменяйки местата на входните колекции може да изпълним логически и
right join.
За да представим подходящи примери ще дефинираме нова масив от
тип
Departments. Дефиницията на класа
е:
public
class
Department {
public
string Name;
public
string
Building;
}
|
А самият масив е инициализиран по този начин:
Department[]
departments = {
new
Department{Name="IT",
Building="Building
A"},
new
Department{Name="Sales",
Building="Building
B"}
};
|
Обърнете внимание, че според колекцията дефинирана в точка
Групиране с LINQ и C# 3.0 нямаме създаден елемент за отделите
“Motor Sport” и “Marketing”.
C# join
Нека да разгледаме как се реализира inner join
с помощта на C# синтаксис. Самото изписване е по
следният начин:
Със следващият пример ще върнем като резултат всички служители,
за които има запис в масива departments:
var
result =
from
d
in
departments
join e
in
employees
on
d.Name equals e.Department
select
new {
EmployeeName = e.Name, Building = d.Building };
|
Начинът на изписване на join е подобен на
from, но с малко допълнения. join
e in employees
указва входяща колекция и променлива за елемените в
нея;
on d.Name equals e.Department
указва ключа, по който се свързват елементите.
Като резултат получаваме елементите от колекцията
departments, за които има съответстващ елемент в
employees, но като резултат получаваме анонимен тип,
съдържащ името на служителя и съответстващата сграда, в която се помещава.Така
се обединява информацията от двете колекции от входни данни, за да се получи:
Joe
- Building A
Galcho - Building A
Jana
- Building B
Mary
- Building B
|
Забележете, че в резулата не са включени всички записи от
колекцията
employees.
Съответно същият резултат може да бъде получен чрез извикване на
разширяващият метод Join():
var
result = departments.Join(employees,
d
=> d.Name,
e
=> e.Department,
(d, e) =>
new
{ Building = d.Building, EmployeeName = e.Name });
|
C# group join
Изписването на group join е по следният начин:
join … in … on … equals … into …
|
Нека да преработим примерът от предишният пример в
group join:
var
result =
from
d
in
departments
join e
in
employees
on
d.Name equals e.Department into emp
select
new {
DepartmentName = d.Name, Building = d.Building, Employees = emp };
|
Освен ключовите думи, които описахме в предходната точка тук
срещаме още една – into emp.
Този израз указва променливата в която ще се поставят елементите (съдържа повече
от един елемент и затова е колекция) от колекцията
employees, които отговарят на условието. След това използвайки
оператора
select указваме, че в резултата ще се състои от нов, анонимен
тип, който съдържа три свойства:
·
DepartmentName –
който се взема от променливата d,
сочеща към даден отдел,
·
Building – името на
сградата, отново взета от променливата d,
·
Employees – и тук е
интересната част: съдържа колекцията от съответстващи елементи от
employees.
Понеже, както и при групирането имаме два вида колекции, се
нуждаем от два вложени цикъла, за да обходим резултата:
foreach (var
item
in
result) {
Console.WriteLine("{0}
- {1}", item.DepartmentName, item.Building);
foreach (var
em
in
item.Employees) {
Console.WriteLine("
{0} - {1}", em.Name, em.Salary);
}
}
|
И като резултат получаваме в конзолата:
IT -
Building A
Joe
- 1800
Galcho - 2000
Sales - Building B
Jana
- 900
Mary
- 700
|
Следваща част: LINQ to Objects