Galin Iliev

Software Architecture & Development

Основни оператори

Тайната на 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# синтаксис. Самото изписване е по следният начин:

join … in … on … equals

Със следващият пример ще върнем като резултат всички служители, за които има запис в масива 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

 

Проекти с примерите

Проект за VS 2008 Beta 2 (33.3KB)

 

Следваща част: LINQ to Objects

Content