Skip to content

Commit e931e3e

Browse files
authored
Fix when using LINQ methods like "Any" on a string (#798)
* ? * t * #440
1 parent 404d9f2 commit e931e3e

File tree

4 files changed

+82
-16
lines changed

4 files changed

+82
-16
lines changed

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

+24-8
Original file line numberDiff line numberDiff line change
@@ -1795,19 +1795,25 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string?
17951795

17961796
if (_textParser.CurrentToken.Id == TokenId.OpenParen)
17971797
{
1798+
Expression[]? args = null;
1799+
17981800
var isStaticAccess = expression == null;
17991801

1800-
if (!isStaticAccess && type != typeof(string))
1802+
if (!isStaticAccess)
18011803
{
18021804
var enumerableType = TypeHelper.FindGenericType(typeof(IEnumerable<>), type);
18031805
if (enumerableType != null)
18041806
{
1805-
Type elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0];
1806-
return ParseEnumerable(expression!, elementType, id, errorPos, type);
1807+
var elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0];
1808+
if (TryParseEnumerable(expression!, elementType, id, errorPos, type, out args, out var enumerableExpression))
1809+
{
1810+
return enumerableExpression;
1811+
}
18071812
}
18081813
}
18091814

1810-
Expression[] args = ParseArgumentList();
1815+
// If args is not set by TryParseEnumerable (in case when the expression is not an Enumerable), do parse the argument list here.
1816+
args ??= ParseArgumentList();
18111817
switch (_methodFinder.FindMethod(type, id, isStaticAccess, ref expression, ref args, out var methodBase))
18121818
{
18131819
case 0:
@@ -2038,7 +2044,7 @@ private Expression ParseAsEnumOrNestedClass(string id)
20382044
return ParseMemberAccess(type, null, identifier);
20392045
}
20402046

2041-
private Expression ParseEnumerable(Expression instance, Type elementType, string methodName, int errorPos, Type? type)
2047+
private bool TryParseEnumerable(Expression instance, Type elementType, string methodName, int errorPos, Type? type, out Expression[]? args, [NotNullWhen(true)] out Expression? expression)
20422048
{
20432049
var oldParent = _parent;
20442050

@@ -2057,15 +2063,24 @@ private Expression ParseEnumerable(Expression instance, Type elementType, string
20572063
_it = innerIt;
20582064
}
20592065

2060-
Expression[] args = ParseArgumentList();
2066+
args = ParseArgumentList();
2067+
2068+
var t = type ?? instance.Type;
2069+
if (t == typeof(string) && _methodFinder.ContainsMethod(t, methodName, false, instance, ref args))
2070+
{
2071+
// In case the type is a string, and does contain the methodName (like "IndexOf"), then return false to indicate that the methodName is not an Enumerable method.
2072+
expression = null;
2073+
return false;
2074+
}
20612075

20622076
_it = outerIt;
20632077
_parent = oldParent;
20642078

20652079
if (type != null && TypeHelper.IsDictionary(type) && _methodFinder.ContainsMethod(type, methodName, false))
20662080
{
20672081
var dictionaryMethod = type.GetMethod(methodName)!;
2068-
return Expression.Call(instance, dictionaryMethod, args);
2082+
expression = Expression.Call(instance, dictionaryMethod, args);
2083+
return true;
20692084
}
20702085

20712086
// #794 - Check if the method is an aggregate (Average or Sum) method and try to update the arguments to match the method arguments
@@ -2139,7 +2154,8 @@ private Expression ParseEnumerable(Expression instance, Type elementType, string
21392154
}
21402155
}
21412156

2142-
return Expression.Call(callType, methodName, typeArgs, args);
2157+
expression = Expression.Call(callType, methodName, typeArgs, args);
2158+
return true;
21432159
}
21442160

21452161
private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null)

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

+17-7
Original file line numberDiff line numberDiff line change
@@ -2088,15 +2088,25 @@ public void ExpressionTests_StringEscaping()
20882088
var baseQuery = new[] { new { Value = "ab\"cd" }, new { Value = "a \\ b" } }.AsQueryable();
20892089

20902090
// Act
2091-
var result1 = baseQuery.Where("it.Value == \"ab\\\"cd\"").ToList();
2092-
var result2 = baseQuery.Where("it.Value.IndexOf('\\\\') != -1").ToList();
2093-
2091+
var result = baseQuery.Where("it.Value == \"ab\\\"cd\"").ToList();
2092+
20942093
// Assert
2095-
Assert.Single(result1);
2096-
Assert.Equal("ab\"cd", result1[0].Value);
2094+
Assert.Single(result);
2095+
Assert.Equal("ab\"cd", result[0].Value);
2096+
}
2097+
2098+
[Fact]
2099+
public void ExpressionTests_StringIndexOf()
2100+
{
2101+
// Arrange
2102+
var baseQuery = new[] { new { Value = "ab\"cd" }, new { Value = "a \\ b" } }.AsQueryable();
20972103

2098-
Assert.Single(result2);
2099-
Assert.Equal("a \\ b", result2[0].Value);
2104+
// Act
2105+
var result = baseQuery.Where("it.Value.IndexOf('\\\\') != -1").ToList();
2106+
2107+
// Assert
2108+
Assert.Single(result);
2109+
Assert.Equal("a \\ b", result[0].Value);
21002110
}
21012111

21022112
[Fact]

test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs

+15-1
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,20 @@ public void Parse_ParseInWrappedInParenthesis()
284284
Check.That(parsedExpression).Equals("value(System.Nullable`1[System.Int64][]).Contains(x.MainCompanyId)");
285285
}
286286

287+
[Fact]
288+
public void Parse_CastActingOnIt()
289+
{
290+
// Arrange
291+
var parameters = new[] { ParameterExpressionHelper.CreateParameterExpression(typeof(User), "u") };
292+
var sut = new ExpressionParser(parameters, "DisplayName.Any(int(it) > 109)", null, null);
293+
294+
// Act
295+
var result = sut.Parse(null);
296+
297+
// Assert
298+
result.Should().NotBeNull();
299+
}
300+
287301
[Theory]
288302
[InlineData("string(\"\")", "")]
289303
[InlineData("string(\"a\")", "a")]
@@ -331,7 +345,7 @@ public void Parse_NullableShouldReturnNullable(string expression, object resultT
331345
[InlineData("Company.Equals(null, null)", "Equals(null, null)")]
332346
[InlineData("MainCompany.Name", "company.MainCompany.Name")]
333347
[InlineData("Name", "company.Name")]
334-
[InlineData("company.Name","company.Name")]
348+
[InlineData("company.Name", "company.Name")]
335349
[InlineData("DateTime", "company.DateTime")]
336350
public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsTrue(string expression, string result)
337351
{

test/System.Linq.Dynamic.Core.Tests/QueryableTests.Is,OfType,As,Cast.cs

+26
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,32 @@ public void CastToType_FromStringToInt()
488488
castDynamic.Should().BeEquivalentTo(new[] { 1, 2 });
489489
}
490490

491+
// #440
492+
[Fact]
493+
public void CastToIntUsingParentheses()
494+
{
495+
// Assign
496+
var qry = new[]
497+
{
498+
new User
499+
{
500+
Id = 1,
501+
DisplayName = "" + (char) 109
502+
},
503+
new User
504+
{
505+
Id = 2,
506+
DisplayName = "abc"
507+
}
508+
}.AsQueryable();
509+
510+
// Act
511+
var result = qry.Where("DisplayName.Any(int(it) >= 109)").ToDynamicArray<User>();
512+
513+
// Assert
514+
result.Should().HaveCount(1).And.Subject.First().Id.Should().Be(1);
515+
}
516+
491517
[Fact]
492518
public void CastToType_Dynamic_ActingOnIt()
493519
{

0 commit comments

Comments
 (0)