Skip to content

Commit f92558e

Browse files
authored
Merge pull request #1 from zzzprojects/stef-668-enum-string-wrapped
Stef 668 enum string wrapped
2 parents 5cf8357 + 38fdeab commit f92558e

8 files changed

+259
-17
lines changed

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ public void Wrap(ref Expression expression)
227227
}
228228
}
229229

230-
public bool TryUnwrap<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value)
230+
public bool TryUnwrapAsValue<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value)
231231
{
232232
if (expression?.Expression is ConstantExpression { Value: WrappedValue<TValue> wrapper })
233233
{
@@ -239,6 +239,18 @@ public bool TryUnwrap<TValue>(MemberExpression? expression, [NotNullWhen(true)]
239239
return false;
240240
}
241241

242+
public bool TryUnwrapAsExpression<TValue>(MemberExpression? expression, [NotNullWhen(true)] out ConstantExpression? value)
243+
{
244+
if (TryUnwrapAsValue<TValue>(expression, out var wrappedValue))
245+
{
246+
value = Expression.Constant(wrappedValue);
247+
return true;
248+
}
249+
250+
value = default;
251+
return false;
252+
}
253+
242254
private static MemberExpression Wrap<TValue>(TValue value)
243255
{
244256
var wrapper = new WrappedValue<TValue>(value);

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

+13-2
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,20 @@ public void WrapConstantExpression(ref Expression argument)
2626
}
2727
}
2828

29-
public bool TryUnwrapConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value)
29+
public bool TryUnwrapAsValue<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value)
3030
{
31-
if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrap(expression as MemberExpression, out value))
31+
if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrapAsValue(expression as MemberExpression, out value))
32+
{
33+
return true;
34+
}
35+
36+
value = default;
37+
return false;
38+
}
39+
40+
public bool TryUnwrapAsExpression<TValue>(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value)
41+
{
42+
if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrapAsExpression<string>(expression as MemberExpression, out value))
3243
{
3344
return true;
3445
}

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

+24-10
Original file line numberDiff line numberDiff line change
@@ -476,13 +476,27 @@ private Expression ParseComparisonOperator()
476476
{
477477
left = e;
478478
}
479-
else if (TypeHelper.IsEnumType(left.Type) && (constantExpr = right as ConstantExpression) != null)
479+
else if (TypeHelper.IsEnumType(left.Type))
480480
{
481-
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpr);
481+
if (right is ConstantExpression constantExprRight)
482+
{
483+
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExprRight);
484+
}
485+
else if (_expressionHelper.TryUnwrapAsExpression<string>(right, out var unwrappedConstantExprRight))
486+
{
487+
right = ParseEnumToConstantExpression(op.Pos, left.Type, unwrappedConstantExprRight);
488+
}
482489
}
483-
else if (TypeHelper.IsEnumType(right.Type) && (constantExpr = left as ConstantExpression) != null)
490+
else if (TypeHelper.IsEnumType(right.Type))
484491
{
485-
left = ParseEnumToConstantExpression(op.Pos, right.Type, constantExpr);
492+
if (left is ConstantExpression constantExprLeft)
493+
{
494+
left = ParseEnumToConstantExpression(op.Pos, right.Type, constantExprLeft);
495+
}
496+
else if (_expressionHelper.TryUnwrapAsExpression<string>(left, out var unwrappedConstantExprLeft))
497+
{
498+
left = ParseEnumToConstantExpression(op.Pos, right.Type, unwrappedConstantExprLeft);
499+
}
486500
}
487501
else
488502
{
@@ -498,11 +512,11 @@ private Expression ParseComparisonOperator()
498512
{
499513
left = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueL), right.Type);
500514
}
501-
else if (_expressionHelper.TryUnwrapConstantExpression<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
515+
else if (_expressionHelper.TryUnwrapAsValue<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
502516
{
503517
right = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueR), left.Type);
504518
}
505-
else if (_expressionHelper.TryUnwrapConstantExpression<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
519+
else if (_expressionHelper.TryUnwrapAsValue<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
506520
{
507521
left = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueL), right.Type);
508522
}
@@ -581,7 +595,7 @@ private Expression ParseComparisonOperator()
581595
return left;
582596
}
583597

584-
private bool HasImplicitConversion(Type baseType, Type targetType)
598+
private static bool HasImplicitConversion(Type baseType, Type targetType)
585599
{
586600
var baseTypeHasConversion = baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)
587601
.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
@@ -597,12 +611,12 @@ private bool HasImplicitConversion(Type baseType, Type targetType)
597611
.Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType);
598612
}
599613

600-
private ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)
614+
private static ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)
601615
{
602616
return Expression.Constant(ParseConstantExpressionToEnum(pos, leftType, constantExpr), leftType);
603617
}
604618

605-
private object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExpression constantExpr)
619+
private static object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExpression constantExpr)
606620
{
607621
try
608622
{
@@ -618,7 +632,7 @@ private object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExp
618632

619633
try
620634
{
621-
return Enum.ToObject(TypeHelper.GetNonNullableType(leftType), constantExpr.Value);
635+
return Enum.ToObject(TypeHelper.GetNonNullableType(leftType), constantExpr.Value!);
622636
}
623637
catch
624638
{

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ internal interface IConstantExpressionWrapper
77
{
88
void Wrap(ref Expression expression);
99

10-
bool TryUnwrap<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value);
10+
bool TryUnwrapAsValue<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value);
11+
12+
bool TryUnwrapAsExpression<TValue>(MemberExpression? expression, [NotNullWhen(true)] out ConstantExpression? value);
1113
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ internal interface IExpressionHelper
3535

3636
void WrapConstantExpression(ref Expression argument);
3737

38-
bool TryUnwrapConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value);
38+
bool TryUnwrapAsValue<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value);
39+
40+
bool TryUnwrapAsExpression<TValue>(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value);
3941

4042
bool MemberExpressionIsDynamic(Expression expression);
4143

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

+73-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace System.Linq.Dynamic.Core.Parser;
1+
using System.Collections.Generic;
2+
3+
namespace System.Linq.Dynamic.Core.Parser;
24

35
internal class WrappedValue<TValue>
46
{
@@ -8,4 +10,74 @@ public WrappedValue(TValue value)
810
{
911
Value = value;
1012
}
13+
14+
public static bool operator ==(WrappedValue<TValue>? left, WrappedValue<TValue>? right)
15+
{
16+
if (ReferenceEquals(left, right))
17+
{
18+
return true;
19+
}
20+
21+
if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
22+
{
23+
return false;
24+
}
25+
26+
return EqualityComparer<TValue>.Default.Equals(left.Value, right.Value);
27+
}
28+
29+
public static bool operator !=(WrappedValue<TValue>? left, WrappedValue<TValue>? right)
30+
{
31+
return !(left == right);
32+
}
33+
34+
public static bool operator ==(WrappedValue<TValue>? left, TValue? right)
35+
{
36+
if (ReferenceEquals(left, null))
37+
{
38+
return false;
39+
}
40+
41+
return EqualityComparer<TValue>.Default.Equals(left.Value, right);
42+
}
43+
44+
public static bool operator !=(WrappedValue<TValue>? left, TValue? right)
45+
{
46+
return !(left == right);
47+
}
48+
49+
public static bool operator ==(TValue? left, WrappedValue<TValue>? right)
50+
{
51+
if (ReferenceEquals(right, null))
52+
{
53+
return false;
54+
}
55+
56+
return EqualityComparer<TValue>.Default.Equals(left, right.Value);
57+
}
58+
59+
public static bool operator !=(TValue? left, WrappedValue<TValue>? right)
60+
{
61+
return !(left == right);
62+
}
63+
64+
public override bool Equals(object? obj)
65+
{
66+
if (ReferenceEquals(this, obj))
67+
{
68+
return true;
69+
}
70+
71+
if (obj is not WrappedValue<TValue> other)
72+
{
73+
return false;
74+
}
75+
76+
return EqualityComparer<TValue>.Default.Equals(Value, other.Value);
77+
}
78+
79+
public override int GetHashCode()
80+
{
81+
return Value?.GetHashCode() ?? 0;
82+
}
1183
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.Linq.Dynamic.Core.Parser;
2+
using FluentAssertions;
3+
using Xunit;
4+
5+
namespace System.Linq.Dynamic.Core.Tests.Parser;
6+
7+
public class WrappedValueTests
8+
{
9+
[Fact]
10+
public void WrappedValue_OfTypeString_OperatorEquals_String()
11+
{
12+
// Arrange
13+
var wrapped = new WrappedValue<string>("str");
14+
15+
// Act
16+
var result1A = wrapped == "str";
17+
var result1B = "str" == wrapped;
18+
var result2A = wrapped == "x";
19+
var result2B = "x" == wrapped;
20+
21+
// Assert
22+
result1A.Should().BeTrue();
23+
result1B.Should().BeTrue();
24+
result2A.Should().BeFalse();
25+
result2B.Should().BeFalse();
26+
}
27+
28+
[Fact]
29+
public void WrappedValue_OfTypeString_OperatorNotEquals_String()
30+
{
31+
// Arrange
32+
var wrapped = new WrappedValue<string>("str");
33+
34+
// Act
35+
var result1A = wrapped != "str";
36+
var result1B = "str" != wrapped;
37+
var result2A = wrapped == "x";
38+
var result2B = "x" == wrapped;
39+
40+
// Assert
41+
result1A.Should().BeFalse();
42+
result1B.Should().BeFalse();
43+
result2A.Should().BeFalse();
44+
result2B.Should().BeFalse();
45+
}
46+
47+
[Fact]
48+
public void WrappedValue_OfTypeString_OperatorEquals_WrappedValue_OfTypeString()
49+
{
50+
// Arrange
51+
var wrapped = new WrappedValue<string>("str");
52+
var testEqual = new WrappedValue<string>("str");
53+
var testNotEqual = new WrappedValue<string>("x");
54+
55+
// Act
56+
var result1A = wrapped == testEqual;
57+
var result1B = testEqual == wrapped;
58+
var result2A = wrapped == testNotEqual;
59+
var result2B = testNotEqual == wrapped;
60+
61+
// Assert
62+
result1A.Should().BeTrue();
63+
result1B.Should().BeTrue();
64+
result2A.Should().BeFalse();
65+
result2B.Should().BeFalse();
66+
}
67+
68+
[Fact]
69+
public void WrappedValue_OfTypeString_OperatorNotEquals_WrappedValue_OfTypeString()
70+
{
71+
// Arrange
72+
var wrapped = new WrappedValue<string>("str");
73+
var testEqual = new WrappedValue<string>("str");
74+
var testNotEqual = new WrappedValue<string>("x");
75+
76+
// Act
77+
var result1A = wrapped != testEqual;
78+
var result1B = testEqual != wrapped;
79+
var result2A = wrapped != testNotEqual;
80+
var result2B = testNotEqual != wrapped;
81+
82+
// Assert
83+
result1A.Should().BeFalse();
84+
result1B.Should().BeFalse();
85+
result2A.Should().BeTrue();
86+
result2B.Should().BeTrue();
87+
}
88+
}

test/System.Linq.Dynamic.Core.Tests/QueryableTests.UseParameterizedNamesInDynamicQuery .cs

+42-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public partial class QueryableTests
1010
/// Issue #645
1111
/// </summary>
1212
[Fact]
13-
public void When_UseParameterizedNamesInDynamicQuery_IsTrue_WrappedStringValue_Should_Be_Unwrapped()
13+
public void When_UseParameterizedNamesInDynamicQuery_IsTrue_WrappedStringValueForDateTime_Should_Be_Unwrapped()
1414
{
1515
// Arrange
1616
var list = new List<Customer>
@@ -64,6 +64,39 @@ public void When_UseParameterizedNamesInDynamicQuery_IsTrue_WrappedStringValue_S
6464
// Assert 2B
6565
result2B.Should().HaveCount(1);
6666
}
67+
68+
/// <summary>
69+
/// Issue #668
70+
/// </summary>
71+
[Fact]
72+
public void When_UseParameterizedNamesInDynamicQuery_IsTrue_WrappedStringValueEnum_Should_Be_Unwrapped()
73+
{
74+
// Arrange
75+
var list = new List<Customer>
76+
{
77+
new()
78+
{
79+
Name = "Duffy",
80+
GenderType = Gender.Female
81+
},
82+
new()
83+
{
84+
Name = "Garry",
85+
GenderType = Gender.Male
86+
}
87+
};
88+
89+
var config = new ParsingConfig
90+
{
91+
UseParameterizedNamesInDynamicQuery = true
92+
};
93+
94+
// Act
95+
var result = list.AsQueryable().Where(config, "GenderType = \"Female\"").ToArray();
96+
97+
// Assert
98+
result.Should().HaveCount(1);
99+
}
67100
}
68101

69102
public class Customer
@@ -75,11 +108,19 @@ public class Customer
75108
public string Phone { get; set; }
76109
public Location Location { get; set; }
77110
public DateTimeOffset? LastContact { get; set; }
111+
public Gender GenderType { get; set; }
78112
}
79113

80114
public class Location
81115
{
82116
public int LocationID { get; set; }
83117
public string Name { get; set; }
84118
public DateTimeOffset UpdateAt { get; set; }
119+
}
120+
121+
public enum Gender
122+
{
123+
Male = 0,
124+
Female = 1,
125+
Other = 2
85126
}

0 commit comments

Comments
 (0)