编写一个扩展方法来获取每个组中具有最大值的行

本文关键字:最大值 获取 一个 方法 扩展 | 更新日期: 2023-09-27 18:06:08

我的应用程序经常需要对一个表进行分组,然后返回该组中具有最大值的行。这在LINQ中很容易做到:

myTable.GroupBy(r => r.FieldToGroupBy)
.Select(r => r.Max(s => s.FieldToMaximize))
.Join(
    myTable,
    r => r,
    r => r.FieldToMaximize,
    (o, i) => i)

现在假设我想把它抽象成它自己的方法。我试着这样写:

public static IQueryable<TSource>
SelectMax<TSource, TGroupKey, TMaxKey>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TGroupKey>> groupKeySelector,
    Expression<Func<TSource, TMaxKey>> maxKeySelector)
    where TMaxKey : IComparable
{
    return source
        .GroupBy(groupKeySelector)
        .Join(
            source,
            g => g.Max(maxKeySelector),
            r => maxKeySelector(r),
                (o, i) => i);
}

不幸的是,这不能编译:maxKeySelector是一个表达式(所以你不能在r上调用它,你甚至不能把它传递给Max。所以我试着重写,使maxKeySelector一个函数,而不是一个表达式:

public static IQueryable<TSource>
SelectMax<TSource, TGroupKey, TMaxKey>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TGroupKey>> groupKeySelector,
    Func<TSource, TMaxKey> maxKeySelector)
    where TMaxKey : IComparable
{
    return source
        .GroupBy(groupKeySelector)
        .Join(
            source,
            g => g.Max(maxKeySelector),
            r => maxKeySelector(r),
                (o, i) => i);
}

现在编译。但它在运行时失败:"查询操作符'Max'使用了不支持的过载。"这就是我所坚持的:我需要找到正确的方法来传递maxKeySelector到Max()。

有什么建议吗?我正在使用LINQ to SQL,这似乎有所不同。

编写一个扩展方法来获取每个组中具有最大值的行

首先,我想指出的是,在LINQ:中,您尝试做的事情甚至比您想象的要容易。

myTable.GroupBy(r => r.FieldToGroupBy)
    .Select(g => g.OrderByDescending(r => r.FieldToMaximize).FirstOrDefault())

…这应该会让我们的第二部分更容易一些:

public static IQueryable<TSource>
SelectMax<TSource, TGroupKey, TMaxKey>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TGroupKey>> groupKeySelector,
    Expression<Func<TSource, TMaxKey>> maxKeySelector)
    where TMaxKey : IComparable
{
    return source
        .GroupBy(groupKeySelector)
        .Select(g => g.AsQueryable().OrderBy(maxKeySelector).FirstOrDefault());
}

关键在于,通过使您的组成为IQueryable,您打开了一组新的LINQ方法,这些方法可以接受实际表达式,而不是接受Func

非常有趣。有时候,"动态"可能会让你在开发和运行时执行上付出比其价值更多的代价。尽管如此,下面是最简单的:

public static IQueryable<Item> _GroupMaxs(this IQueryable<Item> list)
{
    return list.GroupBy(x => x.Family)
        .Select(g => g.OrderByDescending(x => x.Value).First());
}

还有,这里是最动态的方法:

public static IQueryable<T> _GroupMaxs<T, TGroupCol, TValueCol>
    (this IQueryable<T> list, string groupColName, string valueColName)
{
    // (x => x.groupColName)
    var _GroupByPropInfo = typeof(T).GetProperty(groupColName);
    var _GroupByParameter = Expression.Parameter(typeof(T), "x");
    var _GroupByProperty = Expression
            .Property(_GroupByParameter, _GroupByPropInfo);
    var _GroupByLambda = Expression.Lambda<Func<T, TGroupCol>>
        (_GroupByProperty, new ParameterExpression[] { _GroupByParameter });
    // (x => x.valueColName)
    var _SelectParameter = Expression.Parameter(typeof(T), "x");
    var _SelectProperty = Expression
            .Property(_SelectParameter, valueColName);
    var _SelectLambda = Expression.Lambda<Func<T, TValueCol>>
        (_SelectProperty, new ParameterExpression[] { _SelectParameter });
    // return list.GroupBy(x => x.groupColName)
    //   .Select(g => g.OrderByDescending(x => x.valueColName).First());
    return list.GroupBy(_GroupByLambda)
        .Select(g => g.OrderByDescending(_SelectLambda.Compile()).First());
}

如您所见,我在扩展方法前面加了下划线。当然,您不需要这样做。你就照我说的做吧。

你可以这样调用它:

public class Item
{
    public string Family { get; set; }
    public int Value { get; set; }
}
foreach (Item item in _List
        .AsQueryable()._GroupMaxs<Item, String, int>("Family", "Value"))
    Console.WriteLine("{0}:{1}", item.Family, item.Value);
祝你好运!
相关文章: