LINQ - 编写扩展方法来获取每组具有最大值的行

发布于 2024-11-30 11:24:52 字数 1683 浏览 1 评论 0原文

我的应用程序经常需要对表进行分组,然后返回该组中具有最大值的行。这在 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,这似乎有所作为。

My application frequently needs to group a table, then return the row with the maximum value for that group. This is pretty easy to do in LINQ:

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

Now suppose I want to abstract this out into its own method. I tried writing this:

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);
}

Unfortunately this doesn't compile: maxKeySelector is an expression (so you can't call it on r, and you can't even pass it to Max. So I tried rewriting, making maxKeySelector a function rather than an expression:

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);
}

Now this compiles. But it fails at runtime: "Unsupported overload used for query operator 'Max'." This is what I'm stuck on: I need to find the right way to pass maxKeySelector into Max().

Any suggestions? I'm using LINQ to SQL, which seems to make a difference.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

乞讨 2024-12-07 11:24:52

首先,我想指出,您在 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。这应该与大多数标准 LINQ 提供程序兼容。

First of all, I'd like to point out that what you're trying to do is even easier than you think in LINQ:

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

... which should make our lives a little easier for the second part:

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());
}

The key is that by making your group an IQueryable, you open up a new set of LINQ methods that can take actual expressions rather than taking Funcs. This should be compatible with most standard LINQ providers.

小鸟爱天空丶 2024-12-07 11:24:52

非常有趣。有时,“动态”在纯粹的开发和运行时执行方面可能会花费您比其价值更高的成本(恕我直言)。尽管如此,这是最简单的:

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);

祝你好运!

Very interesting. Sometimes "dynamic" can cost you more in sheer development and runtime execution than it is worth (IMHO). Nonetheless, here's the easiest:

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

And, here's the most dynamic approach:

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());
}

As you can see, I precede my Extension Methods with an underscore. You do not need to do this, of course. Just take the general idea and run with it.

You would call it like this:

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);

Best of luck!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文