.NET LINQ - OrderBy ThenBy

Zdravo mojstri!

Ciljam na tistih nekaj .NET programerjev, ker (spet :) ) rabim malenkost pomoči.

Zgodba je ta, da imam objekt tipa DataTable, ki ga je treba posortirat glede na stolpce. Glavni trik je pa v tem, da pri pogoju ni vedno isto število stolpcev (lahko je en, lahko jih je pa n).

Vprašanje je torej: ali ima kdo izkušnje, kako dinamično sestavit LINQ sortiranje?

Med brskanjem po netu sem naletel na tole rešitev, konkretno moja implementacija pa izgleda takole:
.

' split all ORDER BY fields by comas
Dim querySegments As String() = sbOrder.ToString.Split(",")

' get the first order by field
Dim firstSegment As String() = querySegments(0).Split(" ")
Dim fieldName As String = firstSegment(0)

' ordered collection for keeping sort results
Dim orderedResults = dtResult.AsEnumerable.OrderBy(Function(f) f.GetItem(fieldName))

' sort by first field (ASC by default, DESC only if provided)
If firstSegment.Length > 1 AndAlso firstSegment(1) = "DESC" Then
    orderedResults = dtResult.AsEnumerable.OrderByDescending(Function(f) f.GetItem(fieldName))
End If

' sort by additional fields - segments (if provided - ASC by default, DESC only if provided)
If querySegments.Length > 1 Then

    For Each segment As String In querySegments.Skip(1)

        Dim subSegment As String() = segment.Trim().Split(" ")
        fieldName = subSegment(0)

        If subSegment.Length > 1 AndAlso subSegment(1) = "DESC" Then
            orderedResults = orderedResults.ThenByDescending(Function(f) f.GetItem(fieldName))
        Else
            orderedResults = orderedResults.ThenBy(Function(f) f.GetItem(fieldName))
        End If
    Next
End If

dtResult = orderedResults.CopyToDataTable()

.
Ja, gre za VB - ampak mislim, da noben C# ekspert ne bi smel imet problemov s tem.

Še malenkost razlage:

sbOrder je tak, kot je pri SQL sintaksa za ORDER BY - torej, v stilu "NAZIV ASC, CENA DESC". Ta string se potem razbije na tabelo posameznih polj (querySegments), ki za dan primer izgleda: "NAZIV ASC", "CENA DESC". Potem se pa vsak segment razbije še na subSegment polje, kjer je prvi element ime stolpca (npr. "NAZIV"), drugi element je pa smer ("ASC" oz. "DESC"). Če drugega elementa ni, se predpostavi "ASC".

Problem pri tej implementaciji je, da je rezultat sortiran samo po zadnjem pogoju - čeprav ne bi smel biti, ker naj bi ThenBy oz. ThenByDescending upoštevala predhodne pogoje.

Našel sem tudi DynamicQuery knjižnico, ki vsaj glede na dokumentacijo rešuje ta problem. Ampak preden se spravim kopat v tisto smer, bi najprej rad preveril, če pri trenutni implementaciji delam kakšne neumnosti.

Vsak (s temo povezan) komentar izredno dobrodošel.

And as always: tole ni tema za komentarje "V javi/phpju/rubyju/brainfucku je pa to čist simple narest. M$ sucks.".

4 odgovori

Čisto tko na uč. Nevem če se v teh poznejšem ordering tale .ThanByDescending zaveda da je predhodno bil že klican .OrderByDescending(...).

Daj probaj narest query orderedResults.OrderByDescending(..).ThanByDescending(..) v enem šusu pa preveri kak je rezultat.

2

Tole sem že poskusil: če je vse v enem šusu, potem je sortiranje ok.

Teoretično, bi se moral ThenBy zavedat, da je bil OrderBy že klican. Ker najprej se naredi tole:
.

Dim orderedResults = dtResult.AsEnumerable.OrderBy(Function(f) f.GetItem(fieldName))

.

Potem pa se nad tem rezultatom kliče ThenBy
.

orderedResults = orderedResults.ThenBy(Function(f) f.GetItem(fieldName))

.
Obe metodi vrneta OrderedEnumerableRowCollection(of DataRow) in ravno poanta ThenBy naj bi bila, da se jo lahko chaina večkrat, s tem da upošteva predhodno sortiranje.

Recimo, jst lahko naredim tole:
.

dtResult = dtResult.AsEnumerable.OrderBy(Function(f) f.GetItem("POLJE1")).ThenBy(Function(n) n.GetItem("POLJE2")).ThenBy(Function(n) n.GetItem("POLJE3")).CopyToDataTable()

.
in bo rezultat pravilen.

Ampak kot rečeno, niso vedno ista polja po katerih je treba sortirat. Tudi številčno je lahko eno polje za sortiranje, ali pa jih je več. Tako, da iščem rešitev za dinamično sortiranje oz. kako dinamično sestavit vse skupaj.

Je mogoče kakšen alternativen način, kako bi se to dalo rešit?

Hvala obema za odgovore. Izkazalo pa se je, da je problem veliko bolj enostaven in da je šlo za čisto šlamparijo z moje strani :)

Test, ki ga je predlagal wingback, sem izvedel na precej premajhnem vzorcu, zato nisem uspel opazit napake. Problem je bil v tem, da tale moja extension metoda GetItem vedno vrne string (tudi, če gre za numerično vrednost). Torej so vrednosti "1", "4", "10", "30" po sortiranju v tem zaporedju: "1", "10", "30", "4". Kar je sicer smiselno, meni pa nerodno priznat :)

Tako, da je bilo treba dodat preverjanje tipa vrednosti (in ustrezna pretvorba), potem pa laufa ko šus :) "Chainanje" .ThenBy dela brez problema.

Live and learn :)

5