Skip to content

Commit 4e6c8c8

Browse files
authored
Update ComparisonOperator logic to support comparing to object (#805)
* TryConvertTypes(ref left, ref right); * ConvertObjectToSupportComparison * ConvertObjectToSupportComparison * .
1 parent 8cbe295 commit 4e6c8c8

File tree

6 files changed

+187
-29
lines changed

6 files changed

+187
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Linq.Dynamic.Core.Tokenizer;
2+
3+
namespace System.Linq.Dynamic.Core.Extensions;
4+
5+
internal static class TokenIdExtensions
6+
{
7+
internal static bool IsEqualityOperator(this TokenId tokenId)
8+
{
9+
return tokenId is TokenId.Equal or TokenId.DoubleEqual or TokenId.ExclamationEqual or TokenId.LessGreater;
10+
}
11+
12+
internal static bool IsComparisonOperator(this TokenId tokenId)
13+
{
14+
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;
15+
}
16+
}

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

+39-7
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ public Expression GenerateEqual(Expression left, Expression right)
137137
{
138138
OptimizeForEqualityIfPossible(ref left, ref right);
139139

140+
TryConvertTypes(ref left, ref right);
141+
140142
WrapConstantExpressions(ref left, ref right);
141143

142144
return Expression.Equal(left, right);
@@ -146,16 +148,20 @@ public Expression GenerateNotEqual(Expression left, Expression right)
146148
{
147149
OptimizeForEqualityIfPossible(ref left, ref right);
148150

151+
TryConvertTypes(ref left, ref right);
152+
149153
WrapConstantExpressions(ref left, ref right);
150154

151155
return Expression.NotEqual(left, right);
152156
}
153157

154158
public Expression GenerateGreaterThan(Expression left, Expression right)
155159
{
160+
TryConvertTypes(ref left, ref right);
161+
156162
if (left.Type == typeof(string))
157163
{
158-
return Expression.GreaterThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0));
164+
return Expression.GreaterThan(GenerateStaticMethodCall(nameof(string.Compare), left, right), Expression.Constant(0));
159165
}
160166

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

173179
public Expression GenerateGreaterThanEqual(Expression left, Expression right)
174180
{
181+
TryConvertTypes(ref left, ref right);
182+
175183
if (left.Type == typeof(string))
176184
{
177-
return Expression.GreaterThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0));
185+
return Expression.GreaterThanOrEqual(GenerateStaticMethodCall(nameof(string.Compare), left, right), Expression.Constant(0));
178186
}
179187

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

193201
public Expression GenerateLessThan(Expression left, Expression right)
194202
{
203+
TryConvertTypes(ref left, ref right);
204+
195205
if (left.Type == typeof(string))
196206
{
197-
return Expression.LessThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0));
207+
return Expression.LessThan(GenerateStaticMethodCall(nameof(string.Compare), left, right), Expression.Constant(0));
198208
}
199209

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

213223
public Expression GenerateLessThanEqual(Expression left, Expression right)
214224
{
225+
TryConvertTypes(ref left, ref right);
226+
215227
if (left.Type == typeof(string))
216228
{
217-
return Expression.LessThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0));
229+
return Expression.LessThanOrEqual(GenerateStaticMethodCall(nameof(string.Compare), left, right), Expression.Constant(0));
218230
}
219231

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

270282
#if !NET35
271-
if (type == typeof(Guid) && Guid.TryParse(text, out Guid guid))
283+
if (type == typeof(Guid) && Guid.TryParse(text, out var guid))
272284
{
273285
return Expression.Constant(guid, typeof(Guid));
274286
}
275287
#else
276288
try
277289
{
278-
return Expression.Constant(new Guid(text));
290+
return Expression.Constant(new Guid(text!));
279291
}
280292
catch
281293
{
@@ -399,7 +411,7 @@ private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpre
399411
{
400412
switch (expression)
401413
{
402-
case MemberExpression _:
414+
case MemberExpression:
403415
list.Add(sourceExpression);
404416
break;
405417

@@ -443,6 +455,26 @@ private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpre
443455
return list;
444456
}
445457

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+
446478
private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
447479
{
448480
return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right });

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

+4-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics.CodeAnalysis;
55
using System.Globalization;
66
using System.Linq.Dynamic.Core.Exceptions;
7+
using System.Linq.Dynamic.Core.Extensions;
78
using System.Linq.Dynamic.Core.Parser.SupportedMethods;
89
using System.Linq.Dynamic.Core.Parser.SupportedOperands;
910
using System.Linq.Dynamic.Core.Tokenizer;
@@ -475,17 +476,15 @@ private Expression ParseLogicalAndOrOperator()
475476
private Expression ParseComparisonOperator()
476477
{
477478
Expression left = ParseShiftOperator();
478-
while (_textParser.CurrentToken.Id == TokenId.Equal || _textParser.CurrentToken.Id == TokenId.DoubleEqual ||
479-
_textParser.CurrentToken.Id == TokenId.ExclamationEqual || _textParser.CurrentToken.Id == TokenId.LessGreater ||
480-
_textParser.CurrentToken.Id == TokenId.GreaterThan || _textParser.CurrentToken.Id == TokenId.GreaterThanEqual ||
481-
_textParser.CurrentToken.Id == TokenId.LessThan || _textParser.CurrentToken.Id == TokenId.LessThanEqual)
479+
while (_textParser.CurrentToken.Id.IsComparisonOperator())
482480
{
483481
ConstantExpression? constantExpr;
484482
TypeConverter typeConverter;
485483
Token op = _textParser.CurrentToken;
486484
_textParser.NextToken();
487485
Expression right = ParseShiftOperator();
488-
bool isEquality = op.Id == TokenId.Equal || op.Id == TokenId.DoubleEqual || op.Id == TokenId.ExclamationEqual || op.Id == TokenId.LessGreater;
486+
487+
var isEquality = op.Id.IsEqualityOperator();
489488

490489
if (isEquality && (!left.Type.GetTypeInfo().IsValueType && !right.Type.GetTypeInfo().IsValueType || left.Type == typeof(Guid) && right.Type == typeof(Guid)))
491490
{

src/System.Linq.Dynamic.Core/ParsingConfig.cs

+33
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,37 @@ public IQueryableAnalyzer QueryableAnalyzer
240240
/// Caches constant expressions to enhance performance. Periodic cleanup is performed to manage cache size, governed by this configuration.
241241
/// </summary>
242242
public CacheConfig? ConstantExpressionCacheConfig { get; set; }
243+
244+
/// <summary>
245+
/// Converts typeof(object) to the correct type to allow comparison (Equal, NotEqual, GreaterThan, GreaterThanEqual, LessThan and LessThanEqual).
246+
///
247+
/// Default value is <c>false</c>.
248+
///
249+
/// When set to <c>true</c>, the following code will work correct:
250+
/// <example>
251+
/// <code>
252+
/// <![CDATA[
253+
/// class Person
254+
/// {
255+
/// public string Name { get; set; }
256+
/// public object Age { get; set; }
257+
/// }
258+
///
259+
/// var persons = new[]
260+
/// {
261+
/// new Person { Name = "Foo", Age = 99 },
262+
/// new Person { Name = "Bar", Age = 33 }
263+
/// }.AsQueryable();
264+
///
265+
/// var config = new ParsingConfig
266+
/// {
267+
/// ConvertObjectToSupportComparison = true
268+
/// };
269+
///
270+
/// var results = persons.Where(config, "Age > 50").ToList();
271+
/// ]]>
272+
/// </code>
273+
/// </example>
274+
/// </summary>
275+
public bool ConvertObjectToSupportComparison { get; set; }
243276
}

test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class User
2828

2929
public char C { get; set; }
3030

31-
public UserProfile Profile { get; set; }
31+
public UserProfile? Profile { get; set; }
3232

3333
public UserState State { get; set; }
3434

0 commit comments

Comments
 (0)