使用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查询中,您将如何做到这一点?
如果我必须这么做,那么我会这么做:
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);