[ vbvlada @ 06.11.2009. 16:32 ] @
Ne znam kako vi rešavate probleme sa kreiranjem raznih RDLC izveštaja,
ali ja napravim tabelu u DataSet-u, povežem polja sa kontrolama na izveštaju i onda pravim metode
koje pune tu tabelu.
Nekada izveštaji zahtevaju komplikovane procedure, dosta foreach petlji itd. pa mi tada
punjenje tabele bude sporo.
Da li je neko probao da tako komplikovane zadatke reši koristeći LINQ i kakvi su rezultati?
[ vbvlada @ 28.01.2010. 15:20 ] @
Evo na kraju ću sam sebi da odgovorim na pitanje :)

Scenario:
Imamo 5.000 klijenata u jednoj tabeli (Klijent), druga tabela o mesečnim zaduženjima (Zaduzenja - 30.000 zapisa), treća o uplatama (Uplate - 60.000)
Zadatak: Izlistati sve Klijente sa sumiranim zaduženjima, uplatama i preostalim dugom, koristeći podatke u DataSet-u.
Framework 2.0: Select metode, foreach petlje: trajanje nekoliko minuta!
Framework 3.5 i LINQ: trajanje 2-3 sekunde!!

Rešavanje konkretnog problema nije toliko bitno koliko sam hteo da pokažem ogromnu razliku u performansama!
Pozdrav!
[ mmix @ 28.01.2010. 15:50 ] @
To poredjenje slabo znaci jer LINQ ne ucitava sve podatke na klijenta da bi radio agregaciju vec agregaciju radi na serveru, samim tim nije LINQ brzi od dataseta vec je sql brzi u agregaciji od custom foreach petlje ;). Napravi dataset sa semom rezultata agregacije i u select komandi ubaci agregatni SQL i dobices (bar za taj volume koji si naveo) priblizno iste rezultate.
[ vbvlada @ 28.01.2010. 17:27 ] @
Ja sam ovde radio sa podacima koji su već bili učitani u DataSet, dakle koristio sam LINQ to DataSet, tako da nije bilo obraćanja serveru,
a za ovo što si naveo potpuno se slažem.
Rezultat ovog testa je da je LINQ brži od klasičnih metoda selektovanja i dobijanja podataka iz DataSet-a.
[ sallle @ 28.01.2010. 19:21 ] @
rezultat testa je da ti je prethodni algoritam bio katastrofalan
[ vbvlada @ 28.01.2010. 20:10 ] @
Citat:
sallle: rezultat testa je da ti je prethodni algoritam bio katastrofalan

Ok možda je i to tačno, hajde možda dobijem neku korisnu informaciju od vas kako to da uradim bolje, a da i dalje ostanem na framework-u 2.0
Evo konkretnog zadatka: U DataSet-u su 3 tabele: Klijent(id, ime, prezime), Uplata(idKupac, iznos, datum), Zaduzenje(idKupac, iznos, datum), napunjene SVIM podacima iz baze.
Postoje odgovarajuće relacije između tih tabela.
Traži se sledeća tabela: ime, prezime, ukupne uplate, ukupnaZaduzenja, ukupanDug;
Koristiti podatke koji se već nalaze u DataSet-u (koji je, recimo, tipizirani), dakle bez obraćanja serveru, kako bi se kreirao ovaj izvestaj.

Ovo sada nema nikakve veze sa pitanjima sledećeg tipa: "A zašto povlačiš sve podatke iz baze", "A zašto ne kreiraš upit na serveru".. itd...
Nije poenta kako organizovati pristup podacima, već kako rešiti ovaj konkretan primer, i naravno njemu slične...

Eto očekujem neke nekatastrofalne alogritme :)
[ sallle @ 29.01.2010. 02:29 ] @
pretpostavljam da si ti radio nesto tipa:
Code:

foreach (datarow dr in dtKlienti.rows)
{
       foreach (datarow drr in dtUplate.select ("klient_id="+dr["klient_id"]))
       {
             sumUplata = sumUplata + drr.uplata
       }

       forach (datarow drr in dtZaduzenja.select("klient_id="+dr["klient_id"])
       {
             sumZaduzenja = sumZaduzenja+drr.zaduzenje
       }
}

ovi selecti pretpostavljam rade sekvencijalno pretrazivanje, i ne koriste to sto ti imas relaciju.

a verovatno ovako ide mnogo brze:
Code:

foreach (datarow dr in dtklienti.rows)
{
       foreach(datarow drr in dr.getchildrows(klient_uplata))
       {
                sumUplata = sumUplata +drr.uplata;
        }
        foreach (datarow drr in dr.getchildrows(klient_zaduzenje))
        {
                 sumzaduzenje = sumzaduzenje + drr.zaduzenje;
        }
}


probaj pa vidi ima li efekta


[ mmix @ 29.01.2010. 11:43 ] @
Zapravo LINQ mora da ti bude malcice (jedva primetno) sporiji zato sto moras da radis left outer join (preko LINQ group joina) da bi pokrio situacije u kojima klijent nema nijednu uplatu ili nijednu isplatu, obican linq join to proguta dok obican foreach to implicitno odradi. U svakom slucaju krajnji linq for datasets izraz je ovaj (pa uporedi to sa foreach verzijom ). Linq ima svoje dobre strane, ali u ovom primeru je problem resiv sa manje koda bez njega.

Code:
var final = from k in ds.Klijent
            join uu in
                (from u in ds.Uplata
                 group u by u.IDKupac
                 into uu
                     select new {IDKupac = uu.Key, UkupnoUplata = uu.Sum(x => x.Iznos)})
                on k.ID equals uu.IDKupac into ug
            join uz in
                (from u in ds.Zaduzenje
                 group u by u.IDKupac
                 into uz
                     select new {IDKupac = uz.Key, UkupnoZaduzenje = uz.Sum(x => x.Iznos)})
                on k.ID equals uz.IDKupac into zg
            from uglo in ug.DefaultIfEmpty()
            from zglo in zg.DefaultIfEmpty()
            select
                new
                    {
                        k.ID,
                        k.Ime,
                        k.Prezime,
                        UkupnoUplata = uglo == null ? 0 : uglo.UkupnoUplata,
                        UkupnoZaduzenje = zglo == null ? 0 : zglo.UkupnoZaduzenje
                    };

// foreach ;)
foreach (MainDataSet.KlijentRow krow in ds.Klijent)
{
    decimal sumU = 0, sumD = 0;
    foreach (MainDataSet.UplataRow uplataRow in krow.GetUplataRows()) sumU += uplataRow.Iznos;
    foreach (MainDataSet.ZaduzenjeRow zaduzenjeRow in krow.GetZaduzenjeRows()) sumD += zaduzenjeRow.Iznos;
}


Sto se tice vremena, oba su kod mene (sa obim podataka iz tvoje prve poruke) izvrsila (sa sve punjenjem tabele rezultatima) uvek za manje od 200ms. Uostalom pogledaj attached projekat
Kreiranje sample podataka
:: Start (FOREACH)
:: Stop (FOREACH), Rows: 5000 Time: 00:00:00.1100062
:: Start (LINQ)
:: Stop (LINQ), Rows: 5000 Time: 00:00:00.1840105
[ vbvlada @ 01.02.2010. 16:14 ] @
Hvala na odgovoru!

Ovo je sigurno tačno, nema šta, samo mene zanima sledeće:
Recimo da imamo jos i vrstu zaduženja i uplate, recimo za osnovnu pretplatu, dodatke i ostalo
Moj realan problem je sto ne mogu preko GetUplataRows odmah da dobijem željene podatke, već koristim Select metode DataTable objekata.
Recimo na sve ono ši napravio dodamo i te vrste uplata, i napravimo 2 metode: double ukupneUplate(int id_klijent) i double ukupnaZaduzenja(id_klijent)
koje u sebi imaju Select poziv pa onda foreach.
Misliš li da bi to možda bio problem što meni ovo baš i ne radi najsjajnije preko foreach-a?
Evo izmenjenog projekta, zanima me da li sam ovde negde na gubitku?

[ mmix @ 01.02.2010. 19:39 ] @
To sto sad hoces se zove pivoting i postoji resenje za to i preko foreach i preko LINQa. Dacu ti to sutra ujutro, sad nemam VS pored sebe.
[ mmix @ 02.02.2010. 09:31 ] @
Ok, nije pivoting, malo sam zbrzao odgovor jer sam video tri vrste uplata a tebi treba samo jedna kategorija (Osnovne uplate) tako da nam pivot ne treba. Odmah da ti nesto kazem, sve (ali BAS SVE) sto moze da uradi Select() mozes da uradis i u foreach petlji BOLJE i BRZE od select metoda, razlika nije ubiustvena ali uvek postoji. Iz tih razloga ja nikad ne koristim taj metod, ni pre LINQa ga nisam koristio. parametar select metoda je WHERE klauzula koja moze da se implementira sa prostim foreach+if/then

Dakle resenje sa foreach petljom bi ti bilo:

Code:
foreach (MainDataSet.KlijentRow krow in ds.Klijent)
{
    decimal ukUplate = 0, ukZaduzenja = 0;
    foreach (MainDataSet.UplataRow uplataRow in krow.GetUplataRows()) 
        if (uplataRow.vrstaUplate == vrstaZaduzenjaUplate.Osnovna.ToString()) ukUplate += uplataRow.Iznos;
    foreach (MainDataSet.ZaduzenjeRow zaduzenjeRow in krow.GetZaduzenjeRows()) 
        if (zaduzenjeRow.vrstaZaduzenja == vrstaZaduzenjaUplate.Osnovna.ToString()) ukZaduzenja += zaduzenjeRow.Iznos;
}


i vreme izvrsavanja pada sa 500ms na oko 300ms, u oba slucaja medjutim nema govora o nekoliko minuta, cak ni sa select tako da mi nije bas jasno kako si dobio takav rezultat. Najvece usporenje ovde ti pravi poredjenje stringova pre nego kompleksnost algoritma (poredjenje strigova uvek dodaje *O(n) u ciklicni algoritam jer za svaku iteraciju mora da uradi internu petlju da uporedi n karaktera), ako enum kodiras numericki i smestis u int polje brzina ce biti skoro identicna kao u pocetnoj varijanti. U svakom slucaju LINQ varijanta ovog upita (cije vreme izvrsavanja je otprilike isto) je:

Code:
var final = from k in ds.Klijent
            join uu in
                (from u in ds.Uplata
                 where u.vrstaUplate == vrstaZaduzenjaUplate.Ostalo.ToString()
                 group u by u.IDKupac
                     into uu
                     select new { IDKupac = uu.Key, UkupnoUplata = uu.Sum(x => x.Iznos) })
                on k.ID equals uu.IDKupac into ug
            join uz in
                (from u in ds.Zaduzenje
                 where u.vrstaZaduzenja == vrstaZaduzenjaUplate.Ostalo.ToString()
                 group u by u.IDKupac
                     into uz
                     select new { IDKupac = uz.Key, UkupnoZaduzenje = uz.Sum(x => x.Iznos) })
                on k.ID equals uz.IDKupac into zg
            from uglo in ug.DefaultIfEmpty()
            from zglo in zg.DefaultIfEmpty()
            select
                new
                    {
                        k.ID,
                        k.Ime,
                        k.Prezime,
                        UkupnoUplata = uglo == null ? 0 : uglo.UkupnoUplata,
                        UkupnoZaduzenje = zglo == null ? 0 : zglo.UkupnoZaduzenje
                    };

Nema neke mudrosti, samo se ogranici sors tabela pre agregacije.
[ vbvlada @ 02.02.2010. 09:48 ] @
Misliš da bi foreach bio brži i od situacije kada bih, recimo, imao tabelu od 100.000 zapisa, a trebaju mi, npr. sve osnovne uplate za zadataog klijenta??

To što meni radi mnogo sporije od ovih testova, verovatno je posledica toga što radim na jednom ne tako malom sistemu, i što sam pokušavao da
mi kod bude što sređeniji i da ga ima što manje, a da nisam pazio na te stvari. Bilo je tu dosta "pakovanja" u metode, klase,
verovatno mi treba malo vremena da istražim o čemu se tu radi.
[ mmix @ 02.02.2010. 10:00 ] @
Da, bio bi. Ljudi generalno prave neke paralele o performansama izmedju datasetova i SQL Servera i prave paralele izmedju LINQa i T-SQLa, obe te paralele ne postoje. SQL Server ima query language kao LINQ ali SQLServer ima Query Optimizer i indekse koje LINQ nema. Isto tako dataset ima relacije, ali dataset takodje nema indekse. Bilo koja operacija tipa (select nesto from tabela where uslov) na SQL Serveru moze biti optimizovana, u DataSet i LINQ varijanti ne moze. Kako god da okrenes i sta god da koristis ta operacija se svodi na foreach+if i "skenira" kompletnu tabelu, select je najsporji, foreach najbrzi (zapravo najrza je for petlja preko indexera, to nismo pominjali), linq je u sredini. Postoji naravno i ona druga strana medalje vezana za trud, dal ti se isplati da potrosis sate za neki komplikovan query radjen preko for petlje ako to mozes za 2 minuta da uradis preko LINQa, a sve to da bi ustedeo 200ms na izrazu koji se pokrene jednom dnevno.

PS: Moram da se ispravim samo, for nije najbrzi, cak je najsporiji, interna struktura dataRows nije linearna vec je izvedena preko RBTree da bi ubrzala insert/update operacije. Posledica je da svaki table.Rows[index] zahtev mora da resi tabelu da bi vratio red na indeksu, tj svodi se na .Where(r => r.Index == x). Foreach sa druge strane putuje preko svih listova i ne mora da pretrazuje (on je zapravo pretrazivac). Sa for peltjom zapravo ceo ovaj proces moze da potraje podosta minuta, da nisi to mozda koristio?

[Ovu poruku je menjao mmix dana 02.02.2010. u 11:49 GMT+1]
[ vbvlada @ 02.02.2010. 11:06 ] @
Ne, koristio sam Select u većini slučajeva jer mi je tako bilo zgodno da dobijem podatke zbog korišćenja izraza,
a tipizirane metode GetChildrows uopste nisam koristio, misleci da tu nema razlike.
Ali evo upravo sam na konkretnom sistemu testirao pomocu tih metoda umesto Select-a i razlika je osetna!