Skip to content

Commit dd7267c

Browse files
authored
Use TryConvertTypes also for strings (#938)
1 parent 19da876 commit dd7267c

File tree

4 files changed

+87
-22
lines changed

4 files changed

+87
-22
lines changed

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

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,26 @@ public Expression ConvertAnyArrayToObjectArray(Expression arrayExpression)
378378
);
379379
}
380380

381+
/// <inheritdoc/>
382+
public bool TryConvertTypes(ref Expression left, ref Expression right)
383+
{
384+
if (!_parsingConfig.ConvertObjectToSupportComparison || left.Type == right.Type || Constants.IsNull(left) || Constants.IsNull(right))
385+
{
386+
return false;
387+
}
388+
389+
if (left.Type == typeof(object))
390+
{
391+
left = Expression.Convert(left, right.Type);
392+
}
393+
else if (right.Type == typeof(object))
394+
{
395+
right = Expression.Convert(right, left.Type);
396+
}
397+
398+
return true;
399+
}
400+
381401
private Expression? GetMemberExpression(Expression? expression)
382402
{
383403
if (ExpressionQualifiesForNullPropagation(expression))
@@ -455,26 +475,6 @@ private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpre
455475
return list;
456476
}
457477

458-
/// <summary>
459-
/// If the types are different (and not null), try to convert the object type to other type.
460-
/// </summary>
461-
private void TryConvertTypes(ref Expression left, ref Expression right)
462-
{
463-
if (!_parsingConfig.ConvertObjectToSupportComparison || left.Type == right.Type || Constants.IsNull(left) || Constants.IsNull(right))
464-
{
465-
return;
466-
}
467-
468-
if (left.Type == typeof(object))
469-
{
470-
left = Expression.Convert(left, right.Type);
471-
}
472-
else if (right.Type == typeof(object))
473-
{
474-
right = Expression.Convert(right, left.Type);
475-
}
476-
}
477-
478478
private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
479479
{
480480
if (!TryGetStaticMethod(methodName, left, right, out var methodInfo))

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,11 @@ private Expression ParseComparisonOperator()
527527
// If left or right is NullLiteral, just continue. Else check if the types differ.
528528
if (!(Constants.IsNull(left) || Constants.IsNull(right)) && left.Type != right.Type)
529529
{
530-
if (left.Type.IsAssignableFrom(right.Type) || HasImplicitConversion(right.Type, left.Type))
530+
if ((left.Type == typeof(object) || right.Type == typeof(object)) && _expressionHelper.TryConvertTypes(ref left, ref right))
531+
{
532+
// #937
533+
}
534+
else if (left.Type.IsAssignableFrom(right.Type) || HasImplicitConversion(right.Type, left.Type))
531535
{
532536
right = Expression.Convert(right, left.Type);
533537
}
@@ -2551,7 +2555,7 @@ private bool TokenIsIdentifier(string id)
25512555
{
25522556
return _textParser.TokenIsIdentifier(id);
25532557
}
2554-
2558+
25552559
private string GetIdentifier()
25562560
{
25572561
_textParser.ValidateToken(TokenId.Identifier, Res.IdentifierExpected);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,9 @@ internal interface IExpressionHelper
4848
Expression GenerateDefaultExpression(Type type);
4949

5050
Expression ConvertAnyArrayToObjectArray(Expression arrayExpression);
51+
52+
/// <summary>
53+
/// If the types are different (and not null), try to convert the object type to other type.
54+
/// </summary>
55+
public bool TryConvertTypes(ref Expression left, ref Expression right);
5156
}

test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Linq.Dynamic.Core.Tests.Helpers.Entities;
66
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
77
using System.Linq.Expressions;
8+
using System.Text;
9+
using Docker.DotNet.Models;
810
using FluentAssertions;
911
using Xunit;
1012

@@ -326,6 +328,56 @@ public void Where_Dynamic_DateTimeConstructor_Issue662()
326328
result2.Should().HaveCount(1);
327329
}
328330

331+
// #937
332+
[Theory]
333+
[InlineData("NameCalculated == \"FooFoo\"", 1)]
334+
[InlineData("\"FooFoo\" == NameCalculated", 1)]
335+
[InlineData("NameCalculated == \"x\"", 0)]
336+
[InlineData("NameCalculated != \"x\"", 2)]
337+
[InlineData("NameCalculated <> \"x\"", 2)]
338+
[InlineData("\"x\" == NameCalculated", 0)]
339+
[InlineData("\"x\" != NameCalculated", 2)]
340+
[InlineData("\"x\" <> NameCalculated", 2)]
341+
public void Where_Dynamic_CompareObjectToString_ConvertObjectToSupportComparisonIsTrue(string expression, int expectedCount)
342+
{
343+
// Arrange
344+
var config = new ParsingConfig
345+
{
346+
ConvertObjectToSupportComparison = true
347+
};
348+
var queryable = new[]
349+
{
350+
new PersonWithObject { Name = "Foo", DateOfBirth = DateTime.UtcNow.AddYears(-31) },
351+
new PersonWithObject { Name = "Bar", DateOfBirth = DateTime.UtcNow.AddYears(-1) }
352+
}.AsQueryable();
353+
354+
// Act
355+
queryable.Where(config, expression).ToList().Should().HaveCount(expectedCount);
356+
}
357+
358+
// #937
359+
[Theory]
360+
[InlineData("NameCalculated == \"FooFoo\"", 0)] // This is the expected behavior when ConvertObjectToSupportComparison is false because "Foo" is a string and NameCalculated is an object which is a calculated string.
361+
[InlineData("\"FooFoo\" == NameCalculated", 0)] // Also expected.
362+
[InlineData("NameCalculated == \"x\"", 0)]
363+
[InlineData("NameCalculated != \"x\"", 2)]
364+
[InlineData("NameCalculated <> \"x\"", 2)]
365+
[InlineData("\"x\" == NameCalculated", 0)]
366+
[InlineData("\"x\" != NameCalculated", 2)]
367+
[InlineData("\"x\" <> NameCalculated", 2)]
368+
public void Where_Dynamic_CompareObjectToString_ConvertObjectToSupportComparisonIsFalse(string expression, int expectedCount)
369+
{
370+
// Arrange
371+
var queryable = new[]
372+
{
373+
new PersonWithObject { Name = "Foo", DateOfBirth = DateTime.UtcNow.AddYears(-31) },
374+
new PersonWithObject { Name = "Bar", DateOfBirth = DateTime.UtcNow.AddYears(-1) }
375+
}.AsQueryable();
376+
377+
// Act
378+
queryable.Where(expression).ToList().Should().HaveCount(expectedCount);
379+
}
380+
329381
// #451
330382
[Theory]
331383
[InlineData("Age == 99", 0)]
@@ -448,7 +500,11 @@ private class PersonWithObject
448500
{
449501
// Deliberately typing these as `object` to illustrate the issue
450502
public object? Name { get; set; }
503+
504+
public object? NameCalculated => Name + Encoding.ASCII.GetString(Convert.FromBase64String("Rm9v")); // "...Foo";
505+
451506
public object Age => Convert.ToInt32(Math.Floor((DateTime.Today.Month - DateOfBirth.Month + 12 * DateTime.Today.Year - 12 * DateOfBirth.Year) / 12d));
507+
452508
public DateTime DateOfBirth { get; set; }
453509
}
454510

0 commit comments

Comments
 (0)