Skip to content

Commit 0f30f42

Browse files
committed
Aggregate (fixed issue #102)
1 parent c0cb88c commit 0f30f42

File tree

5 files changed

+154
-3
lines changed

5 files changed

+154
-3
lines changed

ChangeLog.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
https://github.com/GitTools/GitReleaseNotes
22

3-
GitReleaseNotes.exe . /OutputFile CHANGELOG.md /Version 1.0.7.6
3+
GitReleaseNotes.exe . /OutputFile CHANGELOG.md /Version 1.0.7.7

src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

+54
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,60 @@ private static Expression OptimizeExpression(Expression expression)
4848
return expression;
4949
}
5050

51+
#region Aggregate
52+
/// <summary>
53+
/// Dynamically runs an aggregate function on the IQueryable.
54+
/// </summary>
55+
/// <param name="source">The IQueryable data source.</param>
56+
/// <param name="function">The name of the function to run. Can be Sum, Average, Min or Max.</param>
57+
/// <param name="member">The name of the property to aggregate over.</param>
58+
/// <returns>The value of the aggregate function run over the specified property.</returns>
59+
public static object Aggregate([NotNull] this IQueryable source, [NotNull] string function, [NotNull] string member)
60+
{
61+
Check.NotNull(source, nameof(source));
62+
Check.NotEmpty(function, nameof(function));
63+
Check.NotEmpty(member, nameof(member));
64+
65+
// Properties
66+
PropertyInfo property = source.ElementType.GetProperty(member);
67+
ParameterExpression parameter = Expression.Parameter(source.ElementType, "s");
68+
Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, property), parameter);
69+
// We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
70+
// which is expressed as ( (TSource s) => s.Price );
71+
72+
var methods = typeof(Queryable).GetMethods().Where(x => x.Name == function && x.IsGenericMethod);
73+
74+
// Method
75+
MethodInfo aggregateMethod = methods.SingleOrDefault(m =>
76+
{
77+
ParameterInfo lastParameter = m.GetParameters().LastOrDefault();
78+
79+
return lastParameter != null ? ExpressionParser.GetUnderlyingType(lastParameter.ParameterType) == property.PropertyType : false;
80+
});
81+
82+
// Sum, Average
83+
if (aggregateMethod != null)
84+
{
85+
return source.Provider.Execute(
86+
Expression.Call(
87+
null,
88+
aggregateMethod.MakeGenericMethod(new[] { source.ElementType }),
89+
new[] { source.Expression, Expression.Quote(selector) }));
90+
}
91+
// Min, Max
92+
else
93+
{
94+
aggregateMethod = methods.SingleOrDefault(m => m.Name == function && m.GetGenericArguments().Length == 2);
95+
96+
return source.Provider.Execute(
97+
Expression.Call(
98+
null,
99+
aggregateMethod.MakeGenericMethod(new[] { source.ElementType, property.PropertyType }),
100+
new[] { source.Expression, Expression.Quote(selector) }));
101+
}
102+
}
103+
#endregion Aggregate
104+
51105
#region Any
52106
private static readonly MethodInfo _any = GetMethod(nameof(Queryable.Any));
53107

src/System.Linq.Dynamic.Core/ExpressionParser.cs

+22-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Globalization;
77
using System.Linq.Dynamic.Core.Exceptions;
88
using System.Linq.Dynamic.Core.Tokenizer;
9+
using System.Linq.Dynamic.Core.Validation;
910

1011
namespace System.Linq.Dynamic.Core
1112
{
@@ -1745,22 +1746,42 @@ static bool IsPredefinedType(Type type)
17451746

17461747
public static bool IsNullableType(Type type)
17471748
{
1749+
Check.NotNull(type, nameof(type));
1750+
17481751
return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
17491752
}
17501753

17511754
public static Type ToNullableType(Type type)
17521755
{
1756+
Check.NotNull(type, nameof(type));
1757+
17531758
if (!type.GetTypeInfo().IsValueType || IsNullableType(type))
17541759
throw ParseError(-1, Res.TypeHasNoNullableForm, GetTypeName(type));
17551760

17561761
return typeof(Nullable<>).MakeGenericType(type);
17571762
}
17581763

1759-
static Type GetNonNullableType(Type type)
1764+
public static Type GetNonNullableType(Type type)
17601765
{
1766+
Check.NotNull(type, nameof(type));
1767+
17611768
return IsNullableType(type) ? type.GetTypeInfo().GetGenericTypeArguments()[0] : type;
17621769
}
17631770

1771+
public static Type GetUnderlyingType(Type type)
1772+
{
1773+
Check.NotNull(type, nameof(type));
1774+
1775+
var genericTypeArguments = type.GetGenericArguments();
1776+
if (genericTypeArguments.Any())
1777+
{
1778+
var outerType = GetUnderlyingType(genericTypeArguments.LastOrDefault());
1779+
return Nullable.GetUnderlyingType(type) == outerType ? type : outerType;
1780+
}
1781+
1782+
return type;
1783+
}
1784+
17641785
static string GetTypeName(Type type)
17651786
{
17661787
Type baseType = GetNonNullableType(type);

src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<Description>This is a .NETStandard/ .NET Core port of the the Microsoft assembly for the .Net 4.0 Dynamic language functionality.</Description>
44
<AssemblyTitle>System.Linq.Dynamic.Core</AssemblyTitle>
5-
<VersionPrefix>1.0.7.6</VersionPrefix>
5+
<VersionPrefix>1.0.7.7</VersionPrefix>
66
<Authors>Microsoft;Scott Guthrie;King Wilder;Nathan Arnott;Stef Heyenrath</Authors>
77
<TargetFrameworks>net35;net40;net45;net46;netstandard1.3;uap10.0</TargetFrameworks>
88
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using Xunit;
2+
3+
namespace System.Linq.Dynamic.Core.Tests
4+
{
5+
public partial class QueryableTests
6+
{
7+
[Fact]
8+
public void Aggregate_Average()
9+
{
10+
// Arrange
11+
var queryable = new[]
12+
{
13+
new AggregateTest { Double = 50, Float = 1.0f, Int = 42, NullableDouble = 400, NullableFloat = 100f, NullableInt = 60 },
14+
new AggregateTest { Double = 0, Float = 0.0f, Int = 0, NullableDouble = 0, NullableFloat = 0, NullableInt = 0 }
15+
}.AsQueryable();
16+
17+
// Act
18+
var resultNullableFloat = queryable.Aggregate("Average", "NullableFloat");
19+
var resultFloat = queryable.Aggregate("Average", "Float");
20+
var resultDouble = queryable.Aggregate("Average", "Double");
21+
var resultNullableDouble = queryable.Aggregate("Average", "NullableDouble");
22+
var resultInt = queryable.Aggregate("Average", "Int");
23+
var resultNullableInt = queryable.Aggregate("Average", "NullableInt");
24+
25+
// Assert
26+
Assert.Equal(50f, resultNullableFloat);
27+
Assert.Equal(200.0, resultNullableDouble);
28+
Assert.Equal(25.0, resultDouble);
29+
Assert.Equal(0.5f, resultFloat);
30+
Assert.Equal(21.0, resultInt);
31+
Assert.Equal(30.0, resultNullableInt);
32+
}
33+
34+
[Fact]
35+
public void Aggregate_Min()
36+
{
37+
// Arrange
38+
var queryable = new[]
39+
{
40+
new AggregateTest { Double = 50, Float = 1.0f, Int = 42, NullableDouble = 400, NullableFloat = 100f, NullableInt = 60 },
41+
new AggregateTest { Double = 51, Float = 2.0f, Int = 90, NullableDouble = 800, NullableFloat = 101f, NullableInt = 61 }
42+
}.AsQueryable();
43+
44+
// Act
45+
var resultDouble = queryable.Aggregate("Min", "Double");
46+
var resultFloat = queryable.Aggregate("Min", "Float");
47+
var resultInt = queryable.Aggregate("Min", "Int");
48+
var resultNullableDouble = queryable.Aggregate("Min", "NullableDouble");
49+
var resultNullableFloat = queryable.Aggregate("Min", "NullableFloat");
50+
var resultNullableInt = queryable.Aggregate("Min", "NullableInt");
51+
52+
// Assert
53+
Assert.Equal(50.0, resultDouble);
54+
Assert.Equal(1.0f, resultFloat);
55+
Assert.Equal(42, resultInt);
56+
Assert.Equal(400.0, resultNullableDouble);
57+
Assert.Equal(100f, resultNullableFloat);
58+
Assert.Equal(60, resultNullableInt);
59+
}
60+
61+
public class AggregateTest
62+
{
63+
public double Double { get; set; }
64+
65+
public double? NullableDouble { get; set; }
66+
67+
public float Float { get; set; }
68+
69+
public float? NullableFloat { get; set; }
70+
71+
public int Int { get; set; }
72+
73+
public int? NullableInt { get; set; }
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)