diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index b3c2eaef..63587615 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -2113,7 +2113,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string } // #794 - Check if the method is an aggregate (Average or Sum) method and try to update the arguments to match the method arguments - _methodFinder.CheckAggregateMethodAndTryUpdateArgsToMatchMethodArgs(methodName, ref args); + var isAggregateMethod = _methodFinder.CheckAggregateMethodAndTryUpdateArgsToMatchMethodArgs(methodName, ref args); var callType = typeof(Enumerable); if (TypeHelper.TryFindGenericType(typeof(IQueryable<>), type, out _) && _methodFinder.ContainsMethod(typeof(Queryable), methodName)) @@ -2121,10 +2121,11 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string callType = typeof(Queryable); } - // #633 - For Average without any arguments, try to find the non-generic Average method on the callType for the supplied parameter type. - if (methodName == nameof(Enumerable.Average) && args.Length == 0 && _methodFinder.TryFindAverageMethod(callType, theType, out var averageMethod)) + // #633 / #856 + // For Average/Sum without any arguments, try to find the non-generic Average/Sum method on the callType for the supplied parameter type. + if (isAggregateMethod && args.Length == 0 && _methodFinder.TryFindAggregateMethod(callType, methodName, theType, out var aggregateMethod)) { - expression = Expression.Call(null, averageMethod, instance); + expression = Expression.Call(null, aggregateMethod, instance); return true; } diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs index 15dc151d..32d8315e 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs @@ -45,25 +45,28 @@ public MethodFinder(ParsingConfig parsingConfig, IExpressionHelper expressionHel _expressionHelper = Check.NotNull(expressionHelper); } - public bool TryFindAverageMethod(Type callType, Type parameterType, [NotNullWhen(true)] out MethodInfo? averageMethod) + public bool TryFindAggregateMethod(Type callType, string methodName, Type parameterType, [NotNullWhen(true)] out MethodInfo? aggregateMethod) { - averageMethod = callType + aggregateMethod = callType .GetMethods() - .Where(m => m is { Name: nameof(Enumerable.Average), IsGenericMethodDefinition: false }) + .Where(m => m.Name == methodName && !m.IsGenericMethodDefinition) .SelectMany(m => m.GetParameters(), (m, p) => new { Method = m, Parameter = p }) .Where(x => x.Parameter.ParameterType == parameterType) .Select(x => x.Method) .FirstOrDefault(); - return averageMethod != null; + return aggregateMethod != null; } - public void CheckAggregateMethodAndTryUpdateArgsToMatchMethodArgs(string methodName, ref Expression[] args) + public bool CheckAggregateMethodAndTryUpdateArgsToMatchMethodArgs(string methodName, ref Expression[] args) { if (methodName is nameof(IAggregateSignatures.Average) or nameof(IAggregateSignatures.Sum)) { ContainsMethod(typeof(IAggregateSignatures), methodName, false, null, ref args); + return true; } + + return false; } public bool ContainsMethod(Type type, string methodName, bool staticAccess = true) diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupBy.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupBy.cs index 0c75e7db..6778e199 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupBy.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupBy.cs @@ -275,4 +275,68 @@ public void GroupBy_Dynamic_SelectWhereAverageWithSelector() // Assert resultDynamic.Should().BeEquivalentTo(result); } + + [Fact] + public void GroupBy_Dynamic_SelectWhereSum() + { + // Arrange + var q = new[] + { + new DataSetA + { + I = 5 + }, + new DataSetA + { + I = 7 + } + } + .AsQueryable(); + + // Act + var result = q + .GroupBy(x => x.Time) + .Select(x => new { q = x.Select(d => d.I).Where(d => d != null).Sum() }) + .ToArray(); + + var resultDynamic = q + .GroupBy("Time") + .Select("new (Select(I).Where(it != null).Sum() as q)") + .ToDynamicArray(); + + // Assert + resultDynamic.Should().BeEquivalentTo(result); + } + + [Fact] + public void GroupBy_Dynamic_SelectWhereSumWithSelector() + { + // Arrange + var q = new[] + { + new DataSetA + { + I = 5 + }, + new DataSetA + { + I = 7 + } + } + .AsQueryable(); + + // Act + var result = q + .GroupBy(x => x.Time) + .Select(x => new { q = x.Select(d => d).Sum(y => y.I) }) + .ToArray(); + + var resultDynamic = q + .GroupBy("Time") + .Select("new (Select(it).Sum(I) as q)") + .ToDynamicArray(); + + // Assert + resultDynamic.Should().BeEquivalentTo(result); + } } \ No newline at end of file