Wielokrotne „zamówienie przez” w LINQ

1582

Mam dwie tabele moviesi najpierw categoriesotrzymuję uporządkowaną listę według kategorii ID, a następnie Nazwy .

Tabela filmów ma trzy kolumny Identyfikator, Nazwa i ID kategorii . Tabela kategorii ma dwie kolumny Identyfikator i Nazwa .

Próbowałem czegoś takiego, ale to nie zadziałało.

var movies = _db.Movies.OrderBy( m => { m.CategoryID, m.Name })
Sasha
źródło
Oto dlaczego to nie działa: Wyrażenie lambda w nawiasach powinno zwracać wartość, której można użyć do zamówienia elementów: m.CategoryID to liczba, której można użyć do zamówienia towarów. Ale „m.CategoryID, m.Name” nie ma sensu w tym kontekście.
chiccodoro
9
.To, czego szukasz?
eka808,
Jeśli przypadkiem chcesz posortować je w porządku malejącym, oto droga do wyjścia.
RBT

Odpowiedzi:

2831

To powinno Ci pomóc:

var movies = _db.Movies.OrderBy(c => c.Category).ThenBy(n => n.Name)
Nathan W.
źródło
4
Dziękuję za odpowiedź, oczywiście ... Ale Var movies = _db.Movies.Orderby(c => c.Category).ThenBy(n => n.Name) jeśli Var movies = _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name) użyję 2 razy „orderBy”, dlaczego wynik jest inny?
147
@devendra, wynik jest inny, ponieważ drugi „OrderBy” działa nad kolekcją będącą wynikiem pierwszego „OrderBy” i zmienia kolejność jego elementów
68
Jak, u licha, spędziłem cały ten czas, nie wiedząc o tym ThenBy! ( Edycja: wygląda na to, że został wprowadzony w .NET 4.0, co wyjaśnia, jak przeszedł obok mnie niezauważony.)
Jordan Gray,
13
Jest to dostępne od czasu dodania LINQ. Ta odpowiedź jest wcześniejsza niż .NET 4.0.
Nathan W
10
Tak, doszedłem do wniosku, że zbyt pochopnie opieram się na braku wersji 3.5 na stronie dokumentacji ; Powinienem był szukać informacji o wersji. Dziękuję za poprawienie mnie. :)
Jordan Grey
594

Używając LINQ innej niż lambda, składni zapytania, możesz to zrobić:

var movies = from row in _db.Movies 
             orderby row.Category, row.Name
             select row;

[EDYCJA, aby skomentować komentarz] Aby kontrolować porządek sortowania, użyj słów kluczowych ascending(które są domyślne i dlatego nie są szczególnie przydatne) lub descending, w ten sposób:

var movies = from row in _db.Movies 
             orderby row.Category descending, row.Name
             select row;
Scott Stafford
źródło
1
W tej składni nie ma sposobu na przewijanie w przód iw tył, prawda?
ehdv 21.01.11
1
W rzeczywistości twoja odpowiedź jest odpowiednikiem _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Bardziej poprawne jestfrom row in _db.Movies orderby row.Category descending orderby row.Name select row
Lodewijk
9
@Lodewijk: Wierzę, że masz to dokładnie wstecz. Twój przykład będzie miał wiersz.Nazwa jest kolumną podstawową, a kolumna wiersz.Kategoria, co jest równoważne z _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Dwa podane fragmenty kodu są sobie równe, a nie OP.
Scott Stafford,
6
Jedynym minusem korzystania ze składni SQL dla Linq jest to, że nie wszystkie funkcje są obsługiwane, większość, ale nie wszystkie
Joshua G,
74

Dodaj nowe":

var movies = _db.Movies.OrderBy( m => new { m.CategoryID, m.Name })

To działa na moim pudełku. Zwraca coś, co można wykorzystać do sortowania. Zwraca obiekt o dwóch wartościach.

Podobne, ale inne niż sortowanie według połączonej kolumny, jak następuje.

var movies = _db.Movies.OrderBy( m => (m.CategoryID.ToString() + m.Name))
Alex
źródło
21
Zachowaj ostrożność, używając tej liczby.
WoF_Angel
7
Możesz użyć OrderByDescending i ThenBy lub OrderBy i ThenByDescending, w zależności od potrzeb.
Ali Shah Ahmed
6
Jestem pewien, że .OrderBy( m => new { m.CategoryID, m.Name })i .OrderBy( m => new { m.Name, m.CategoryID })będzie produkować takie same wyniki niż poszanowaniem zamierzony priorytet. Czasami wydaje się, że daje ci porządek, który chcesz wyłącznie przez przypadek. Dodatkowo m.CategoryID.ToString() + m.Namewygeneruje nieprawidłowe zamówienia, jeśli CategoryID to int. Na przykład coś o id = 123, nazwa = 5 razy pojawi się po id = 1234, nazwa = coś zamiast wcześniej. Nie jest również nieefektywne porównywanie ciągów, w których mogą wystąpić porównania int.
AaronLS,
7
Kiedy próbuję złożyć zamówienie na typ anonimowy, otrzymuję ArgumentException z komunikatem „Co najmniej jeden obiekt musi implementować IComparable”. Widzę, że inni muszą przy tym zadeklarować osobę porównującą. Zobacz stackoverflow.com/questions/10356864/… .
Robert Gowland
4
To absolutnie źle. Kolejność według nowego anonimowego typu, który nie ma implementacji ICompariable, nie może działać, ponieważ nie ma kolejności właściwości anonimowego typu. Nie wiedziałoby, czy posortować według CategoryID jako pierwszego, czy Imię jako pierwsze, nie mówiąc już o tym, czy mają być sortowane w odwrotnej kolejności.
Triynko
29

użyj następującego wiersza w DataContext, aby zarejestrować działanie SQL w DataContext w konsoli - wtedy możesz zobaczyć dokładnie, czego żądają twoje instrukcje linq z bazy danych:

_db.Log = Console.Out

Następujące instrukcje LINQ:

var movies = from row in _db.Movies 
             orderby row.CategoryID, row.Name
             select row;

I

var movies = _db.Movies.OrderBy(m => m.CategoryID).ThenBy(m => m.Name);

utworzyć następujący kod SQL:

SELECT [t0].ID, [t0].[Name], [t0].CategoryID
FROM [dbo].[Movies] as [t0]
ORDER BY [t0].CategoryID, [t0].[Name]

Podczas gdy powtarzanie OrderBy w Linq wydaje się odwracać wynikowy wynik SQL:

var movies = from row in _db.Movies 
             orderby row.CategoryID
             orderby row.Name
             select row;

I

var movies = _db.Movies.OrderBy(m => m.CategoryID).OrderBy(m => m.Name);

utworzyć następujący kod SQL (nazwy i identyfikator kategorii są przełączane):

SELECT [t0].ID, [t0].[Name], [t0].CategoryID
FROM [dbo].[Movies] as [t0]
ORDER BY [t0].[Name], [t0].CategoryID
Oliver Slay
źródło
24

Stworzyłem kilka metod rozszerzenia (poniżej), więc nie musisz się martwić, czy IQueryable jest już zamówiony, czy nie. Jeśli chcesz zamówić według wielu właściwości, zrób to w następujący sposób:

// We do not have to care if the queryable is already sorted or not. 
// The order of the Smart* calls defines the order priority
queryable.SmartOrderBy(i => i.Property1).SmartOrderByDescending(i => i.Property2);

Jest to szczególnie przydatne, jeśli tworzysz porządek dynamicznie, np. Z listy właściwości do sortowania.

public static class IQueryableExtension
{
    public static bool IsOrdered<T>(this IQueryable<T> queryable) {
        if(queryable == null) {
            throw new ArgumentNullException("queryable");
        }

        return queryable.Expression.Type == typeof(IOrderedQueryable<T>);
    }

    public static IQueryable<T> SmartOrderBy<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
        if(queryable.IsOrdered()) {
            var orderedQuery = queryable as IOrderedQueryable<T>;
            return orderedQuery.ThenBy(keySelector);
        } else {
            return queryable.OrderBy(keySelector);
        }
    }

    public static IQueryable<T> SmartOrderByDescending<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
        if(queryable.IsOrdered()) {
            var orderedQuery = queryable as IOrderedQueryable<T>;
            return orderedQuery.ThenByDescending(keySelector);
        } else {
            return queryable.OrderByDescending(keySelector);
        }
    }
}
sjkm
źródło
1
Ta odpowiedź jest złota! Połączę sprawdzanie dla query.IsOrders () z odpowiedzią z tego postu, aby mieć jedną metodę, która obiera kierunek sortowania: stackoverflow.com/questions/388708
SwissCoder
1
W ten sposób wdrożenie Linq powinno być na pierwszym miejscu! OrderBy jest źle zaprojektowany ...
Andrzej Martyna
1
@ Marie, najwyraźniej nie rozumiesz przypadku użycia. Jak na przykład dynamicznie tworzysz porządkowanie na podstawie listy właściwości? To jedyny sposób. Proszę nie głosować i ponownie rozważać. Dziękuję Ci.
sjkm
Słusznie. Nadal nie widzę korzyści, ale cofnę swój głos
Marie,
czy to zadziała znullable datetime
aggie
16

Jest co najmniej jeden sposób, aby to zrobić za pomocą LINQ, choć nie najłatwiejszy. Możesz to zrobić przy użyciu OrberBy()metody wykorzystującej IComparer. Najpierw musisz zaimplementować IComparerdla takiej Movieklasy:

public class MovieComparer : IComparer<Movie>
{
    public int Compare(Movie x, Movie y)
    {
        if (x.CategoryId == y.CategoryId)
        {
            return x.Name.CompareTo(y.Name);
        }
        else
        {
            return x.CategoryId.CompareTo(y.CategoryId);
        }
    }
}

Następnie możesz zamówić filmy o następującej składni:

var movies = _db.Movies.OrderBy(item => item, new MovieComparer());

Jeśli chcesz zmienić kolejność na malejącą dla jednego z elementów, po prostu przełącz xiy wewnątrz Compare() metody MovieComparerodpowiednio.

prudentcoder
źródło
1
Lubię to jako bardziej ogólne niż wtedy, ponieważ możesz robić dziwne rzeczy z porównywaniem, w tym mieć różne obiekty porównania z różnymi algorytmami gotowe. Jest to lepsze niż moje preferowane rozwiązanie przed zapoznaniem się z tym, czym było stworzenie klasy, która implementuje interfejs IComparable.
Gerard ONeill
1
Od 2012 r. (.NET wersja 4.5) nie musisz samodzielnie tworzyć klasy MovieComparer; zamiast tego możesz zrobić _db.Movies.OrderBy(item => item, Comparer<Movie>.Create((x, y) => { if (x.CategoryId == y.CategoryId) { return x.Name.CompareTo(y.Name); } else { return x.CategoryId.CompareTo(y.CategoryId); } }));. Oczywiście, jeśli wolisz napisać logikę jako jedno wyrażenie, zamiast if... else, lamda (x, y) => exprmoże być prostsza.
Jeppe Stig Nielsen
3

Jeśli używasz ogólnego repozytorium

> lstModule = _ModuleRepository.GetAll().OrderBy(x => new { x.Level,
> x.Rank}).ToList();

jeszcze

> _db.Module.Where(x=> ......).OrderBy(x => new { x.Level, x.Rank}).ToList();
Saint-Martin Guillaume
źródło
1
Wyrażenia anonimowe zostaną przeanalizowane lokalnie przez rdzeń struktury encji. Wyrażenie LINQ nie mogło zostać przetłumaczone i zostanie ocenione lokalnie.
alhpe