diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs index 05b26969..7b0167d8 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs @@ -378,6 +378,26 @@ public Expression ConvertAnyArrayToObjectArray(Expression arrayExpression) ); } + /// + public bool TryConvertTypes(ref Expression left, ref Expression right) + { + if (!_parsingConfig.ConvertObjectToSupportComparison || left.Type == right.Type || Constants.IsNull(left) || Constants.IsNull(right)) + { + return false; + } + + if (left.Type == typeof(object)) + { + left = Expression.Convert(left, right.Type); + } + else if (right.Type == typeof(object)) + { + right = Expression.Convert(right, left.Type); + } + + return true; + } + private Expression? GetMemberExpression(Expression? expression) { if (ExpressionQualifiesForNullPropagation(expression)) @@ -455,26 +475,6 @@ private List CollectExpressions(bool addSelf, Expression sourceExpre return list; } - /// - /// If the types are different (and not null), try to convert the object type to other type. - /// - private void TryConvertTypes(ref Expression left, ref Expression right) - { - if (!_parsingConfig.ConvertObjectToSupportComparison || left.Type == right.Type || Constants.IsNull(left) || Constants.IsNull(right)) - { - return; - } - - if (left.Type == typeof(object)) - { - left = Expression.Convert(left, right.Type); - } - else if (right.Type == typeof(object)) - { - right = Expression.Convert(right, left.Type); - } - } - private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) { if (!TryGetStaticMethod(methodName, left, right, out var methodInfo)) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index eba7fffc..a43b534e 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -527,7 +527,11 @@ private Expression ParseComparisonOperator() // If left or right is NullLiteral, just continue. Else check if the types differ. if (!(Constants.IsNull(left) || Constants.IsNull(right)) && left.Type != right.Type) { - if (left.Type.IsAssignableFrom(right.Type) || HasImplicitConversion(right.Type, left.Type)) + if ((left.Type == typeof(object) || right.Type == typeof(object)) && _expressionHelper.TryConvertTypes(ref left, ref right)) + { + // #937 + } + else if (left.Type.IsAssignableFrom(right.Type) || HasImplicitConversion(right.Type, left.Type)) { right = Expression.Convert(right, left.Type); } @@ -2551,7 +2555,7 @@ private bool TokenIsIdentifier(string id) { return _textParser.TokenIsIdentifier(id); } - + private string GetIdentifier() { _textParser.ValidateToken(TokenId.Identifier, Res.IdentifierExpected); diff --git a/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs index ce4b902e..bbc691cd 100644 --- a/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs @@ -48,4 +48,9 @@ internal interface IExpressionHelper Expression GenerateDefaultExpression(Type type); Expression ConvertAnyArrayToObjectArray(Expression arrayExpression); + + /// + /// If the types are different (and not null), try to convert the object type to other type. + /// + public bool TryConvertTypes(ref Expression left, ref Expression right); } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs index 22d22a72..23f6a963 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs @@ -5,6 +5,8 @@ using System.Linq.Dynamic.Core.Tests.Helpers.Entities; using System.Linq.Dynamic.Core.Tests.Helpers.Models; using System.Linq.Expressions; +using System.Text; +using Docker.DotNet.Models; using FluentAssertions; using Xunit; @@ -326,6 +328,56 @@ public void Where_Dynamic_DateTimeConstructor_Issue662() result2.Should().HaveCount(1); } + // #937 + [Theory] + [InlineData("NameCalculated == \"FooFoo\"", 1)] + [InlineData("\"FooFoo\" == NameCalculated", 1)] + [InlineData("NameCalculated == \"x\"", 0)] + [InlineData("NameCalculated != \"x\"", 2)] + [InlineData("NameCalculated <> \"x\"", 2)] + [InlineData("\"x\" == NameCalculated", 0)] + [InlineData("\"x\" != NameCalculated", 2)] + [InlineData("\"x\" <> NameCalculated", 2)] + public void Where_Dynamic_CompareObjectToString_ConvertObjectToSupportComparisonIsTrue(string expression, int expectedCount) + { + // Arrange + var config = new ParsingConfig + { + ConvertObjectToSupportComparison = true + }; + var queryable = new[] + { + new PersonWithObject { Name = "Foo", DateOfBirth = DateTime.UtcNow.AddYears(-31) }, + new PersonWithObject { Name = "Bar", DateOfBirth = DateTime.UtcNow.AddYears(-1) } + }.AsQueryable(); + + // Act + queryable.Where(config, expression).ToList().Should().HaveCount(expectedCount); + } + + // #937 + [Theory] + [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. + [InlineData("\"FooFoo\" == NameCalculated", 0)] // Also expected. + [InlineData("NameCalculated == \"x\"", 0)] + [InlineData("NameCalculated != \"x\"", 2)] + [InlineData("NameCalculated <> \"x\"", 2)] + [InlineData("\"x\" == NameCalculated", 0)] + [InlineData("\"x\" != NameCalculated", 2)] + [InlineData("\"x\" <> NameCalculated", 2)] + public void Where_Dynamic_CompareObjectToString_ConvertObjectToSupportComparisonIsFalse(string expression, int expectedCount) + { + // Arrange + var queryable = new[] + { + new PersonWithObject { Name = "Foo", DateOfBirth = DateTime.UtcNow.AddYears(-31) }, + new PersonWithObject { Name = "Bar", DateOfBirth = DateTime.UtcNow.AddYears(-1) } + }.AsQueryable(); + + // Act + queryable.Where(expression).ToList().Should().HaveCount(expectedCount); + } + // #451 [Theory] [InlineData("Age == 99", 0)] @@ -448,7 +500,11 @@ private class PersonWithObject { // Deliberately typing these as `object` to illustrate the issue public object? Name { get; set; } + + public object? NameCalculated => Name + Encoding.ASCII.GetString(Convert.FromBase64String("Rm9v")); // "...Foo"; + public object Age => Convert.ToInt32(Math.Floor((DateTime.Today.Month - DateOfBirth.Month + 12 * DateTime.Today.Year - 12 * DateOfBirth.Year) / 12d)); + public DateTime DateOfBirth { get; set; } }