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

时间:2015-06-24 14:04:22

标签: c# linq

假设您有类似的课程:

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

我有一个这些对象的列表,我想获得最小开始日期和最大结束日期,但我想使用一个linq查询,所以我知道我只是迭代列表一次。

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

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

我可以有两个linq查询来获取最小值和最大值,但我知道在封面下,它需要迭代所有值两次。

我之前看过这样的min和max查询,但是有分组。如果没有分组,并且在单个linq查询中,您将如何做到这一点?

2 个答案:

答案 0 :(得分:1)

  

如果没有分组,并且在单个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

OR

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

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

  

我有这些对象的列表

如果我非常关心性能,我就不会使用Linq。

  

但我想使用一个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相同)。它可以生成对于各种相似但不完全相同的查询非常有效的代码,其中手工编码中的替代方案要么遭受大量浪费,要么编写类似的但是 - 不太相同的方法。

但如果你假设Linq给你一次传球,那么它也可以隐藏一些N + 1或N * N的行为。如果您需要特定的单程行为,请添加到Linq;它是可扩展的。

答案 1 :(得分:-2)

您可以使用MinMax

List<Section> test = new List<Section>();

minStartDate = test.Min(o => o.StartDate);
maxEndDate = test.Max(o => o.EndDate);