Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ComparisonOperator logic to support comparing to object #805

Merged
merged 13 commits into from
Jun 26, 2024
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