Exception with LINQ OrderBy

本文关键字:OrderBy LINQ with Exception | 更新日期: 2023-09-27 18:17:46

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

虽然问题现在已经解决了,但我仍然想学习如何避免上述错误:)

Exception with LINQ OrderBy

首先要注意的是

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-对象(但不是数据库和其他 linq 查询提供程序(OrderBy通过调用 IComparable<T>(如果可用(工作,否则IComparable(除非我们稍后会谈到的例外(。由于agreementsMatching是内存中的列表,因此这是使用的形式。这就是OrderBy如何知道给定类型的"排序依据"意味着什么。

字符串和所有内置的数字类型(intdouble等(都实现了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:

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(引入内存中。然而:

如果数据库中开始了某些内容,那么大多数情况下最好将其保留在那里尽可能长的时间。有很多例外,但通常希望将内容保留在数据库中并通过将其放入内存作为对少数异常的优化来进行优化,而不是习惯于快速将内容放入内存,然后在 97% 的时间内进行优化,当将其保存在数据库中更快时!

如果需要切换到 linq-to-objects,ToEnumerable() 将执行此操作,而无需急切地执行查询,因此大多数时候它比ToList()要好。

有时ToList()是最有效的(特别是如果你必须两次点击同一个列表(,但你应该在看到对它的明确需求时调用它,而不是默认。