Исключение с LINQ OrderBy
Я все еще новичок в LINQ, и я создал запрос, который я считаю немного неуклюжим. Мне было интересно, есть ли способ упростить это?
Мой запрос выглядит следующим образом:
var agreementsMatching = _ctx.LeasingAgreements
.Where(x => x.Dealer.Id == dealer.dealerId)
.ToList();
var ag = agreementsMatching
.OrderBy(o => o.Model.Specification)
.OrderBy(o => o.Model.ModelName)
.OrderBy(o => o.Model.ModelBrand)
.OrderBy(c => c.LeasingAgreementClicks)
.GroupBy(sg => sg.Model.Specification)
.Select(sg => new { GroupId = sg.Key, Agreements = sg });
Причина, по которой я думаю, что это не самый лучший запрос, заключается также в том, что он дает мне исключение:
По крайней мере, один объект должен реализовывать IComparable.
Я знаю, что это происходит потому, что один или несколько объектов не реализуют IComparable
интерфейс. Я просто не уверен, как на самом деле справиться с этим.
Любая помощь / подсказка очень ценится на этом!
Редактировать: Оказалось, что мне не нужны все эти OrderBy
звонки. Я мог бы просто сделать это:
var agreementsMatching = _ctx.LeasingAgreements
.Where(x => x.Dealer.Id == dealer.dealerId)
.ToList();
var ag = agreementsMatching
.GroupBy(sg => sg.Model.Specification)
.Select(sg => new { GroupId = sg.Key, Agreements = sg });
Хотя проблема решена сейчас, я все же хотел бы узнать, как избежать вышеупомянутой ошибки:)
1 ответ
Первое, что нужно отметить, это то, что
someList.OrderBy(item => item.SomeProp).OrderBy(item => item.SomeOtherProp);
Более или менее эквивалентно:
someList.OrderBy(item => item.SomeOtherProp);
Потому что второй OrderBy
отменяет работу первого. Как правило, вы хотите:
someList.OrderBy(item => item.SomeProp).ThenBy(item => item.SomeOtherProp);
Обратите внимание, что эквивалент:
from item in someList orderby item.SomeProp, item.SomeOtherProp select item
Использует ThenBy
как указано выше.
Теперь с любым синтаксисом, с Linq-to-objects (но не с базой данных и другими поставщиками запросов linq) OrderBy
работает через вызов IComparable<T>
если доступно, и IComparable
в противном случае (за исключением исключения, к которому мы придем позже). поскольку agreementsMatching
это список в памяти, это используемая форма. Вот как OrderBy
может знать, что означает "упорядочить" для данного типа.
Строки и все встроенные числовые типы (int
, double
и т. д.), внедрить IComparable<T>
так что все они могут быть использованы без каких-либо действий с вашей стороны.
Предположительно, по крайней мере, одно из свойств, которые вы заказывали выше, было не одним из этих типов, а вашим собственным типом. Я не могу сказать, что из вашего кода, поэтому я собираюсь сделать следующее:
Я собираюсь предположить, что Specification
собственность вернула Spec
объект, и это Spec
объекты должны быть упорядочены по их Name
свойство без учета регистра в соответствии с инвариантной культурой. Итак, я начинаю с:
class Spec
{
public property Name
{
get { /* code I don't care about here*/ }
set { /* code I don't care about here*/ }
}
/* more code I don't care about here*/
}
Я добавляю реализацию IComparable<Spec>
, Это также хорошая идея для реализации IComparable
в таких случаях для обратной совместимости, хотя это можно пропустить. Оба определяют метод, который сравнивает экземпляр с другим объектом, возвращая число < 0, если экземпляр "меньше" (идет первым по порядку), 0, если они эквивалентны по порядку, и>0, если экземпляр "больше":
class Spec : IComparable<Spec>, IComparable
{
public property Name
{
get { /* code I don't care about here*/ }
set { /* code I don't care about here*/ }
}
/* more code I don't care about here*/
public int CompareTo(Spec other)
{
if(other == null)
return 1;
//Often we make use of an already-existing comparison, though not always
return string.Compare(Name, other.Name, StringComparison.InvariantCultureIgnoreCase)
}
//For backwards compatibility:
public int CompareTo(object other)
{
if(other == null)
return 1;
Spec os = other as Spec;
if(os == null)
throw new ArgumentException("Comparison between Spec and " + other.GetType().FullName + " is not allowed");
return CompareTo(os);
}
}
Сейчас, OrderBy
может обрабатывать сравнения Spec
объекты, которые он будет делать по имени (также мы можем использовать List<Spec>.Sort()
и множество других вещей.
И последнее, что происходит, если нам нужно отсортировать по некоторым другим правилам или если нам нужно отсортировать тип, в котором у нас нет исходного кода и он не реализует IComparable<T>
или же IComparable
?
Здесь мы можем создать класс, который реализует IComparer<T>
и это обойдет использование IComparable<T>
, Вот пример, который демонстрирует это:
public class OddBeforeEven : IComparer<int>
{
public int Compare(int x, int y)
{
int compareOddEven = y % 2 - x % 2;
if(compareOddEven != 0)
return compareOddEven;
//if both odd or both even, use default ordering:
return x.CompareTo(y);
}
}
/* ... */
var oddBeforeEven0To20 = Enumerable.Range(0, 21).OrderBy(x => x, new OddBeforeEven());
/*Enumerating oddBeforeEven0To20 will produce 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20*/
Последнее замечание
Вы уверены, что вам нужно ToList()
в вашем вопросе?
Прибывшие в Линк часто звонят ToList()
Во многом отчасти для того, чтобы держать вещи в структуре, они могут лучше изображать, а отчасти потому, что многие учебные примеры будут интенсивно использовать их.
Есть конечно времена, когда ToList()
это единственное разумное, что нужно сделать, а также случаи, когда необходимо перенести что-то из не связанной с памятью формы Linq (например, в базу данных или XMLDocument) в оперативную память. Тем не мение:
Если что-то начинается в базе данных, то большую часть времени лучше хранить там как можно дольше. Существует множество исключений из этого, но в целом они стремятся сохранить данные в базе данных и оптимизировать их, перенеся в память в качестве оптимизации для этих нескольких исключений, а не в привычку быстро вводить данные в память, а затем оптимизировать их. % времени при хранении в базе данных происходит быстрее!
Если вам нужно переключиться на linq-to-objects, ToEnumerable()
будет делать это без страстного выполнения запроса, так что это лучше, чем ToList()
большую часть времени.
Есть времена, когда ToList()
является наиболее эффективным (особенно если вам нужно дважды попасть в один и тот же список), но вы должны вызывать его, когда видите очевидную необходимость в нем, а не по умолчанию.