优雅地组合两个LINQ查询的方法

8
如何将这两个LINQ查询合并为一个?
var maxEndDate = lstDates.Select(s => s.EndDate).Max();    
var record = lstDates.Where(s => s.EndDate == maxEndDate).First();

3
我喜欢现在的样子...易读。 - Ryan Bennett
1
坦白地说,这很好。它非常易读且是O(n)(一般来说你不会做得更好)。你可以看看MaxBy,但这可能并不真正是“一个查询”(如果您的LINQ提供程序是数据库上的包装,则不会将其转换为SQL)。 - jason
@Jason:+1,因为可读性很重要。如果需要高性能,请不要使用LINQ。LINQ适用于使事情清晰明了。 - Tigran
6个回答

8

MaxBy就是你所需要的: http://code.google.com/p/morelinq/source/browse/trunk/MoreLinq/MaxBy.cs 使用方法如下:

var record = lstDates.MaxBy(a => a.EndDate);
编辑1:正如Jason所指出的,该方法仅适用于使用LINQ to Objects时使用。如果您正在查询数据库(因此您正在使用LINQ to SQL或其他内容),则应考虑使用其他方法。
您提供的代码似乎很容易理解,但如果您不满意,您可以在IQueryable对象上调用AsEnumerable方法,然后使用MaxBy方法。
var record = lstDates.AsEnumerable().MaxBy(a => a.EndDate);

编辑 2:在您的查询中,有一件事情可以改变,那就是第二个语句。尝试将其缩短如下(为了避免使用Where):

var record = lstDates.First(s => s.EndDate == maxEndDate);

小心,目前我知道的任何 LINQ 提供程序都无法将此项转换为 SQL。不确定这是否算作“一个查询”。 - jason

2
var record = lstDates.OrderByDescending(d => d.EndDate).First();

2
OP的方法是O(n),而你的方法是O(n log n) - jason
1
性能并不是这个问题的前提条件。如果提问者想要最高性能的选项,他可以更新他的问题。 - Kevin Kalitowski
@Jason,不一定;这取决于所使用的排序算法以及它是否能够在不进行完整排序的情况下产生最大元素。 - Dan Bryant
@Kevin Kalitowski:抱歉,但我认为指出将此归约为一个查询的注意事项很重要(无论是可读性、性能损失还是可能无法由LINQ提供程序转换为SQL)。他可能没有要求注意事项,但这对于完整的答案非常重要。 - jason
1
说到LINQ to SQL,这是一个完美的LINQ to SQL用法,因为它可以使用EndDate列上的索引。 - Kevin Kalitowski

1
请注意,最好不要使用LINQ来完成这个任务。只需遍历列表,跟踪最大日期和首次找到该日期的项即可:
DateTime maxDate = default(DateTime);
YourClass maxItem = null;

foreach (var item in lstDates)
{
    if (item.EndDate > maxDate)
    {
        maxDate = item.EndDate;
        maxItem = item;
    }
}

现在你只需要迭代一次,而不必承受排序的影响。

这假定你正在使用LINQ-to-Objects。如果不是,那么这将从数据库(或其他地方)检索整个集合,这是不可取的。在这种情况下,我会使用你已经有的方法。


1
var record = (from r in lstDates
              orderby r.EndDate descending
              select r).First();

1

针对链接到对象(Linq-to-Sql 不支持聚合操作)。执行时间为 N(n)(好吧,实际上是 O(n+1))

var record = lstDates.Aggregate(lstDates.First(), 
                                (mx, i) => i.EndDate > mx.EndDate ? i : mx));

对于那些阅读有困难的人,第一个参数是累加器的初始值,它通常会聚合列表中的值,但在这里只是保持当前最高记录。然后对于列表中的每个记录,都会调用lambda函数,给出当前最高和下一个项目。它返回新的当前最高值。


哎呀,我正要发布一个类似的解决方案,尽管我同意dlev的观点;如果你正在使用Linq2Objects,根据我的经验,大多数开发人员更喜欢普通的for循环而不是.Aggregate(在我的经验中,.Where/.Select/.OrderBy则相反)。 - Just another metaprogrammer

-1
var record = lstDates.Where(s => s.EndDate == lstDates.Max(v => v.EndDate)).First();

1
这并不是一种改进,而且可以说更糟,因为它不够易读。 - jason
虽然我不反对,但问题并不在于改进代码,而在于如何将它们组合起来。如果这是在 CodeReview 上,我不会给出同样的答案。 - Matt McHugh
实际上,由于每次调用Where时都会执行Max(),我们已经从O(2n)(原始)绕过了O(n ln n)(排序),并达到了O(n * n)。 - James Curran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接