Skip to content

Commit e8b5273

Browse files
authored
Fix Aggregate methods Average and Sum (#795)
* Add test * Fix Aggregate methods Average and Sum * public void DynamicExpressionParser_ParseLambda_Aggregate(string operation)
1 parent f8eddd6 commit e8b5273

File tree

3 files changed

+78
-12
lines changed

3 files changed

+78
-12
lines changed

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -2064,6 +2064,9 @@ private Expression ParseEnumerable(Expression instance, Type elementType, string
20642064
return Expression.Call(instance, dictionaryMethod, args);
20652065
}
20662066

2067+
// #794 - Check if the method is an aggregate (Average or Sum) method and try to update the arguments to match the method arguments
2068+
_methodFinder.CheckAggregateMethodAndTryUpdateArgsToMatchMethodArgs(methodName, ref args);
2069+
20672070
var callType = typeof(Enumerable);
20682071
if (type != null && TypeHelper.FindGenericType(typeof(IQueryable<>), type) != null && _methodFinder.ContainsMethod(type, methodName))
20692072
{
@@ -2081,7 +2084,7 @@ private Expression ParseEnumerable(Expression instance, Type elementType, string
20812084
typeArgs = new[] { ResolveTypeFromArgumentExpression(methodName, args[0]) };
20822085
args = new Expression[0];
20832086
}
2084-
else if (new[] { "Min", "Max", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(methodName))
2087+
else if (new[] { "Max", "Min", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(methodName))
20852088
{
20862089
if (args.Length == 2)
20872090
{

src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs

+47-11
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,55 @@ internal class MethodFinder
1010
private readonly ParsingConfig _parsingConfig;
1111
private readonly IExpressionHelper _expressionHelper;
1212

13+
/// <summary>
14+
/// #794
15+
/// </summary>
16+
private interface IAggregateSignatures
17+
{
18+
void Average(decimal? selector);
19+
void Average(decimal selector);
20+
void Average(double? selector);
21+
void Average(double selector);
22+
void Average(float? selector);
23+
void Average(float selector);
24+
void Average(int? selector);
25+
void Average(int selector);
26+
void Average(long? selector);
27+
void Average(long selector);
28+
29+
void Sum(decimal? selector);
30+
void Sum(decimal selector);
31+
void Sum(double? selector);
32+
void Sum(double selector);
33+
void Sum(float? selector);
34+
void Sum(float selector);
35+
void Sum(int? selector);
36+
void Sum(int selector);
37+
void Sum(long? selector);
38+
void Sum(long selector);
39+
}
40+
1341
public MethodFinder(ParsingConfig parsingConfig, IExpressionHelper expressionHelper)
1442
{
1543
_parsingConfig = Check.NotNull(parsingConfig);
1644
_expressionHelper = Check.NotNull(expressionHelper);
1745
}
1846

47+
public void CheckAggregateMethodAndTryUpdateArgsToMatchMethodArgs(string methodName, ref Expression[] args)
48+
{
49+
if (methodName is nameof(IAggregateSignatures.Average) or nameof(IAggregateSignatures.Sum))
50+
{
51+
ContainsMethod(typeof(IAggregateSignatures), methodName, false, null, ref args);
52+
}
53+
}
54+
1955
public bool ContainsMethod(Type type, string methodName, bool staticAccess = true)
2056
{
2157
Check.NotNull(type);
2258

2359
#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD)
24-
var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance);
25-
return type.FindMembers(MemberTypes.Method, flags, Type.FilterNameIgnoreCase, methodName).Any();
60+
var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance);
61+
return type.FindMembers(MemberTypes.Method, flags, Type.FilterNameIgnoreCase, methodName).Any();
2662
#else
2763
return type.GetTypeInfo().DeclaredMethods.Any(m => (m.IsStatic || !staticAccess) && m.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase));
2864
#endif
@@ -40,17 +76,17 @@ public bool ContainsMethod(Type type, string methodName, bool staticAccess, Expr
4076

4177
public int FindMethod(Type? type, string methodName, bool staticAccess, ref Expression? instance, ref Expression[] args, out MethodBase? method)
4278
{
43-
#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD)
44-
BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance);
45-
foreach (Type t in SelfAndBaseTypes(type))
79+
#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD)
80+
BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance);
81+
foreach (Type t in SelfAndBaseTypes(type))
82+
{
83+
MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterNameIgnoreCase, methodName);
84+
int count = FindBestMethodBasedOnArguments(members.Cast<MethodBase>(), ref args, out method);
85+
if (count != 0)
4686
{
47-
MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterNameIgnoreCase, methodName);
48-
int count = FindBestMethodBasedOnArguments(members.Cast<MethodBase>(), ref args, out method);
49-
if (count != 0)
50-
{
51-
return count;
52-
}
87+
return count;
5388
}
89+
}
5490
#else
5591
foreach (Type t in SelfAndBaseTypes(type))
5692
{

test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs

+27
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,33 @@ public override string ToString()
250250
}
251251
}
252252

253+
internal class TestClass794
254+
{
255+
public byte ByteValue { get; set; }
256+
public byte? NullableByteValue { get; set; }
257+
public int IntValue { get; set; }
258+
public int? NullableIntValue { get; set; }
259+
}
260+
261+
[Theory]
262+
[InlineData("Average")]
263+
[InlineData("Max")]
264+
[InlineData("Min")]
265+
[InlineData("Sum")]
266+
public void DynamicExpressionParser_ParseLambda_Aggregate(string operation)
267+
{
268+
foreach (var propertyInfo in typeof(TestClass794).GetProperties())
269+
{
270+
var expression = $"{operation}({propertyInfo.Name})"; // e.g., "Sum(ByteValue)"
271+
272+
// Act on IEnumerable
273+
DynamicExpressionParser.ParseLambda(itType: typeof(IEnumerable<TestClass794>), resultType: typeof(double?), expression: expression);
274+
275+
// Act on IQueryable
276+
DynamicExpressionParser.ParseLambda(itType: typeof(IQueryable<TestClass794>), resultType: typeof(double?), expression: expression);
277+
}
278+
}
279+
253280
[Fact]
254281
public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_false_String()
255282
{

0 commit comments

Comments
 (0)