Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1440,7 +1440,7 @@
}
else
{
if (!TryGetMemberName(expr, out propName)) // TODO : investigate this

Check warning on line 1443 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
{
if (expr is MethodCallExpression methodCallExpression
&& methodCallExpression.Arguments.Count == 1
Expand Down Expand Up @@ -1496,7 +1496,7 @@

if (newType != null)
{
return Expression.NewArrayInit(newType, expressions.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true)));

Check warning on line 1499 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Argument of type 'IEnumerable<Expression?>' cannot be used for parameter 'initializers' of type 'IEnumerable<Expression>' in 'NewArrayExpression Expression.NewArrayInit(Type type, IEnumerable<Expression> initializers)' due to differences in the nullability of reference types.

Check warning on line 1499 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Argument of type 'IEnumerable<Expression?>' cannot be used for parameter 'initializers' of type 'IEnumerable<Expression>' in 'NewArrayExpression Expression.NewArrayInit(Type type, IEnumerable<Expression> initializers)' due to differences in the nullability of reference types.

Check warning on line 1499 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Argument of type 'IEnumerable<Expression?>' cannot be used for parameter 'initializers' of type 'IEnumerable<Expression>' in 'NewArrayExpression Expression.NewArrayInit(Type type, IEnumerable<Expression> initializers)' due to differences in the nullability of reference types.
}

return Expression.NewArrayInit(expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object), expressions);
Expand Down Expand Up @@ -1564,7 +1564,7 @@
else
{
Type propertyType = constructorParameters[i].ParameterType;
string cParameterName = constructorParameters[i].Name;

Check warning on line 1567 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Converting null literal or possible null value to non-nullable type.

Check warning on line 1567 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Converting null literal or possible null value to non-nullable type.
var propertyAndIndex = properties.Select((p, index) => new { p, index })
.First(p => p.p.Name == cParameterName && (p.p.Type == propertyType || p.p.Type == Nullable.GetUnderlyingType(propertyType)));
// Promote from Type to Nullable Type if needed
Expand All @@ -1572,7 +1572,7 @@
}
}

return Expression.New(ctor, expressionsPromoted, (IEnumerable<MemberInfo>)propertyInfos);

Check warning on line 1575 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Argument of type 'List<Expression?>' cannot be used for parameter 'arguments' of type 'IEnumerable<Expression>' in 'NewExpression Expression.New(ConstructorInfo constructor, IEnumerable<Expression>? arguments, IEnumerable<MemberInfo>? members)' due to differences in the nullability of reference types.
}
}

Expand All @@ -1586,7 +1586,7 @@
.Select((t, i) => _parsingConfig.ExpressionPromoter.Promote(expressions[i], t.ParameterType, true, true))
.ToArray();

return Expression.New(exactConstructor, expressionsPromoted);

Check warning on line 1589 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Argument of type 'Expression?[]' cannot be used for parameter 'arguments' of type 'Expression[]' in 'NewExpression Expression.New(ConstructorInfo constructor, params Expression[]? arguments)' due to differences in the nullability of reference types.
}

// Option 2. Call the default (empty) constructor and set the members
Expand Down Expand Up @@ -2113,18 +2113,19 @@
}

// #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))
{
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
64 changes: 64 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupBy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading