Исключение с 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() является наиболее эффективным (особенно если вам нужно дважды попасть в один и тот же список), но вы должны вызывать его, когда видите очевидную необходимость в нем, а не по умолчанию.

Другие вопросы по тегам