Кілька "замовлень" у LINQ


1582

У мене дві таблиці, moviesі categoriesя отримую впорядкований список спочатку по категоріїID, а потім по Імені .

Таблиця фільму містить три стовпці ідентифікатор, ім'я та категоріяID . У таблиці категорій є два стовпці ідентифікатор та ім’я .

Я спробував щось подібне, але це не вийшло.

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

Ось чому це не може працювати: Вираз лямбда в дужках повинен повертати значення, яке може бути використане для замовлення елементів: m.CategoryID - це число, яке можна використовувати для замовлення елементів. Але "m.CategoryID, m.Name" не має сенсу в цьому контексті.
chiccodoro

9
. Тоді те, що ви шукаєте?
eka808

Якщо випадково ви хочете сортувати їх у порядку зменшення, ось тут є шлях.
RBT

Відповіді:


2831

Це повинно працювати для вас:

var movies = _db.Movies.OrderBy(c => c.Category).ThenBy(n => n.Name)

4
Дякую за відповідь звичайно ... Але замість того, Var movies = _db.Movies.Orderby(c => c.Category).ThenBy(n => n.Name) якщо я використовую Var movies = _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name) 2 рази "orderBy", чому результат відрізняється?

147
@devendra, результат інший, тому що другий "OrderBy" працює над колекцією, що є результатом першого "OrderBy", і впорядковує їх елементи

68
Як на землі я пройшов увесь цей час, не знаючи про це ThenBy?! ( Редагувати: схоже, він був представлений у .NET 4.0, що пояснює, як він непомітно проскочив повз мене.)
Джордан Грей

13
Це було з моменту додавання LINQ. Ця відповідь попередньо .NET 4.0.
Натан Ш

10
Так, я зробив висновок, що занадто поспішно на основі 3.5 не знаходитись у спаді версії на сторінці документації ; Мені слід було б увесь погляд на інформацію про версію. Дякуємо за виправлення. :)
Джордан Грей

594

Використовуючи не-лямбда-запит-синтаксис LINQ, ви можете це зробити:

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

[ЗРЕДИТИ на адресу коментаря] Щоб керувати порядком сортування, використовуйте ключові слова ascending(що є типовим і, отже, не особливо корисним) або descending, наприклад:

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

1
У цьому синтаксисі немає способу перевернутись туди-сюди між низхідним та не-таким?
ehdv

1
Власне, ваша відповідь рівнозначна _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Більш правильнеfrom row in _db.Movies orderby row.Category descending orderby row.Name select row
Lodewijk

9
@Lodewijk: Я вважаю, у вас це саме назад. У вашому прикладі буде рядок. Ім'я є основним стовпцем і рядком. Категорія другорядна, що еквівалентно _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Два фрагменти, які ви надаєте, еквівалентні один одному, а не ОП.
Скотт Стаффорд

6
Єдиним недоліком використання синтаксису SQL для Linq є те, що не всі функції підтримуються, більшість, але не всі
Джошуа G

74

Додати новий":

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

Це працює на моїй коробці. Це повертає те, що можна використовувати для сортування. Він повертає об'єкт з двома значеннями.

Аналогічне, але відмінне від сортування за комбінованим стовпцем наступним чином.

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

21
Будьте обережні, використовуючи це для цифр.
WoF_Angel

7
Ви можете використовувати OrderByDescending та OndaBy, або OrderBy та OndaByDescending, залежно від вашої потреби.
Алі Шах Ахмед

6
Я майже впевнений, що .OrderBy( m => new { m.CategoryID, m.Name })і .OrderBy( m => new { m.Name, m.CategoryID })дасть ті самі результати, а не дотримуючись запланованого пріоритету. Іноді, як видається, ви дасте замовлення, яке ви хочете, лише за збігом обставин. Крім того, m.CategoryID.ToString() + m.Nameбуде створено неправильне замовлення, якщо CategoryID є int. Наприклад, щось із id = 123, name = 5 разів з’явиться після id = 1234, name = щось замість раніше. Крім того, неефективно проводити порівняння рядків, де можуть відбуватися порівняння.
AaronLS

7
Коли я намагаюся замовити за анонімним типом, я отримую ArgumentException з повідомленням "Принаймні один об'єкт повинен реалізувати IComparable". Я бачу, що інші повинні оголосити порівняння, роблячи це. Дивіться stackoverflow.com/questions/10356864/… .
Роберт Гоуланд

4
Це абсолютно неправильно. Замовлення за новим анонімним типом, який не має ICompariable реалізації, не може працювати, оскільки немає порядку для властивостей анонімного типу. Невідомо, чи слід сортувати спочатку категоріюID або ім'я, не кажучи вже про те, якби вони були відсортовані у протилежному порядку.
Трайнко

29

використовуйте наступний рядок у вашому DataContext, щоб записати активність SQL на DataContext до консолі - тоді ви зможете точно побачити, що ваші оператори linq вимагають від бази даних:

_db.Log = Console.Out

Наступні твердження LINQ:

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

І

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

виробляють наступний SQL:

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

В той час, як повторення OrderBy в Linq, схоже, повертає результат SQL:

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

І

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

виробляють наступний SQL (ім'я та ідентифікатор категорії переключені):

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

24

Я створив кілька методів розширення (нижче), тому вам не потрібно хвилюватися, чи вже замовлений IQueryable чи ні. Якщо ви хочете замовити кілька об'єктів, виконайте наступне:

// 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);

Це особливо корисно, якщо ви створюєте замовлення динамічно, fe зі списку властивостей для сортування.

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);
        }
    }
}

1
Ця відповідь золота! Я об'єднати чек на queryable.IsOrdered () з відповіддю від цієї посади, щоб мати єдиний метод , який приймає напрямок сортування: stackoverflow.com/questions/388708
SwissCoder

1
Таким чином реалізація Linq повинна йти в першу чергу! OrderBy погано розроблений ...
Анджей Мартина

1
@Marie ви явно не зрозуміли випадку використання. Як, наприклад, динамічно створювати замовлення із списку властивостей? Це єдиний спосіб. Будь ласка, не зволікайте та не переглядайте. Дякую.
sjkm

Справедливо. Я все ще не бачу вигоди, але я візьму свій голос
Марі,

буде працювати з цимnullable datetime
aggie

16

Існує хоча б ще один спосіб зробити це за допомогою LINQ, хоча і не найпростіший. Ви можете зробити це, використовуючи OrberBy()метод, який використовує IComparer. Спочатку потрібно реалізувати клас IComparerдля такого Movieкласу:

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);
        }
    }
}

Потім ви можете замовити фільми з таким синтаксисом:

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

Якщо вам потрібно переключити замовлення на низхідний для одного з елементів, просто перемкніть х і у всередині Compare() методу MovieComparerвідповідно.


1
Мені подобається, що це більш загальне, ніж тоді, оскільки ви можете робити дивні речі з порівнянням, включаючи наявність різних об'єктів порівняння з різними алгоритмами, готовими до роботи. Це краще, ніж моє вподобане рішення, перш ніж дізнатися про тодішньому, який повинен був створити клас, який реалізує інтерфейс IComparable.
Джерард ONeill

1
З 2012 року (.NET версія 4.5) вам не потрібно самостійно створювати клас MovieComparer; натомість ви можете робити _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); } }));. Звичайно, якщо ви віддаєте перевагу писати логіку як один вираз, а не if... else, тоді ламда (x, y) => exprможе бути простішою.
Jeppe Stig Nielsen

3

Якщо використовувати загальне сховище

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

ще

> _db.Module.Where(x=> ......).OrderBy(x => new { x.Level, x.Rank}).ToList();

1
Анонімні вирази будуть проаналізовані локально за допомогою основного ядра сутності. Вираз LINQ неможливо перекласти та буде оцінено локально.
alhpe
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.