使用linq在一个查询中获取特定字段的最小值和最大值

本文关键字:获取 字段 最大值 最小值 查询 linq 一个 使用 | 更新日期: 2023-09-27 17:59:20

假设您有一个类,如:

public class Section {
   public DateTime  StartDate;
   public DateTime? EndDate;
}

我有一个这些对象的列表,我想得到最短的开始日期和最长的结束日期,但我想使用一个linq查询,这样我就知道我只在列表上迭代一次。

例如,如果我在没有linq的情况下这样做,我的代码会看起来有点像这样(不检查null):

DateTime? minStartDate;
DateTime? maxEndDate;
foreach(var s in sections) {
     if(s.StartDate < minStartDate) minStartDate = s.StartDate;
     if(s.EndDate > maxEndDate) maxEndDate = s.EndDate;
}

我可以使用两个linq查询来获得最小值和最大值,但我知道在实际情况下,它需要对所有值迭代两次。

我以前见过像这样的最小和最大查询,但使用分组。在没有分组的情况下,在一个linq查询中,您将如何做到这一点?

使用linq在一个查询中获取特定字段的最小值和最大值

在没有分组的情况下,在一个linq查询中,您将如何做到这一点?

如果我必须这么做,那么我会这么做:

var minMax = (from s0 in sections
  from s1 in sections
  orderby s0.StartDate, s1.EndDate descending
  select new {s0.StartDate, s1.EndDate}).FirstOrDefault();

但我也会考虑性能影响,这取决于所讨论的提供商。

在数据库上,我希望它能变成这样:

SELECT s0.StartDate, s1.EndDate
FROM Sections AS s0
CROSS JOIN Sections AS s1
ORDER BY created ASC, EndDate DESC 
LIMIT 1

SELECT TOP 1 s0.StartDate, s1.EndDate
FROM Sections AS s0, Sections AS s1
ORDER BY created ASC, EndDate DESC 

取决于数据库类型。反过来,如何执行可能是两次表扫描,但如果我要关心这些日期,我会在这些列上有索引,所以应该在每个索引的末尾进行两次索引扫描,所以我希望它会很快。

我有这些对象的列表

如果我很在乎表现的话,我就不会用林克了。

但我想使用一个linq查询,这样我就知道我只在列表上迭代一次

这就是为什么我不会用linq。由于linq中没有任何设计来处理这种特殊情况,所以它会打击更糟糕的组合。事实上,它将比2次迭代更糟糕,它将是N+1次迭代,其中N是Sections中的元素数量。Linq提供程序很好,但它们并不是魔术。

如果我真的想在Linq中做到这一点,比如我有时会针对内存中的列表,有时会针对数据库等等,我会添加我自己的方法来尽可能做到最好:

public static Tuple<DateTime, DateTime?> MinStartMaxEnd(this IQueryable<Section> source)
{
  if(source == null)
    return null;
  var minMax = (from s0 in source
  from s1 in source
  orderby s0.StartDate, s1.EndDate descending
  select new {s0.StartDate, s1.EndDate}).FirstOrDefault();
  return minMax == null ? null : Tuple.Create(minMax.StartDate, minMax.EndDate);
}
public static Tuple<DateTime, DateTime?> MinStartMaxEnd(this IEnumerable<Section> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      if(en.MoveNext())
      {
        var cur = en.Current;
        var start = cur.StartDate;
        var end = cur.EndDate;
        while(en.MoveNext())
        {
          cur = en.Current;
          if(cur.StartDate < start)
            start = cur.StartDate;
          if(cur.EndDate.HasValue && (!end.HasValue || cur.EndDate > end))
            end = cur.EndDate;
        }
        return Tuple.Create(start, end);
      }
  return null;
}

但我想使用一个linq查询,这样我就知道我只在列表上迭代一次

回到这个问题上来。Linq不承诺对列表进行一次迭代。它有时可以这样做(或者根本不迭代)。它可以调用数据库查询,然后将概念上的几次迭代转换为一次或两次(CTE常见)。它可以为各种类似但不完全相同的查询生成非常高效的代码,而手头的另一种编码方式是要么遭受大量浪费,要么编写大量类似但不太相同的方法。

但它也可以隐藏一些N+1或N*N的行为,如果你假设林克给你一个单传球,看起来会少很多。如果你需要特定的单传球行为,请添加林克;它是可扩展的。

您可以使用最小值和最大值:

List<Section> test = new List<Section>();
minStartDate = test.Min(o => o.StartDate);
maxEndDate = test.Max(o => o.EndDate);