如何使用LINQ来select具有最小或最大属性值的对象

我有一个可空的DateOfBirth属性的Person对象。 有没有办法使用LINQ来查询最早/最小的DateOfBirth值的Person对象的列表。

这是我开始的:

var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue)); 

Null DateOfBirth值设置为DateTime.MaxValue,以便将它们排除Min考虑(假设至less有一个具有指定的DOB)。

但所有这一切对我来说是设置firstBornDate为DateTime值。 我想得到的是与之相匹配的Person对象。 我是否需要编写第二个查询,如下所示:

 var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate); 

还是有一个更瘦的方法呢?

 People.Aggregate((curMin, x) => (curMin == null || (x.DateOfBirth ?? DateTime.MaxValue) < curMin.DateOfBirth ? x : curMin)) 

不幸的是,没有一个内置的方法来做到这一点。

PM> Install-Package morelinq

 var firstBorn = People.MinBy(p => p.DateOfBirth ?? DateTime.MaxValue); 

或者,您可以使用MinBy.cs中MoreLINQ中的实现 。 (当然,还有一个相应的MaxBy 。)下面是它的胆量:

 public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) { return source.MinBy(selector, null); } public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer) { if (source == null) throw new ArgumentNullException("source"); if (selector == null) throw new ArgumentNullException("selector"); comparer = comparer ?? Comparer<TKey>.Default; using (var sourceIterator = source.GetEnumerator()) { if (!sourceIterator.MoveNext()) { throw new InvalidOperationException("Sequence contains no elements"); } var min = sourceIterator.Current; var minKey = selector(min); while (sourceIterator.MoveNext()) { var candidate = sourceIterator.Current; var candidateProjected = selector(candidate); if (comparer.Compare(candidateProjected, minKey) < 0) { min = candidate; minKey = candidateProjected; } } return min; } } 

请注意,如果序列为空,则会引发exception,如果有多个,则返回具有最小值的第一个元素。

注意:由于OP没有提及数据源是什么,我们不应该做任何假设。

这个查询给出了正确的答案,但是可能会比较慢,因为它可能必须对People 所有项进行sorting,具体取决于People是什么数据结构:

 var oldest = People.OrderBy(p => p.DateOfBirth ?? DateTime.MaxValue).First(); 

更新:其实我不应该称这个解决scheme“天真”,但用户确实需要知道他在查询什么。 这个解决scheme的“缓慢”取决于底层数据。 如果这是一个数组或List<T> ,那么LINQ to Objects只能在select第一个项目之前先sorting整个集合。 在这种情况下,它会比其他解决schemebuild议的慢。 但是,如果这是一个LINQ to SQL表,并且DateOfBirth是一个索引列,那么SQL Server将使用索引而不是对所有行进行sorting。 其他自定义IEnumerable<T>实现也可以使用索引(请参阅i4o:索引LINQ或对象数据库db4o ),并使此解决scheme比需要迭代整个集合的Aggregate()MaxBy() / MinBy()更快一旦。 实际上,LINQ to Objects可能(理论上)在OrderBy()SortedList<T>这样的sorting集合做了特殊情况,但据我所知,它并不是。

 People.OrderBy(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue)).First() 

会做的伎俩

 public class Foo { public int bar; public int stuff; }; void Main() { List<Foo> fooList = new List<Foo>(){ new Foo(){bar=1,stuff=2}, new Foo(){bar=3,stuff=4}, new Foo(){bar=2,stuff=3}}; Foo result = fooList.Aggregate((u,v) => u.bar < v.bar ? u: v); result.Dump(); } 

没有检查,但这必须做预期的事情:

 var itemWithMaxValue = SomeListOfClass.OrderByDescending(i => i.SomeFloat).FirstOrDefault(); 

min:

 var itemWithMinValue = SomeListOfClass.OrderByDescending(i => i.SomeFloat).LastOrDefault(); 

只有检查了这个Entity framework 6.0.0>:这可以做到:

 var MaxValue = dbContext.YourDataClass.Select(x => x.ColumnToFindMaxValueFrom).Max(); var MinValue = dbContext.YourDataClass.Select(x => x.ColumnToFindMinValueFrom).Min(); 

以下是更通用的解决scheme。 它基本上做同样的事情(在O(N)顺序),但在任何IEnumberabletypes,可以与types的属性select器可以返回null。

 public static class LinqExtensions { public static T MinBy<T>(this IEnumerable<T> source, Func<T, IComparable> selector) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (selector == null) { throw new ArgumentNullException(nameof(selector)); } return source.Aggregate((min, cur) => { if (min == null) { return cur; } var minComparer = selector(min); if (minComparer == null) { return cur; } var curComparer = selector(cur); if (curComparer == null) { return min; } return minComparer.CompareTo(curComparer) > 0 ? cur : min; }); } } 

testing:

 var nullableInts = new int?[] {5, null, 1, 4, 0, 3, null, 1}; Assert.AreEqual(0, nullableInts.MinBy(i => i));//should pass 

再次编辑:

抱歉。 除了缺less可空的我看错了function,

Min <(Of <(TSource,TResult>)>)(IEnumerable <(Of <(TSource>)>)),Func <(Of <(TSource,TResult>)>))返回结果types。

我会说一个可能的解决scheme是实现IComparable和使用Min <(Of <(TSource>)>)(IEnumerable <(Of <(TSource>))>)) ,它确实从IEnumerable返回一个元素。 当然,如果你不能修改这个元素,那对你没有任何帮助。 我觉得MS的devise有点奇怪。

当然,如果你需要,你总是可以做一个for循环,或者使用Jon Skeet提供的MoreLINQ实现。

我正在寻找类似的东西,最好不使用库或sorting整个列表。 我的解决scheme最终类似于问题本身,只是简化了一下。

 var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == People.Min(p2 => p2.DateOfBirth));