Skip to content

Commit

Permalink
Update ComparisonOperator logic to support comparing to object (#805)
Browse files Browse the repository at this point in the history
* TryConvertTypes(ref left, ref right);

* ConvertObjectToSupportComparison

* ConvertObjectToSupportComparison

* .
  • Loading branch information
StefH authored Jun 26, 2024
1 parent 8cbe295 commit 4e6c8c8
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 29 deletions.
16 changes: 16 additions & 0 deletions src/System.Linq.Dynamic.Core/Extensions/TokenIdExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Linq.Dynamic.Core.Tokenizer;

namespace System.Linq.Dynamic.Core.Extensions;

internal static class TokenIdExtensions
{
internal static bool IsEqualityOperator(this TokenId tokenId)
{
return tokenId is TokenId.Equal or TokenId.DoubleEqual or TokenId.ExclamationEqual or TokenId.LessGreater;
}

internal static bool IsComparisonOperator(this TokenId tokenId)
{
return tokenId is TokenId.Equal or TokenId.DoubleEqual or TokenId.ExclamationEqual or TokenId.LessGreater or TokenId.GreaterThan or TokenId.GreaterThanEqual or TokenId.LessThan or TokenId.LessThanEqual;
}
}
46 changes: 39 additions & 7 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ public Expression GenerateEqual(Expression left, Expression right)
{
OptimizeForEqualityIfPossible(ref left, ref right);

TryConvertTypes(ref left, ref right);

WrapConstantExpressions(ref left, ref right);

return Expression.Equal(left, right);
Expand All @@ -146,16 +148,20 @@ public Expression GenerateNotEqual(Expression left, Expression right)
{
OptimizeForEqualityIfPossible(ref left, ref right);

TryConvertTypes(ref left, ref right);

WrapConstantExpressions(ref left, ref right);

return Expression.NotEqual(left, right);
}

public Expression GenerateGreaterThan(Expression left, Expression right)
{
TryConvertTypes(ref left, ref right);

if (left.Type == typeof(string))
{
return Expression.GreaterThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0));
return Expression.GreaterThan(GenerateStaticMethodCall(nameof(string.Compare), left, right), Expression.Constant(0));
}

if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum)
Expand All @@ -172,9 +178,11 @@ public Expression GenerateGreaterThan(Expression left, Expression right)

public Expression GenerateGreaterThanEqual(Expression left, Expression right)
{
TryConvertTypes(ref left, ref right);

if (left.Type == typeof(string))
{
return Expression.GreaterThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0));
return Expression.GreaterThanOrEqual(GenerateStaticMethodCall(nameof(string.Compare), left, right), Expression.Constant(0));
}

if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum)
Expand All @@ -192,9 +200,11 @@ public Expression GenerateGreaterThanEqual(Expression left, Expression right)

public Expression GenerateLessThan(Expression left, Expression right)
{
TryConvertTypes(ref left, ref right);

if (left.Type == typeof(string))
{
return Expression.LessThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0));
return Expression.LessThan(GenerateStaticMethodCall(nameof(string.Compare), left, right), Expression.Constant(0));
}

if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum)
Expand All @@ -212,9 +222,11 @@ public Expression GenerateLessThan(Expression left, Expression right)

public Expression GenerateLessThanEqual(Expression left, Expression right)
{
TryConvertTypes(ref left, ref right);

if (left.Type == typeof(string))
{
return Expression.LessThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0));
return Expression.LessThanOrEqual(GenerateStaticMethodCall(nameof(string.Compare), left, right), Expression.Constant(0));
}

if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum)
Expand Down Expand Up @@ -268,14 +280,14 @@ public void OptimizeForEqualityIfPossible(ref Expression left, ref Expression ri
#endif

#if !NET35
if (type == typeof(Guid) && Guid.TryParse(text, out Guid guid))
if (type == typeof(Guid) && Guid.TryParse(text, out var guid))
{
return Expression.Constant(guid, typeof(Guid));
}
#else
try
{
return Expression.Constant(new Guid(text));
return Expression.Constant(new Guid(text!));
}
catch
{
Expand Down Expand Up @@ -399,7 +411,7 @@ private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpre
{
switch (expression)
{
case MemberExpression _:
case MemberExpression:
list.Add(sourceExpression);
break;

Expand Down Expand Up @@ -443,6 +455,26 @@ 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)
{
return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right });
Expand Down
9 changes: 4 additions & 5 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Dynamic.Core.Extensions;
using System.Linq.Dynamic.Core.Parser.SupportedMethods;
using System.Linq.Dynamic.Core.Parser.SupportedOperands;
using System.Linq.Dynamic.Core.Tokenizer;
Expand Down Expand Up @@ -475,17 +476,15 @@ private Expression ParseLogicalAndOrOperator()
private Expression ParseComparisonOperator()
{
Expression left = ParseShiftOperator();
while (_textParser.CurrentToken.Id == TokenId.Equal || _textParser.CurrentToken.Id == TokenId.DoubleEqual ||
_textParser.CurrentToken.Id == TokenId.ExclamationEqual || _textParser.CurrentToken.Id == TokenId.LessGreater ||
_textParser.CurrentToken.Id == TokenId.GreaterThan || _textParser.CurrentToken.Id == TokenId.GreaterThanEqual ||
_textParser.CurrentToken.Id == TokenId.LessThan || _textParser.CurrentToken.Id == TokenId.LessThanEqual)
while (_textParser.CurrentToken.Id.IsComparisonOperator())
{
ConstantExpression? constantExpr;
TypeConverter typeConverter;
Token op = _textParser.CurrentToken;
_textParser.NextToken();
Expression right = ParseShiftOperator();
bool isEquality = op.Id == TokenId.Equal || op.Id == TokenId.DoubleEqual || op.Id == TokenId.ExclamationEqual || op.Id == TokenId.LessGreater;

var isEquality = op.Id.IsEqualityOperator();

if (isEquality && (!left.Type.GetTypeInfo().IsValueType && !right.Type.GetTypeInfo().IsValueType || left.Type == typeof(Guid) && right.Type == typeof(Guid)))
{
Expand Down
33 changes: 33 additions & 0 deletions src/System.Linq.Dynamic.Core/ParsingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,37 @@ public IQueryableAnalyzer QueryableAnalyzer
/// Caches constant expressions to enhance performance. Periodic cleanup is performed to manage cache size, governed by this configuration.
/// </summary>
public CacheConfig? ConstantExpressionCacheConfig { get; set; }

/// <summary>
/// Converts typeof(object) to the correct type to allow comparison (Equal, NotEqual, GreaterThan, GreaterThanEqual, LessThan and LessThanEqual).
///
/// Default value is <c>false</c>.
///
/// When set to <c>true</c>, the following code will work correct:
/// <example>
/// <code>
/// <![CDATA[
/// class Person
/// {
/// public string Name { get; set; }
/// public object Age { get; set; }
/// }
///
/// var persons = new[]
/// {
/// new Person { Name = "Foo", Age = 99 },
/// new Person { Name = "Bar", Age = 33 }
/// }.AsQueryable();
///
/// var config = new ParsingConfig
/// {
/// ConvertObjectToSupportComparison = true
/// };
///
/// var results = persons.Where(config, "Age > 50").ToList();
/// ]]>
/// </code>
/// </example>
/// </summary>
public bool ConvertObjectToSupportComparison { get; set; }
}
2 changes: 1 addition & 1 deletion test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class User

public char C { get; set; }

public UserProfile Profile { get; set; }
public UserProfile? Profile { get; set; }

public UserState State { get; set; }

Expand Down
Loading

0 comments on commit 4e6c8c8

Please sign in to comment.