假设您有类似的课程:
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查询中,您将如何做到这一点?
答案 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)