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
40 changes: 20 additions & 20 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,26 @@ public Expression ConvertAnyArrayToObjectArray(Expression arrayExpression)
);
}

/// <inheritdoc/>
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))
Expand Down Expand Up @@ -455,26 +475,6 @@ private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpre
return list;
}

/// <summary>
/// If the types are different (and not null), try to convert the object type to other type.
/// </summary>
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))
Expand Down
8 changes: 6 additions & 2 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,11 @@
// 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);
}
Expand Down Expand Up @@ -1950,8 +1954,8 @@
switch (member)
{
case PropertyInfo property:
var propertyIsStatic = property?.GetGetMethod().IsStatic ?? property?.GetSetMethod().IsStatic ?? false;

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

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.
propertyOrFieldExpression = propertyIsStatic ? Expression.Property(null, property) : Expression.Property(expression, property);

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

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Possible null reference argument for parameter 'property' in 'MemberExpression Expression.Property(Expression? expression, PropertyInfo property)'.

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

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Possible null reference argument for parameter 'property' in 'MemberExpression Expression.Property(Expression? expression, PropertyInfo property)'.
return true;

case FieldInfo field:
Expand Down Expand Up @@ -2551,7 +2555,7 @@
{
return _textParser.TokenIsIdentifier(id);
}

private string GetIdentifier()
{
_textParser.ValidateToken(TokenId.Identifier, Res.IdentifierExpected);
Expand Down
5 changes: 5 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ internal interface IExpressionHelper
Expression GenerateDefaultExpression(Type type);

Expression ConvertAnyArrayToObjectArray(Expression arrayExpression);

/// <summary>
/// If the types are different (and not null), try to convert the object type to other type.
/// </summary>
public bool TryConvertTypes(ref Expression left, ref Expression right);
}
56 changes: 56 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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; }
}

Expand Down
Loading