我有一个具有Nullable DateOfBirth属性的Person对象。是否有一种方法可以使用LINQ来查询Person对象列表中最早/最小的DateOfBirth值?

这是我的开场白:

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

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

但是所有这些对我来说都是将firstBornDate设置为DateTime值。我想要的是与之匹配的Person对象。我是否需要像这样写第二个查询:

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

或者有没有更精简的方法?


当前回答

下面是更通用的解决方案。它本质上做相同的事情(以O(N)顺序),但对任何IEnumerable类型,并且可以与属性选择器可以返回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;
        });
    }
}

测试:

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

其他回答

聚合的简单使用(相当于其他语言中的折叠):

var firstBorn = People.Aggregate((min, x) => x.DateOfBirth < min.DateOfBirth ? x : min);

唯一的缺点是每个sequence元素访问属性两次,这可能会很昂贵。这很难解决。

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

这是一个获取最小值和最大值的简单方法:

    `dbcontext.tableName.Select(x=>x.Feild1).Min()`
    

无需额外包装的解决方案:

var min = lst.OrderBy(i => i.StartDate).FirstOrDefault();
var max = lst.OrderBy(i => i.StartDate).LastOrDefault();

你也可以把它包装成扩展:

public static class LinqExtensions
{
    public static T MinBy<T, TProp>(this IEnumerable<T> source, Func<T, TProp> propSelector)
    {
        return source.OrderBy(propSelector).FirstOrDefault();
    }

    public static T MaxBy<T, TProp>(this IEnumerable<T> source, Func<T, TProp> propSelector)
    {
        return source.OrderBy(propSelector).LastOrDefault();
    }
}

在这种情况下:

var min = lst.MinBy(i => i.StartDate);
var max = lst.MaxBy(i => i.StartDate);

顺便说一下……O(n²)不是最佳解。保罗·贝茨给出的解决方案比我的。但我仍然是LINQ解决方案,它比这里的其他解决方案更简单,更简短。

我自己也在寻找类似的东西,最好不使用库或对整个列表进行排序。我的解决方案与问题本身相似,只是简化了一点。

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