Skip to content

Commit c59391a

Browse files
authored
Add support for property for Is,As and Cast (#567)
* Add support for property for Is,As and Cast * . * .
1 parent d077db0 commit c59391a

File tree

5 files changed

+88
-34
lines changed

5 files changed

+88
-34
lines changed

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

+55-28
Original file line numberDiff line numberDiff line change
@@ -904,10 +904,9 @@ Expression ParseIdentifier()
904904
}
905905
else
906906
{
907-
var lambda = expr as LambdaExpression;
908-
if (lambda != null)
907+
if (expr is LambdaExpression lambdaExpression)
909908
{
910-
return ParseLambdaInvocation(lambda);
909+
return ParseLambdaInvocation(lambdaExpression);
911910
}
912911
}
913912

@@ -1021,14 +1020,25 @@ Expression ParseFunctionIs()
10211020

10221021
Expression[] args = ParseArgumentList();
10231022

1024-
if (args.Length != 1)
1023+
if (args.Length != 1 && args.Length != 2)
10251024
{
1026-
throw ParseError(errorPos, Res.FunctionRequiresOneArg, functionName);
1025+
throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName);
10271026
}
10281027

1029-
Type resolvedType = ResolveTypeFromArgumentExpression(functionName, args[0]);
1028+
Expression typeArgument;
1029+
Expression it;
1030+
if (args.Length == 1)
1031+
{
1032+
typeArgument = args[0];
1033+
it = _it;
1034+
}
1035+
else
1036+
{
1037+
typeArgument = args[1];
1038+
it = args[0];
1039+
}
10301040

1031-
return Expression.TypeIs(_it, resolvedType);
1041+
return Expression.TypeIs(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length));
10321042
}
10331043

10341044
// As(...) function
@@ -1040,14 +1050,25 @@ Expression ParseFunctionAs()
10401050

10411051
Expression[] args = ParseArgumentList();
10421052

1043-
if (args.Length != 1)
1053+
if (args.Length != 1 && args.Length != 2)
10441054
{
1045-
throw ParseError(errorPos, Res.FunctionRequiresOneArg, functionName);
1055+
throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName);
10461056
}
10471057

1048-
Type resolvedType = ResolveTypeFromArgumentExpression(functionName, args[0]);
1058+
Expression typeArgument;
1059+
Expression it;
1060+
if (args.Length == 1)
1061+
{
1062+
typeArgument = args[0];
1063+
it = _it;
1064+
}
1065+
else
1066+
{
1067+
typeArgument = args[1];
1068+
it = args[0];
1069+
}
10491070

1050-
return Expression.TypeAs(_it, resolvedType);
1071+
return Expression.TypeAs(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length));
10511072
}
10521073

10531074
// Cast(...) function
@@ -1059,14 +1080,25 @@ Expression ParseFunctionCast()
10591080

10601081
Expression[] args = ParseArgumentList();
10611082

1062-
if (args.Length != 1)
1083+
if (args.Length != 1 && args.Length != 2)
10631084
{
1064-
throw ParseError(errorPos, Res.FunctionRequiresOneArg, functionName);
1085+
throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName);
10651086
}
10661087

1067-
Type resolvedType = ResolveTypeFromArgumentExpression(functionName, args[0]);
1088+
Expression typeArgument;
1089+
Expression it;
1090+
if (args.Length == 1)
1091+
{
1092+
typeArgument = args[0];
1093+
it = _it;
1094+
}
1095+
else
1096+
{
1097+
typeArgument = args[1];
1098+
it = args[0];
1099+
}
10681100

1069-
return Expression.ConvertChecked(_it, resolvedType);
1101+
return Expression.ConvertChecked(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length));
10701102
}
10711103

10721104
Expression GenerateConditional(Expression test, Expression expressionIfTrue, Expression expressionIfFalse, bool nullPropagating, int errorPos)
@@ -1101,8 +1133,7 @@ Expression GenerateConditional(Expression test, Expression expressionIfTrue, Exp
11011133
{
11021134
// - create nullable constant from expressionIfTrue with type from expressionIfFalse
11031135
// - convert expressionIfFalse to nullable (unless it's already nullable)
1104-
1105-
Type nullableType = TypeHelper.ToNullableType(expressionIfFalse.Type);
1136+
var nullableType = TypeHelper.ToNullableType(expressionIfFalse.Type);
11061137
expressionIfTrue = Expression.Constant(null, nullableType);
11071138

11081139
if (!TypeHelper.IsNullableType(expressionIfFalse.Type))
@@ -1343,10 +1374,10 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
13431374
#endif
13441375
}
13451376

1346-
IEnumerable<PropertyInfo> propertyInfos = type.GetProperties();
1377+
var propertyInfos = type.GetProperties();
13471378
if (type.GetTypeInfo().BaseType == typeof(DynamicClass))
13481379
{
1349-
propertyInfos = propertyInfos.Where(x => x.Name != "Item");
1380+
propertyInfos = propertyInfos.Where(x => x.Name != "Item").ToArray();
13501381
}
13511382

13521383
Type[] propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray();
@@ -1836,8 +1867,9 @@ Expression ParseEnumerable(Expression instance, Type elementType, string methodN
18361867
return Expression.Call(callType, methodName, typeArgs, args);
18371868
}
18381869

1839-
private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression)
1870+
private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null)
18401871
{
1872+
string argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second ";
18411873
switch (argumentExpression)
18421874
{
18431875
case ConstantExpression constantExpression:
@@ -1850,21 +1882,16 @@ private Type ResolveTypeFromArgumentExpression(string functionName, Expression a
18501882
return type;
18511883

18521884
default:
1853-
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresOneNotNullArgOfType, functionName, "string or System.Type");
1885+
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresNotNullArgOfType, functionName, argument, "string or System.Type");
18541886
}
18551887

18561888
default:
1857-
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresOneNotNullArgOfType, functionName, "ConstantExpression");
1889+
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresNotNullArgOfType, functionName, argument, "ConstantExpression");
18581890
}
18591891
}
18601892

18611893
private Type ResolveTypeStringFromArgument(string functionName, string typeName)
18621894
{
1863-
if (string.IsNullOrEmpty(typeName))
1864-
{
1865-
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresOneNotNullArg, functionName, typeName);
1866-
}
1867-
18681895
Type resultType = _typeFinder.FindTypeByName(typeName, new[] { _it, _parent, _root }, true);
18691896
if (resultType == null)
18701897
{
@@ -1962,7 +1989,7 @@ static bool TryGetMemberName(Expression expression, out string memberName)
19621989
var memberExpression = expression as MemberExpression;
19631990
if (memberExpression == null && expression.NodeType == ExpressionType.Coalesce)
19641991
{
1965-
memberExpression = (expression as BinaryExpression).Left as MemberExpression;
1992+
memberExpression = (expression as BinaryExpression)?.Left as MemberExpression;
19661993
}
19671994

19681995
if (memberExpression != null)

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ public Type FindTypeByName(string name, ParameterExpression[] expressions, bool
2424

2525
_keywordsHelper.TryGetValue(name, out object type);
2626

27-
Type result = type as Type;
28-
if (result != null)
27+
if (type is Type result)
2928
{
3029
return result;
3130
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ internal static class Res
2929
public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'";
3030
public const string FunctionRequiresOneArg = "The '{0}' function requires one argument";
3131
public const string FunctionRequiresOneNotNullArg = "The '{0}' function requires one argument which is not null.";
32-
public const string FunctionRequiresOneNotNullArgOfType = "The '{0}' function requires one argument of type {1} which is not null.";
32+
public const string FunctionRequiresNotNullArgOfType = "The '{0}' function requires the {1}argument to be not null and of type {2}.";
33+
public const string FunctionRequiresOneOrTwoArgs = "The '{0}' function requires 1 or 2 arguments";
3334
public const string HexCharExpected = "Hexadecimal character expected";
3435
public const string IQueryableProviderNotAsync = "The provider for the source IQueryable doesn't implement IAsyncQueryProvider/IDbAsyncQueryProvider. Only providers that implement IAsyncQueryProvider/IDbAsyncQueryProvider can be used for Entity Framework asynchronous operations.";
3536
public const string IdentifierExpected = "Identifier expected";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace System.Linq.Dynamic.Core.Tests.Entities
2+
{
3+
public class EmployeeWrapper
4+
{
5+
public BaseEmployee Employee { get; set; }
6+
}
7+
}

test/System.Linq.Dynamic.Core.Tests/QueryableTests.Is,OfType,As,Cast.cs

+23-3
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,8 @@ public void CastToType_Dynamic_ActingOnIt()
289289
// Assign
290290
var qry = new BaseEmployee[]
291291
{
292-
new Worker { Name = "1", Other = "x" }, new Worker { Name = "2" }
292+
new Worker { Name = "1", Other = "x" },
293+
new Worker { Name = "2" }
293294
}.AsQueryable();
294295

295296
// Act
@@ -306,17 +307,36 @@ public void IsAndCastToType_Dynamic_ActingOnIt_And_GetProperty()
306307
// Assign
307308
var qry = new BaseEmployee[]
308309
{
309-
new Worker { Name = "1", Other = "x" }, new Boss { Name = "2", Function = "y" }
310+
new Worker { Name = "1", Other = "x" },
311+
new Boss { Name = "2", Function = "y" }
310312
}.AsQueryable();
311313

312314
// Act
313-
var cast = qry.Select(c => (c is Worker) ? (c as Worker).Other : "-").ToArray();
315+
var cast = qry.Select(c => c is Worker ? ((Worker)c).Other : "-").ToArray();
314316
var castDynamic = qry.Select("iif(Is(\"System.Linq.Dynamic.Core.Tests.Entities.Worker\"), Cast(\"System.Linq.Dynamic.Core.Tests.Entities.Worker\").Other, \"-\")").ToDynamicArray();
315317

316318
// Assert
317319
Check.That(cast.Length).Equals(castDynamic.Length);
318320
}
319321

322+
[Fact]
323+
public void IsAndCastToType_Dynamic_ActingOnProperty_And_GetProperty()
324+
{
325+
// Assign
326+
var qry = new []
327+
{
328+
new EmployeeWrapper { Employee = new Worker { Name = "1", Other = "x" } },
329+
new EmployeeWrapper { Employee = new Boss { Name = "2", Function = "y" } }
330+
}.AsQueryable();
331+
332+
// Act
333+
var cast = qry.Select(c => c.Employee is Worker ? ((Worker) c.Employee).Other : "-").ToArray();
334+
var castDynamic = qry.Select("iif(Is(Employee, \"System.Linq.Dynamic.Core.Tests.Entities.Worker\"), Cast(Employee, \"System.Linq.Dynamic.Core.Tests.Entities.Worker\").Other, \"-\")").ToDynamicArray();
335+
336+
// Assert
337+
Check.That(cast.Length).Equals(castDynamic.Length);
338+
}
339+
320340
[Fact]
321341
public void CastToType_Dynamic_ActingOnIt_WithType()
322342
{

0 commit comments

Comments
 (0)