按元素选择器和结果选择器分组

19

Enumerable.GroupByQueryable.GroupBy扩展方法有8个重载。其中两个(用于Enumerable.GroupBy)是:

// (a)
IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TKey, IEnumerable<TSource>, TResult> resultSelector);

// (b)
IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector,
    Func<TKey, IEnumerable<TElement>, TResult> resultSelector);

(针对Queryable.GroupBy,只需使用Expression<Func<... 替换 Func<... 即可。)
(b)有一个额外的elementSelector参数。
在 MSDN 上,重载(a)的示例重载(b)的示例都使用相同的源集合进行操作。
List<Pet> petsList = new List<Pet>
{
    new Pet { Name="Barley", Age=8.3 },
    new Pet { Name="Boots", Age=4.9 },
    new Pet { Name="Whiskers", Age=1.5 },
    new Pet { Name="Daisy", Age=4.3 }
};

例(a)使用以下查询:

var query = petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector
    (age, pets) => new          // resultSelector
    {
        Key = age,
        Count = pets.Count(),
        Min = pets.Min(pet => pet.Age),
        Max = pets.Max(pet => pet.Age)
    });

例子 (b) 使用了这个查询:

var query = petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector
    pet => pet.Age,             // elementSelector
    (baseAge, ages) => new      // resultSelector
    {
        Key = baseAge,
        Count = ages.Count(),
        Min = ages.Min(),
        Max = ages.Max()
    });

两个查询的结果完全相同。

问题1:是否存在一种查询,无论如何都无法仅使用resultSelector表达,并且我真正需要elementSelector?或者说这两种重载的能力始终相等,只是使用一种方式还是另一种方式纯粹是个人喜好的问题?

问题2:在使用LINQ查询语法时,是否有这两个不同重载的对应方法?

(附带问题:当使用Entity Framework的Queryable.GroupBy时,这两个重载会被转换为完全相同的SQL吗?)

1个回答

23

对于 IEnumerable:

petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector
    (age, pets) => new          // resultSelector
    {
        Key = age,
        Count = pets.Count(),
        Min = pets.Min(pet => pet.Age),
        Max = pets.Max(pet => pet.Age)
    });

等同于:

var query = petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector
    pet => pet,             // elementSelector
    (baseAge, ages) => new      // resultSelector
    {
        Key = baseAge,
        Count = ages.Count(),
        Min = ages.Min(pet => pet.Age),
        Max = ages.Max(pet => pet.Age)
    });

使用elementSelector可以简化resultSelector中的表达式(与下一个和上一个进行比较):

var query = petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector
    pet => pet.Age,             // elementSelector
    (baseAge, ages) => new      // resultSelector
    {
        Key = baseAge,
        Count = ages.Count(),
        Min = ages.Min(), //there is no lambda due to element selector
        Max = ages.Max() ////there is no lambda due to element selector
    });

在IQueryable中,情况并不那么简单。您可以查看此方法的源代码:

public static IQueryable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, Expression<Func<TSource, TElement>> elementSelector, Expression<Func<TKey, IEnumerable<TElement>, TResult>> resultSelector)
        {
            if (source == null)
                throw Error.ArgumentNull("source"); 
            if (keySelector == null)
                throw Error.ArgumentNull("keySelector"); 
            if (elementSelector == null) 
                throw Error.ArgumentNull("elementSelector");
            if (resultSelector == null) 
                throw Error.ArgumentNull("resultSelector");
            return source.Provider.CreateQuery<TResult>(
                Expression.Call(
                    null, 
                    ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement), typeof(TResult)),
                    new Expression[] { source.Expression, Expression.Quote(keySelector), Expression.Quote(elementSelector), Expression.Quote(resultSelector) } 
                    )); 
        }

public static IQueryable<TResult> GroupBy<TSource, TKey, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector,Expression<Func<TKey, IEnumerable<TSource>, TResult>> resultSelector)
        {
            if (source == null)
                throw Error.ArgumentNull("source"); 
            if (keySelector == null)
                throw Error.ArgumentNull("keySelector"); 
            if (resultSelector == null) 
                throw Error.ArgumentNull("resultSelector");
            return source.Provider.CreateQuery<TResult>( 
                Expression.Call(
                    null,
                    ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TResult)),
                    new Expression[] { source.Expression, Expression.Quote(keySelector), Expression.Quote(resultSelector) } 
                    ));
        } 

正如您所看到的,它们返回不同的表达式,因此我不确定在所有情况下结果SQL查询将相同,但我认为具有elementSelector + resultSelector重载的SQL查询与没有elementSelector的重载相比不会更慢。

答案1:不,对于IEnumerable,只使用resultSelector就可以表达任何查询。

答案2:没有,在使用LINQ查询语法时不存在两种不同重载的对应方法。扩展方法比LINQ查询语法有更多的可能性。

答案3(针对副问题):不能保证这些重载的SQL查询将是相同的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接