Исключение с LINQ OrderBy - PullRequest
       1

Исключение с LINQ OrderBy

2 голосов
/ 13 декабря 2011

Я все еще новичок в 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 Ответ

8 голосов
/ 13 декабря 2011

Первое, на что нужно обратить внимание, это то, что

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() в вашем вопросе?

Новички в Linq часто звонят ToList(), часто, чтобы держать вещи в структуре, которую они могут изобразить лучше, и частично, потому что многопримеров из учебников будет использовать его интенсивно.

Безусловно, бывают случаи, когда ToList() - единственно разумная вещь, а также случаи, когда необходимо извлечь что-то из формы Linq без памяти (например, для базы данных или XMLDocument).в памяти.Однако:

Если что-то начинается в базе данных, то большую часть времени лучше хранить там как можно дольше.Существует множество исключений из этого, но обычно они стремятся сохранить вещи в базе данных и оптимизировать их, поместив в память в качестве оптимизации для этих нескольких исключений, вместо того, чтобы привыкать быстро вводить вещи в память, а затем оптимизировать их.% времени хранения в базе данных выполняется быстрее!

Если вам нужно переключиться на linq-to-objects, ToEnumerable() сделает это без энергичного выполнения запроса, поэтому лучше, чем ToList()большую часть времени.

Бывают случаи, когда ToList() наиболее эффективен (особенно если вам нужно дважды попасть в один и тот же список), но вы должны вызывать его, когда видите очевидную потребность в нем.не по умолчанию.

...