You don't need anonymous type. You just need a type with the 3 properties Sum
, Count
and Average
. Sum
and Average
type aren't known at design time. So, use Object
type for these 2 properties. Count
is always an int
.
public class Aggregation
{
public Aggregation(object sum, object average, int count)
{
Sum = sum;
Average = average;
Count = count;
}
public object Sum { get; private set; }
public object Average { get; private set; }
public int Count { get; private set; }
}
Like the Sum
extension method described in the article How to do a Sum using Dynamic LINQ, you can write an Aggregate
extension method which compute an Aggregation
class instance from a IQueryable
collection and a property name.
The real difficulty is about determining the Average overload method which match the property type. Overload can't be determined from the return type but from the return type of the lambda expression used as second argument.
For example, if the property type is an int, code has to select the public static double Average<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, int>> selector
)
overload.
public static Aggregation Aggregate(this IQueryable source, string member)
{
if (source == null)
throw new ArgumentNullException("source");
if (member == null)
throw new ArgumentNullException("member");
// Properties
PropertyInfo property = source.ElementType.GetProperty(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "s");
Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, property), parameter);
// We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
// which is expressed as ( (TSource s) => s.Price );
// Methods
MethodInfo sumMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Sum"
&& m.ReturnType == property.PropertyType // should match the type of the property
&& m.IsGenericMethod);
MethodInfo averageMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Average"
&& m.IsGenericMethod
&& m.GetParameters()[1]
.ParameterType
.GetGenericArguments()[0]
.GetGenericArguments()[1] == property.PropertyType);
MethodInfo countMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Count"
&& m.IsGenericMethod);
return new Aggregation(
source.Provider.Execute(
Expression.Call(
null,
sumMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) })),
source.Provider.Execute(
Expression.Call(
null,
averageMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) })),
(int)source.Provider.Execute(
Expression.Call(
null,
countMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression })));
}