Skip to content
Open
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
17 changes: 12 additions & 5 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2139,7 +2139,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
// Create a new innerIt based on the elementType.
var innerIt = ParameterExpressionHelper.CreateParameterExpression(elementType, string.Empty, _parsingConfig.RenameEmptyParameterExpressionNames);

if (new[] { "Contains", "ContainsKey", "Skip", "Take" }.Contains(methodName))
if (Contains(["Contains", "ContainsKey", "Skip", "Take"], methodName))
{
// For any method that acts on the parent element type, we need to specify the outerIt as scope.
_it = outerIt;
Expand Down Expand Up @@ -2194,7 +2194,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
}

Type[] typeArgs;
if (new[] { "OfType", "Cast" }.Contains(methodName))
if (Contains(["OfType", "Cast"], methodName))
{
if (args.Length != 1)
{
Expand All @@ -2204,7 +2204,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
typeArgs = [ResolveTypeFromArgumentExpression(methodName, args[0])];
args = [];
}
else if (new[] { "Max", "Min", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(methodName))
else if (Contains(["Max", "Min", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy"], methodName))
{
if (args.Length == 2)
{
Expand All @@ -2219,7 +2219,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
typeArgs = [elementType];
}
}
else if (methodName == "SelectMany")
else if (Contains(["SelectMany"], methodName))
{
var bodyType = Expression.Lambda(args[0], innerIt).Body.Type;
var interfaces = bodyType.GetInterfaces().Union([bodyType]);
Expand All @@ -2238,7 +2238,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
}
else
{
if (new[] { "Concat", "Contains", "ContainsKey", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union", "SequenceEqual" }.Contains(methodName))
if (Contains(["Concat", "Contains", "ContainsKey", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union", "SequenceEqual"], methodName))
{
args = [instance, args[0]];
}
Expand All @@ -2259,6 +2259,13 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
return true;
}

private bool Contains(string[] haystack, string needle)
{
return _parsingConfig.IsCaseSensitive
? haystack.Contains(needle)
: haystack.Any(item => item.Equals(needle, StringComparison.OrdinalIgnoreCase));
}

private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null)
{
var argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second ";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,83 @@ public void Parse_InvalidExpressionShouldThrowArgumentException()
// Assert
act.Should().Throw<ArgumentException>().WithMessage("Method 'Compare' not found on type 'System.String' or 'System.Int32'");
}

[Theory]
[InlineData("List.MAX(x => x)", false, "List.Max(Param_0 => Param_0)")]
[InlineData("List.min(x => x)", false, "List.Min(Param_0 => Param_0)")]
[InlineData("List.Min(x => x)", false, "List.Min(Param_0 => Param_0)")]
[InlineData("List.Min(x => x)", true, "List.Min(Param_0 => Param_0)")]
[InlineData("List.WHERE(x => x.Ticks >= 100000).max()", false, "List.Where(Param_0 => (Param_0.Ticks >= 100000)).Max()")]
public void Parse_LinqMethodsRespectCasing(string expression, bool caseSensitive, string result)
{
// Arrange
var parameters = new[] { Expression.Parameter(typeof(DateTime[]), "List") };

var parser = new ExpressionParser(
parameters,
expression,
[],
new ParsingConfig
{
IsCaseSensitive = caseSensitive
});

// Act
var parsedExpression = parser.Parse(typeof(DateTime)).ToString();

// Assert
parsedExpression.Should().Be(result);
}

[Fact]
public void Parse_InvalidCasingShouldThrowInvalidOperationException()
{
// Arrange & Act
var parameters = new[] { Expression.Parameter(typeof(DateTime[]), "List") };

Action act = () => new ExpressionParser(
parameters,
"List.MAX(x => x)",
[],
new ParsingConfig
{
IsCaseSensitive = true
})
.Parse(typeof(DateTime));

// Assert
act.Should().Throw<InvalidOperationException>().WithMessage("No generic method 'MAX' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.*");
}

[Theory]
[InlineData("List.max(x => x.max)", "List.Max(Param_0 => Param_0.max)")]
[InlineData("List.MAX(x => x.max)", "List.Max(Param_0 => Param_0.max)")]
[InlineData("List[0].MAX", "List[0].max")]
[InlineData("List.select(max => max.MAX).max()", "List.Select(Param_0 => Param_0.max).Max()")]
[InlineData("List.max(max)", "List.Max(Param_0 => Param_0.max)")]
public void Parse_LinqMethodsRespectCasingFromModel(string expression, string result)
{
// Arrange
var parameters = new[] { Expression.Parameter(typeof(Model[]), "List") };

var parser = new ExpressionParser(
parameters,
expression,
[],
new ParsingConfig
{
IsCaseSensitive = false
});

// Act
var parsedExpression = parser.Parse(typeof(DateTime)).ToString();

// Assert
parsedExpression.Should().Be(result);
}

private class Model
{
public DateTime max { get; set; }
}
}