【问题标题】:Exception with LINQ OrderByLINQ OrderBy 异常
【发布时间】:2011-12-13 10:12:37
【问题描述】:

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

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

【问题讨论】:

  • 您的规范类是否实现了 IComparable?
  • 嗨 Inu,感谢您的输入 :) 我不认为它可以,因为我使用的是 linq2sql(因此模型是一个 dbml 文件)(?) :)
  • 进行编辑,因为您剩下的问题是关于错误的,并且因为您当时没有使用 linq2sql 而是使用 linq-to-objects(在ToList() 之后,您使用的是列表,而不是数据库)。

标签: c# linq linq-to-objects


【解决方案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 如何知道给定类型的“排序依据”的含义。

字符串和所有内置数字类型(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&lt;Spec&gt;.Sort() 和一大堆其他东西。

最后的问题是,如果我们需要按其他规则排序,或者如果我们需要对没有源代码且它没有实现 IComparable&lt;T&gt;IComparable 的类型进行排序,会发生什么?

这里我们可以创建一个实现IComparer&lt;T&gt;的类,这将绕过IComparable&lt;T&gt;的使用。下面是一个例子来证明这一点:

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() 是最有效的(特别是如果您必须点击同一个列表两次),但您应该在明确需要时调用它,而不是默认调用它。

【讨论】:

  • 谢谢,它对我很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-25
  • 2020-06-10
  • 2011-11-21
  • 1970-01-01
相关资源
最近更新 更多