Skip to content

Commit fab87f5

Browse files
authored
Fix Contains on enum (#992)
* Fix Contains on enum * mssql
1 parent 78dc702 commit fab87f5

4 files changed

Lines changed: 99 additions & 29 deletions

File tree

src-console/ConsoleApp_net10/Program.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,25 @@ public static IEnumerable<MyEntity> CreateList(IEnumerable<int> ids)
4949
public int Id { get; set; }
5050
}
5151

52+
public enum TestEnum
53+
{
54+
T1, T2
55+
}
56+
57+
public class TestClass
58+
{
59+
public TestEnum P1 { get; set; }
60+
61+
public IEnumerable<TestEnum> P2 { get; set; } = [];
62+
}
63+
5264
class Program
5365
{
5466
static void Main(string[] args)
5567
{
68+
Issue963();
69+
return;
70+
5671
Issue987();
5772
return;
5873

@@ -86,6 +101,18 @@ static void Main(string[] args)
86101
Dynamic();
87102
}
88103

104+
private static void Issue963()
105+
{
106+
var list = new List<TestClass>
107+
{
108+
new TestClass { P1 = TestEnum.T1, P2 = [TestEnum.T1, TestEnum.T2] },
109+
new TestClass { P1 = TestEnum.T2, P2 = [TestEnum.T2] }
110+
};
111+
112+
var result1 = list.AsQueryable().Where("\"T1\" in P2").ToArray();
113+
var result2 = list.AsQueryable().Where("P2.Contains(\"T1\")").ToArray();
114+
}
115+
89116
private static void Issue987()
90117
{
91118
var list = new List<MyEntity>();

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

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,11 @@ private Expression ParseIn()
417417
}
418418
else
419419
{
420+
if (TypeHelper.TryGetFirstGenericArgument(right.Type, out var genericRightType))
421+
{
422+
TryConvertExpressionToEnum(genericRightType, ref left, token.Pos);
423+
}
424+
420425
var typeArgs = new[] { left.Type };
421426
var args = new[] { right, left };
422427
accumulate = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), typeArgs, args);
@@ -451,20 +456,8 @@ private Expression ProcessInExpressions(Expression left, Dictionary<Expression,
451456
var tokenPos = expressions.ElementAt(i).Value;
452457

453458
// if the identifier is an Enum (or nullable Enum), try to convert the right-side also to an Enum.
454-
if (TypeHelper.GetNonNullableType(left.Type).GetTypeInfo().IsEnum)
455-
{
456-
if (right is ConstantExpression constantExprRight)
457-
{
458-
right = ParseEnumToConstantExpression(tokenPos, left.Type, constantExprRight);
459-
}
460-
else if (_expressionHelper.TryUnwrapAsConstantExpression(right, out var unwrappedConstantExprRight))
461-
{
462-
right = ParseEnumToConstantExpression(tokenPos, left.Type, unwrappedConstantExprRight);
463-
}
464-
}
465-
466459
// else, check for direct type match
467-
else if (left.Type != right.Type)
460+
if (!TryConvertExpressionToEnum(left.Type, ref right, tokenPos) && left.Type != right.Type)
468461
{
469462
CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, tokenPos);
470463
}
@@ -502,6 +495,26 @@ private Expression ProcessInExpressions(Expression left, Dictionary<Expression,
502495
return _expressionHelper.GenerateBinaryOrElseTree(comparisons);
503496
}
504497

498+
private bool TryConvertExpressionToEnum(Type leftType, ref Expression right, int tokenPos)
499+
{
500+
if (TypeHelper.GetNonNullableType(leftType).GetTypeInfo().IsEnum)
501+
{
502+
if (right is ConstantExpression constantExprRight)
503+
{
504+
right = ParseEnumToConstantExpression(tokenPos, leftType, constantExprRight);
505+
return true;
506+
}
507+
508+
if (_expressionHelper.TryUnwrapAsConstantExpression(right, out var unwrappedConstantExprRight))
509+
{
510+
right = ParseEnumToConstantExpression(tokenPos, leftType, unwrappedConstantExprRight);
511+
return true;
512+
}
513+
}
514+
515+
return false;
516+
}
517+
505518
// &, | bitwise operators
506519
private Expression ParseLogicalAndOrOperator()
507520
{
@@ -2000,7 +2013,7 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string?
20002013

20012014
if (isApplicableForEnumerable &&
20022015
TypeHelper.TryFindGenericType(typeof(IEnumerable<>), type, out var enumerableType) &&
2003-
TryParseEnumerable(expression!, enumerableType, id, type, out args, out var enumerableExpression))
2016+
TryParseEnumerable(expression!, enumerableType, id, type, errorPos, out args, out var enumerableExpression))
20042017
{
20052018
return enumerableExpression;
20062019
}
@@ -2262,7 +2275,7 @@ private Expression ParseAsEnumOrNestedClass(string id)
22622275
return ParseMemberAccess(type, null, identifier);
22632276
}
22642277

2265-
private bool TryParseEnumerable(Expression instance, Type enumerableType, string methodName, Type? type, out Expression[]? args, [NotNullWhen(true)] out Expression? expression)
2278+
private bool TryParseEnumerable(Expression instance, Type enumerableType, string methodName, Type? type, int tokenPos, out Expression[]? args, [NotNullWhen(true)] out Expression? expression)
22662279
{
22672280
var elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0];
22682281

@@ -2379,6 +2392,11 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
23792392
{
23802393
if (new[] { "Concat", "Contains", "ContainsKey", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union", "SequenceEqual" }.Contains(methodName))
23812394
{
2395+
if (TypeHelper.TryGetFirstGenericArgument(instance.Type, out var genericInstanceType))
2396+
{
2397+
TryConvertExpressionToEnum(genericInstanceType, ref args[0], tokenPos);
2398+
}
2399+
23822400
if (args.Length == 1)
23832401
{
23842402
args = [instance, args[0]];

test/System.Linq.Dynamic.Core.Tests/EntitiesTests.DatabaseFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace System.Linq.Dynamic.Core.Tests;
1010
public class EntitiesTestsDatabaseFixture : IAsyncLifetime
1111
{
1212
// https://github.com/microsoft/mssql-docker/issues/892
13-
private readonly Lazy<MsSqlContainer> _msSqlContainer = new(() => new MsSqlBuilder().WithImage("mcr.microsoft.com/mssql/server:2022-latest").Build());
13+
private readonly Lazy<MsSqlContainer> _msSqlContainer = new(() => new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-latest").Build());
1414

1515
public string ConnectionString => _msSqlContainer.Value.GetConnectionString();
1616

test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,30 +1297,56 @@ public void ExpressionTests_HexadecimalInteger()
12971297
Assert.Throws<ParseException>(() => values.AsQueryable().Where("it < 11a"));
12981298
}
12991299

1300+
public class TestClass
1301+
{
1302+
public TestEnum P1 { get; set; }
1303+
1304+
public IEnumerable<TestEnum> P2 { get; set; } = [];
1305+
}
1306+
1307+
[Fact]
1308+
public void ExpressionTests_Enum_Issue963()
1309+
{
1310+
// Arrange
1311+
var list = new List<TestClass>
1312+
{
1313+
new TestClass { P1 = TestEnum.Var1, P2 = [TestEnum.Var1, TestEnum.Var2] },
1314+
new TestClass { P1 = TestEnum.Var2, P2 = [TestEnum.Var2] }
1315+
};
1316+
1317+
// Act
1318+
var result1 = list.AsQueryable().Where("\"Var1\" in P2").ToArray().First();
1319+
var result2 = list.AsQueryable().Where("P2.Contains(\"Var1\")").ToArray().First();
1320+
1321+
// Assert
1322+
Assert.Equal(TestEnum.Var1, result1.P1);
1323+
Assert.Equal(TestEnum.Var1, result2.P1);
1324+
}
1325+
13001326
[Fact]
13011327
public void ExpressionTests_In_Enum()
13021328
{
13031329
// Arrange
1330+
var objectList = new List<string> { "Var1", "Var2" };
13041331
var model1 = new ModelWithEnum { TestEnum = TestEnum.Var1 };
13051332
var model2 = new ModelWithEnum { TestEnum = TestEnum.Var2 };
13061333
var model3 = new ModelWithEnum { TestEnum = TestEnum.Var3 };
13071334
var qry = new[] { model1, model2, model3 }.AsQueryable();
13081335

13091336
// Act
13101337
var expected = qry.Where(x => new[] { TestEnum.Var1, TestEnum.Var2 }.Contains(x.TestEnum)).ToArray();
1311-
var result1 = qry.Where("it.TestEnum in (\"Var1\", \"Var2\")").ToArray();
1312-
var result2 = qry.Where("it.TestEnum in (0, 1)").ToArray();
1313-
var result3 = qry.Where("it.TestEnum in @0", new[] { TestEnum.Var1, TestEnum.Var2 });
1314-
var objectList = new List<string> { "Var1", "Var2" };
1315-
var result4 = qry.Where("it.TestEnum in @0", objectList);
1316-
var result5 = qry.Where("it.TestEnum in @0", GetVar1AndVar2());
1338+
var result01 = qry.Where("it.TestEnum in (\"Var1\", \"Var2\")").ToArray();
1339+
var result02 = qry.Where("it.TestEnum in (0, 1)").ToArray();
1340+
var result03 = qry.Where("it.TestEnum in @0", new[] { TestEnum.Var1, TestEnum.Var2 }).ToArray();
1341+
var result04 = qry.Where("it.TestEnum in @0", objectList).ToArray();
1342+
var result05 = qry.Where("it.TestEnum in @0", GetVar1AndVar2()).ToArray();
13171343

13181344
// Assert
1319-
Assert.Equivalent(result1, expected);
1320-
Assert.Equivalent(result2, expected);
1321-
Assert.Equivalent(result3, expected);
1322-
Assert.Equivalent(result4, expected);
1323-
Assert.Equivalent(result5, expected);
1345+
Assert.Equivalent(result01, expected);
1346+
Assert.Equivalent(result02, expected);
1347+
Assert.Equivalent(result03, expected);
1348+
Assert.Equivalent(result04, expected);
1349+
Assert.Equivalent(result05, expected);
13241350
}
13251351

13261352
private static List<string> GetVar1AndVar2()
@@ -1332,6 +1358,7 @@ private static List<string> GetVar1AndVar2()
13321358
public void ExpressionTests_In_EnumIsNullable()
13331359
{
13341360
// Arrange
1361+
var objectList = new List<string> { "Var1", "Var2" };
13351362
var model1 = new ModelWithEnum { TestEnumNullable = TestEnum.Var1 };
13361363
var model2 = new ModelWithEnum { TestEnumNullable = TestEnum.Var2 };
13371364
var model3 = new ModelWithEnum { TestEnumNullable = TestEnum.Var3 };
@@ -1343,8 +1370,6 @@ public void ExpressionTests_In_EnumIsNullable()
13431370
var result1 = qry.Where("it.TestEnumNullable in (\"Var1\", \"Var2\")").ToArray();
13441371
var result2 = qry.Where("it.TestEnumNullable in (0, 1)").ToArray();
13451372
var result3 = qry.Where("it.TestEnumNullable in @0", new[] { TestEnum.Var1, TestEnum.Var2 });
1346-
1347-
var objectList = new List<string> { "Var1", "Var2" };
13481373
var result4 = qry.Where("it.TestEnumNullable in @0", objectList);
13491374
var result5 = qry.Where("it.TestEnumNullable in @0", GetVar1AndVar2());
13501375

0 commit comments

Comments
 (0)