Linq päringukeel

From EIK wiki

LINQ päringukeelega seotud tehnoloogiad

LINQ päringukeele kasutamine on võimalik tänu mitmetele uutele tehnoloogiatele C# programmeerimiskeeles. Järgnevalt lühike ülevaade mõningatest nendest tehnoloogiatest.

  • Muutujatüüp var
  • Objekti initsialiseerijad
  • Anonüümsed muutujatüübid
  • Laiendusmeetodid
  • Lambda avaldised

Meetod ja päringulause LINQ

Jämedalt on võimalik LINQ kaheks jagada - päringulause ja meetodite LINQ.

  • Meetodite LINQ puhul järjestatakse enumereeritava objekti järgi meetodid, milles kasutatakse lambda avaldisi.
  • Päringulausete puhul kasutatakse SQL päringukeele sarnase süntaksiga päringukirjeldusi.

Metoodika erinevuse ja mugavuse selgitamiseks loome XML'i, millele päringuid teha:

<?xml version="1.0" encoding="utf-8" ?> 
<house>
  <room id="1">
    <computer id="1" OS="Windows" />
    <computer id="2" OS="Linux" />
    <computer id="3" OS="Linux" />
  </room>
  <room id="2">
    <computer id="4" OS="Windows" />
    <computer id="5" OS="Windows" />
    <computer id="6" OS="Linux" />
  </room>
  <room id="3">
    <computer id="7" OS="Windows" />
    <computer id="8" OS="Windows" />
    <computer id="9" OS="Linux" />
    <computer id="10" OS="Windows" />
  </room>
</house>

Esmaks teeme päringu meetoditega sellesse XML'i pärime sealt välja kõik arvutid, mille operatsioonisüsteemiks on Linux:

var computers = XDocument.Load("house.xml") // Laeme XML'i failist
                  .Root // Võtame juurelemendi, milleks on "house" element
                  .Elements("room") // Saame juurelemendi kõik alamelemendid, mille nimi on "room"
                  .SelectMany( // Me peame kasutama SelectMany funktsiooni, kuna meil on mitmedimensiooniline XML
                    room => room.Elements("computer") // Vaatame järjest igas ruumis olevaid arvuteid
                      .Where(computer => computer.Attribute("OS").Value == "Linux") // Tagastame vaid need, mis on Linuxiga
                  );
// Prindime välja kõik saadud elemendid
foreach (var e in computers)
  Console.WriteLine(e.ToString());

Nüüd sama asi LINQ päringulausena:

var computers = from XElement room in XDocument.Load("house.xml").Root.Elements("room") // Käime läbi XMLi juurelemendis olevad "room" elemendid 
                from XElement computer in room.Elements("computer") // Igas ruumis käime läbi kõik "computer" elemendid
                where computer.Attribute("OS").Value == "Linux" // Filtreerime tagastatuks ainult need elemendid, millel on Linux
                select computer; // Valime, et tagastataks "computer" element
// Prindime välja kõik saadud elemendid
foreach (var e in computers)
  Console.WriteLine(e.ToString());

Mõlema LINQ päringu tulemus on sama:

<computer id="2" OS="Linux" />
<computer id="3" OS="Linux" />
<computer id="6" OS="Linux" />
<computer id="9" OS="Linux" />

Milles siis on vahe? Vahe on tegelikult ju loetavuses. Päringulausena on päring palju arusaadavam. Samas lihtsamate päringute puhul on jällegi mugavam kasutada meetodeid.

Muutujatüüp var

Muutujatüübiga var tähistatakse C# programmeerimiskeeles muutujaid, mille tüüp määratakse initsialiseerimise käigus. Samas on var tüübikindel, sest kui tüüp on korra määratud, siis edaspidi seda muuta ei ole võimalik.

Näide

 
string sona = "tere"; // luuakse string tüüpi muutuja sona
var sona2 = "tere";   // luuakse string tüüpi muutuja sona2
sona2 = 3                   // tekib viga, sest püütakse string tüüpi muutujale omistada int tüüpi väärtus

Objekti initsialiseerijad

Kui tavaliselt tuleb objekti loomise järel kõik vajalikud andmeväljad eraldi täita, siis objekti initsialiseerijad loovad võimaluse algväärtustada loomise käigus ka kõik vajalikud andmeväljad.


Näide

inime tonu = new inime(); // luuakse uus andmeobjekt tonu, mis on inime tüüpi
tonu.eesnimi = "jaan";       // väärtustatakse andmeväli eesnimi stringiga „jaan“
tonu.vanus = 19;                 // väärtustatakse andmeväli vanus int tüüpi väärtusega 19

inime tonu2 = new inime { eesnimi = "jaan", vanus = 19 }; // on samaväärne eelnevaga

Anonüümsed muutujatüübid

Anonüümsed muutujatüübid võimaldavad andmeobjekti tüübi luua selleks eelnevalt klassikirjeldust loomata.


Näide

var inime2 = new { eesnimi = "Heiki",  vanus = 28 }; // luuakse kahe andmeväljaga uus muutujatüüp
var jorss = new { vanus = 8, eesnimi = "Jaan" };  // luuakse kahe andmeväljaga uus muutujatüüp

Eelnevas näites toodud muutujatüübid on erinevad, sest nende andmeväljad on erinevas järjekorras (esimesel string ja int ning teisel int ja string).

Laiendusmeetodid

Laiendusmeetod on staatiline meetod, mis on väljakutsutav moel nagu see oleks konkreetse instantsi dünaamiline meetod. Võimalikuks teeb selle märksõna this meetodid parameetri ees. Kui luua uus klaas Laiendused ning sinna sisse meetod LoeSonadLauses, mis näib välja nii:

    public static class Laiendused
    {
        public static int LoeSonadLauses(this String lause)
        {
            return lause.Split(new char[] { ' ', ',', '.' }).Length;
        }
    }

siis tekib kõigile samas nimeruumis olevatele stringidele kasutatavaks meetod LoeSonadLauses. Kui on soov kasutada stringidel teistes nimeruumides ka seda laiendusmeetodit, siis tuleb importida see klass vastavasse nimeruumi (kasutades using konstruktsiooni). Kui kasutada System.Linq nimeruumi, siis tekib muutujatüüpidele, mis realiseerivad IEnumerable tüüpi liidese laiendusmeetodid GroupBy, OrderBy, Average jne.

Lambda avaldised

Lambda avaldised C# keeles võimaldavad luua lihtsaid tingimuslauseid ning omistamisi.


Näide

List<int> arvud = new List<int>(); // luuakse int tüüpi list
for (int i = 0; i < 500; i++)
    arvud.Add(i); // täidetakse list arvudega 0..500
var paaritud = arvud.FindAll(N => N%2 != 0); // valitakse ainult paaritud arvud

Järgnevalt mõned lambda avaldiste kasutamise näited:

Lambda avaldis	Tulemus	Vastuse tüüp
x => x +1	Kui x = 1, siis tulemus on 2	int
x => x == 5	Kui x = 5, siis vastus true, muul juhul false	boolean
x => x % 5 == 0	Kui x jagub viiega, siis true, muul juhul false	boolean
(x,y) => x+y	x ja y summa	int
(x,y) => x== y	Kui x ja y on võrdsed, siis true, muul juhul false	boolean

LINQ päringud

LINQ päringud sarnanevad mõnevõrra SQL keele päringutele, kuid samas on need päringud rohkem sarnased keele üldisele süntaksile.


Näide

// loome lihtsa arvude listi, mis sisaldab arve nullist kuni 499’ni
List<int> arvud = new List<int>(); // luuakse int tüüpi list
for (int i = 0; i < 500; i++)
    arvud.Add(i); // täidetakse list arvudega 0..500

// teeme päringu, mis valiks sajast suuremad arvud:
var paaritud2 = from x in arvud
       where x % 2 == 0
       select x;

Eesti keeles võiks öelda, et üldiselt on LINQ päringu süntaks on järgmine: võta midagi kusagilt suuremast kuhjast, vaata, kas sobib, kui sobib, siis pista teise kuhja. Sorteerimine LINQ pärnig võimaldab ka tulemuste sorteerimist mingisuguste omaduste järgi.


Näide

  // Leia inimesed kelle nime pikkus on suurem kolmest ja sorteeri need vanuse järgi
  var inimesed3 = from x in inimesed
        where x.eesnimi.Length > 3
        orderby x.vanus
        select x;

Samas on LINQ lauset võimalik kasutada ka ainult sorteerimiseks.


Näide

    // Leia inimesed ja sorteeri need vanuse järgi
    var inimesed3 = from x in inimesed
          orderby x.vanus
          select x;

Samuti on võimalik luua LINQ päring, kus sobivad elemendid sorteeritakse mintme erineva omaduse järgi ning kasutada vastupidist sorteerimisloogikat (alustades suurimast).

// Leia inimesed kelle nime pikkus on suurem kolmest ja sorteeri need vanuse ning nime järgi
//vastupidises järjekorras
var inimesed3 = from x in inimesed
     where x.eesnimi.Length > 3
     orderby x.vanus, x.eesnimi descending
     select x;

Iteratiivne vs deklaratiivne

Kui LINQ ilmavalgust nägi, siis paljude arendajate jaoks oli see kui jumalik kingitus, kuna sai paljusid päringuid ülilihtsalt ja samas ka Lazy moodi teha.

Järgmise näitega üritame selgitada, miks just kasutada LINQ-t. Mis on tema eelised iteratiivselt implementeeritud koodi puhul.

Kõigepealt iteratiivne .NET 2.0 kood. Loome klassi kahe Employee klassi võrdlemiseks:[1]

public class EmployeeLastNameComparer : IComparer<Employee>
{
    public int Compare(Employee x, Employee y)
    {
        return x.LastName.CompareTo(y.LastName);
    }
}

Nüüd sorteerime .NET 2.0 stiilis:[2]

var employeeList = new List<Employee>();
employeeList.AddRange(employees);
employeeList.Sort(new EmployeeLastNameComparer());
var names = new List<string>();

foreach (var employee in employeeList)
{
    if (employee.Role == Role.Developer)
    {
        names.Add(employee.FullName);
    }
}

var developerNames = names.ToArray();

Kuid sama asja saame ju LINQ'ga deklaratiivselt teha:[3]

var developerNames = employees.Where(e => e.Role == Role.Developer)
                              .OrderBy(e => e.LastName)
                              .Select(e => e.FullName)
                              .ToArray();

Tohutult vähem koodi! Ning mis on veel selle pluss? Loetavus ja vigade võimalus on väiksem!

LINQ to XML

LINQ to XML tehnoloogia sisaldab hulka tehnoloogiaid, mis on mõeldud XML formaadis info ja failide loomiseks, töötlemiseks ning muutmiseks. LINQ to XML vahendid on koondatud System.Xml.Linq nimeruumi. System.Xml.Linq nimeruum sisaldab mitmete klasside kirjeldusi, peamised klassid ja nende omavahelised seosed on kirjeldatud alloleval joonisel


XDocument on andmetüüp, mis võimaldab mälus hoida koopiat XML dokumendist. XDocument võib sisaldada: XElement (juurelement, mida tohib olla ainult üks), Xdeclaration, XdocumentType ja XProcessingInstruction. XElement on andmetüüp, mis võimaldab mälus hoida koopiat XML elemendist. XDocument võib sisaldada: XElement (võib olla mitu), XComment, an XProcessingInstruction, XAttribute ja tekstilisi väärtuseid. XAttribute on andmetüüp, mis sisaldab nime ja väärtuse paari. Eelmainitud kolm andmetüüpi ongi põhilised, millega programmeerijal tuleb kokku puutuda. XML formaadis andmeid võib ette kujutada kui XElementidest koosnevat puud.

  XML objektide loomine XML objektidest kõige enam kasutatav on XElement. Uue XElemendi loomiseks saab kasutada XElemendi klassi konstruktoreid:

 XElement(XElement)	Loob uue XElemendi olemasoleva XElemendi baasil
 XElement(XName)	Loob uue XElemendi etteantud märgdendinimega
 XElement(XStreamingElement)	Loob uue XElemendi olemasoleva XStreamingElement baasil
 XElement(XName, Object)	Loob uue XElemendi etteantud märgdendinimega ning määrab elemendi sisuks etteanutd objekti
 XElement(XName, Object[])	Loob uue XElemendi etteantud märgdendinimega ning määrab elemendi sisuks etteanutd objektide massiivi

Vaatame kõige lihtsamat juhtu:

//Loome uue XElemendi märgendinimega inimene
            XElement inimene = new XElement("inimene");
            Console.WriteLine(inimene);
            // Väljund: <inimene />

Loome nüüd XML objekti, mis sisaldab ka infot:

//Loome uue XElemendi märgendinimega inimene, 
//mis sisaldab väärtust "Jaan"
            XElement inimene2 = new XElement("inimene", "Jaan");
            Console.WriteLine(inimene2);
            // Väljund: <inimene>Jaan</inimene>

Loome nüüd XML objekti, mis sisaldab teist XElementi:

//Loome uue XElemendi märgendinimega inimene, 
//mis sisaldab teist XElementi
            XElement inimene3 = new XElement("inimene", 
                new XElement("nimi","Jaan"));
            Console.WriteLine(inimene3);
            // Väljund:
            //<inimene>
            //  <nimi>Jaan</nimi>
            //</inimene>

Loome nüüd XML objekti, mis sisaldab kahte XElementi ja atribuuti:

//Loome uue XElemendi märgendinimega inimene, 
//mis sisaldab kahte XElementi ja atribuuti
            XElement inimene4 = new XElement("inimene",
                new XElement("eesNimi", "Jaan"),
                new XElement("pereNimi", "Igamees"),
                new XAttribute("id", 1));
            Console.WriteLine(inimene4);
            // Väljund:
            //<inimene id="1">
            //  <eesNimi>Jaan</eesNimi>
            //  <pereNimi>Igamees</pereNimi>
            //</inimene>

Eelnevaga samaväärne on järgnev näide:

//Loome uue XElemendi märgendinimega inimene, 
//mis sisaldab kahte XElementi ja atribuuti
            XElement inimene5 = new XElement("inimene");
            inimene5.Add(new XElement("eesNimi", "Jaan"));
            inimene5.Add(new XElement("pereNimi", "Igamees"));
            inimene5.Add(new XAttribute("id", 1));
            Console.WriteLine(inimene5);
            // Väljund:
            //<inimene id="1">
            //  <eesNimi>Jaan</eesNimi>
            //  <pereNimi>Igamees</pereNimi>
            //</inimene>

XML failidest lugemine ja nende muutmine

XML failist info lugemiseks kasutame näitena Eesti Panga poolt väljastatavaid valuutakursse.

Eesti Pank pakub XML formaadis valuuta päevakursse aadressil : http://www.eestipank.ee/dynamic/erp/erp_xml.jsp?day=1&month=10&year=2009&type=4&lang=et, kusjuures antud aadressil on päevakursid 1. oktoober 2009 seisuga. Kui on soov saada mõne muu päeva päevakursse, siis peaks muutma aadressis sisalduvat kuupäeva.


XML formaadis andmete mällu lugemiseks saab kasutada XElement või XDocument klassi meetodit Load. Näide:

//Loen Eesti panga poolt pakutavad päevakursid mällu
            XElement kursid =   XElement.Load(
"http://www.eestipank.ee/dynamic/erp/erp_xml.jsp?day=1&month=10&year=2009&type=4&lang=et");
            //Trükin saadud XML formaadis andmed konsooli
            Console.WriteLine(kursid);
            //

Kui andmed on mällu loetud, siis on võimalik nende andmetega teostada LINQ päringuid. Näide:

//Teen päringu kurssidesse valides välja
            //"Body" nimelise elemendi alamelemendi "Currencies"
            // kõik alamelemendid
            var valuutaNimed = from x in kursid.Element("Body").Element("Currencies").Elements()
                               select x.Attribute("text").Value;
            foreach (var x in valuutaNimed)
                Console.WriteLine(x);

Samuti on siinkohal võimalik seada kitsendusi, nii on näiteks võimalik otsida üles valuutad, mis sisaldavad nimes „kroon“.


Näide:

//Teen päringu kurssidesse valides välja
            //"Body" nimelise elemendi alamelemendi "Currencies"
            // kõigi alamelementide atribuudi "text" väärtused, mis sisaldavad
            // "kroon"
            var kroonid = from x in kursid.Element("Body").Element("Currencies").Elements()
                          where x.Attribute("text").Value.Contains("kroon")
                          select x.Attribute("text").Value;
            foreach (var x in kroonid)
                Console.WriteLine(x);

Lisaks on alati võimalik kasutada sorteerimist:


Näide:

//Teen päringu kurssidesse valides välja
            //"Body" nimelise elemendi alamelemendi "Currencies"
            // kõigi alamelementide atribuudid "text", mis sisaldavad
            // "kroon" ja sorteerin need pikkuse järgi
            var kroonid = from x in kursid.Element("Body").Element("Currencies").Elements()
                          where x.Attribute("text").Value.Contains("kroon")
                          orderby x.Attribute("text").Value.Length
                          select x.Attribute("text");
            foreach (var x in kroonid)
                Console.WriteLine(x.Value);

Kui nüüd muuta mõnda päringuga saadud väärtust, näiteks asendada saadud kroonide väärtuses „kroon“ sõnaga „euro“ siis muutuvad ka nende elementdide ja atribuutide väärtused algses Xelemendis!


Näide:

            // Asendan väärtuse "kroon" väärtusega "euro"
            foreach (var x in kroonid)
                x.Value = x.Value.Replace("kroon", "euro");
            //Trükin välja mälus oleva XElemendi "kursid"
            Console.WriteLine(kursid);

Muudetud XElement on lihtsalt salvestatav, selleks tuleb kasutada meetodit Save. NB! Kui anda meetodile ette lihtsalt failinimi, siis salvestatakse fail programmi töökataloogi


Näide:

            //Salvestan XElement kursid faili nimega kursid_muudetud.xml
            kursid.Save("kursid_muudetud.xml");