Skip to content

Commit c035e5b

Browse files
authored
Support nullable notation "xxx?" in As expression (#647)
* Support nullable notation "xxx?" in As expression * int? * ut * . * ut * ExpressionTests_Cast_To_Enum + ExpressionTests_Cast_To_NullableEnum * - * more tests
1 parent ec55d34 commit c035e5b

File tree

11 files changed

+253
-24
lines changed

11 files changed

+253
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<OutputType>Exe</OutputType>
5-
<TargetFramework>net6.0</TargetFramework>
6-
<RootNamespace>ConsoleApp_net6._0</RootNamespace>
7-
</PropertyGroup>
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<RootNamespace>ConsoleApp_net6._0</RootNamespace>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
89

9-
<ItemGroup>
10-
<ProjectReference Include="..\..\src\System.Linq.Dynamic.Core\System.Linq.Dynamic.Core.csproj" />
11-
</ItemGroup>
10+
<ItemGroup>
11+
<ProjectReference Include="..\..\src\System.Linq.Dynamic.Core\System.Linq.Dynamic.Core.csproj" />
12+
</ItemGroup>
1213

1314
</Project>

src-console/ConsoleApp_net6.0/Program.cs

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,35 @@
1-
using System;
1+
using System.Collections.Generic;
22
using System.Linq;
33
using System.Linq.Dynamic.Core;
44

55
namespace ConsoleApp_net6._0
66
{
7+
public class X
8+
{
9+
public string Key { get; set; } = null!;
10+
11+
public List<Y>? Contestants { get; set; }
12+
}
13+
14+
public class Y
15+
{
16+
}
17+
718
class Program
819
{
920
static void Main(string[] args)
1021
{
22+
var q = new[]
23+
{
24+
new X { Key = "x" },
25+
new X { Key = "a" },
26+
new X { Key = "a", Contestants = new List<Y> { new Y() } }
27+
}.AsQueryable();
28+
var groupByKey = q.GroupBy("Key");
29+
var selectQry = groupByKey.Select("new (Key, Sum(np(Contestants.Count, 0)) As TotalCount)").ToDynamicList();
30+
1131
Normal();
1232
Dynamic();
13-
14-
int y = 0;
1533
}
1634

1735
private static void Normal()
@@ -40,4 +58,4 @@ private static void Dynamic()
4058
//var d = q.FirstOrDefault(i => i == 0, 42);
4159
}
4260
}
43-
}
61+
}

src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public Dictionary<Type, List<MethodInfo>> GetExtensionMethods()
6666
/// <inheritdoc cref="IDynamicLinqCustomTypeProvider.ResolveType"/>
6767
public Type? ResolveType(string typeName)
6868
{
69-
Check.NotEmpty(typeName, nameof(typeName));
69+
Check.NotEmpty(typeName);
7070

7171
IEnumerable<Assembly> assemblies = _assemblyHelper.GetAssemblies();
7272
return ResolveType(assemblies, typeName);
@@ -75,7 +75,7 @@ public Dictionary<Type, List<MethodInfo>> GetExtensionMethods()
7575
/// <inheritdoc cref="IDynamicLinqCustomTypeProvider.ResolveTypeBySimpleName"/>
7676
public Type? ResolveTypeBySimpleName(string simpleTypeName)
7777
{
78-
Check.NotEmpty(simpleTypeName, nameof(simpleTypeName));
78+
Check.NotEmpty(simpleTypeName);
7979

8080
IEnumerable<Assembly> assemblies = _assemblyHelper.GetAssemblies();
8181
return ResolveTypeBySimpleName(assemblies, simpleTypeName);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Collections.Generic;
2+
3+
namespace System.Linq.Dynamic.Core.Extensions
4+
{
5+
internal static class LinqExtensions
6+
{
7+
// Ex: collection.TakeLast(5);
8+
public static IEnumerable<T> TakeLast<T>(this IList<T> source, int n)
9+
{
10+
return source.Skip(Math.Max(0, source.Count() - n));
11+
}
12+
}
13+
}

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -1907,13 +1907,20 @@ private Type ResolveTypeFromArgumentExpression(string functionName, Expression a
19071907

19081908
private Type ResolveTypeStringFromArgument(string typeName)
19091909
{
1910+
bool typeIsNullable = false;
1911+
if (typeName.EndsWith("?"))
1912+
{
1913+
typeName = typeName.TrimEnd('?');
1914+
typeIsNullable = true;
1915+
}
1916+
19101917
var resultType = _typeFinder.FindTypeByName(typeName, new[] { _it, _parent, _root }, true);
19111918
if (resultType == null)
19121919
{
19131920
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeName);
19141921
}
19151922

1916-
return resultType;
1923+
return typeIsNullable ? TypeHelper.ToNullableType(resultType) : resultType;
19171924
}
19181925

19191926
private Expression[] ParseArgumentList()

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

+16-3
Original file line numberDiff line numberDiff line change
@@ -311,16 +311,29 @@ public static bool IsNullableType(Type type)
311311

312312
public static bool TypeCanBeNull(Type type)
313313
{
314-
Check.NotNull(type, nameof(type));
314+
Check.NotNull(type);
315315

316316
return !type.GetTypeInfo().IsValueType || IsNullableType(type);
317317
}
318318

319319
public static Type ToNullableType(Type type)
320320
{
321-
Check.NotNull(type, nameof(type));
321+
Check.NotNull(type);
322+
323+
if (IsNullableType(type))
324+
{
325+
// Already nullable, just return the type.
326+
return type;
327+
}
328+
329+
if (!type.GetTypeInfo().IsValueType)
330+
{
331+
// Type is a not a value type, just return the type.
332+
return type;
333+
}
322334

323-
return IsNullableType(type) ? type : typeof(Nullable<>).MakeGenericType(type);
335+
// Convert type to a nullable type
336+
return typeof(Nullable<>).MakeGenericType(type);
324337
}
325338

326339
public static bool IsSignedIntegralType(Type type)

test/System.Linq.Dynamic.Core.Tests.Net7/System.Linq.Dynamic.Core.Tests.Net7.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<SignAssembly>True</SignAssembly>
88
<AssemblyOriginatorKeyFile>../../src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.snk</AssemblyOriginatorKeyFile>
99
<IsPackable>false</IsPackable>
10-
10+
<Nullable>enable</Nullable>
1111
<DefineConstants>$(DefineConstants);NETCOREAPP;EFCORE;EFCORE_3X;NETCOREAPP3_1</DefineConstants>
1212
</PropertyGroup>
1313

test/System.Linq.Dynamic.Core.Tests/Entities/Department.cs

+2
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
public class Department
44
{
55
public BaseEmployee Employee { get; set; }
6+
7+
public BaseEmployee? NullableEmployee { get; set; }
68
}
79
}

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

+92-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System.Collections.Generic;
22
using System.Dynamic;
33
using System.Globalization;
4+
using System.Linq.Dynamic.Core.CustomTypeProviders;
45
using System.Linq.Dynamic.Core.Exceptions;
56
using System.Linq.Dynamic.Core.Tests.Helpers;
67
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
78
using FluentAssertions;
9+
using Moq;
810
using Newtonsoft.Json.Linq;
911
using NFluent;
1012
using Xunit;
@@ -251,7 +253,7 @@ public void ExpressionTests_BinaryOrNumericConvert()
251253
}
252254

253255
[Fact]
254-
public void ExpressionTests_Cast_To_nullableint()
256+
public void ExpressionTests_Cast_To_NullableInt()
255257
{
256258
// Arrange
257259
var list = new List<SimpleValuesModel>
@@ -268,7 +270,94 @@ public void ExpressionTests_Cast_To_nullableint()
268270
}
269271

270272
[Fact]
271-
public void ExpressionTests_Cast_Automatic_To_nullablelong()
273+
public void ExpressionTests_Cast_To_Enum_Using_DynamicLinqType()
274+
{
275+
// Arrange
276+
var list = new List<SimpleValuesModel>
277+
{
278+
new SimpleValuesModel { EnumValueDynamicLinqType = SimpleValuesModelEnumAsDynamicLinqType.A }
279+
};
280+
281+
// Act
282+
var expectedResult = list.Select(x => x.EnumValueDynamicLinqType);
283+
var result = list.AsQueryable().Select("SimpleValuesModelEnumAsDynamicLinqType(EnumValueDynamicLinqType)");
284+
285+
// Assert
286+
Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray<SimpleValuesModelEnumAsDynamicLinqType>());
287+
}
288+
289+
[Fact]
290+
public void ExpressionTests_Cast_To_Enum_Using_CustomTypeProvider()
291+
{
292+
// Arrange
293+
var dynamicLinqCustomTypeProviderMock = new Mock<IDynamicLinkCustomTypeProvider>();
294+
dynamicLinqCustomTypeProviderMock.Setup(d => d.GetCustomTypes()).Returns(new HashSet<Type> { typeof(SimpleValuesModelEnum) });
295+
var config = new ParsingConfig
296+
{
297+
CustomTypeProvider = dynamicLinqCustomTypeProviderMock.Object
298+
};
299+
300+
var list = new List<SimpleValuesModel>
301+
{
302+
new SimpleValuesModel { EnumValue = SimpleValuesModelEnum.A }
303+
};
304+
305+
// Act
306+
var expectedResult = list.Select(x => x.EnumValue);
307+
var result = list.AsQueryable().Select(config, "SimpleValuesModelEnum(EnumValue)");
308+
309+
// Assert
310+
Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray<SimpleValuesModelEnum>());
311+
}
312+
313+
[Fact]
314+
public void ExpressionTests_Cast_To_NullableEnum_Using_DynamicLinqType()
315+
{
316+
// Arrange
317+
var list = new List<SimpleValuesModel>
318+
{
319+
new SimpleValuesModel { EnumValueDynamicLinqType = SimpleValuesModelEnumAsDynamicLinqType.A }
320+
};
321+
322+
// Act
323+
var expectedResult = list.Select(x => (SimpleValuesModelEnumAsDynamicLinqType?)x.EnumValueDynamicLinqType);
324+
var result = list.AsQueryable().Select("SimpleValuesModelEnumAsDynamicLinqType?(EnumValueDynamicLinqType)");
325+
326+
// Assert
327+
Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray<SimpleValuesModelEnumAsDynamicLinqType?>());
328+
}
329+
330+
[Fact]
331+
public void ExpressionTests_Cast_To_NullableEnum_Using_CustomTypeProvider()
332+
{
333+
// Arrange
334+
var dynamicLinqCustomTypeProviderMock = new Mock<IDynamicLinkCustomTypeProvider>();
335+
dynamicLinqCustomTypeProviderMock.Setup(d => d.GetCustomTypes()).Returns(new HashSet<Type>
336+
{
337+
typeof(SimpleValuesModelEnum),
338+
typeof(SimpleValuesModelEnum?)
339+
});
340+
341+
var config = new ParsingConfig
342+
{
343+
CustomTypeProvider = dynamicLinqCustomTypeProviderMock.Object
344+
};
345+
346+
var list = new List<SimpleValuesModel>
347+
{
348+
new SimpleValuesModel { EnumValue = SimpleValuesModelEnum.A }
349+
};
350+
351+
// Act
352+
var expectedResult = list.Select(x => (SimpleValuesModelEnum?)x.EnumValue);
353+
var result = list.AsQueryable().Select(config, $"SimpleValuesModelEnum?(EnumValue)");
354+
355+
// Assert
356+
Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray<SimpleValuesModelEnum?>());
357+
}
358+
359+
[Fact]
360+
public void ExpressionTests_Cast_Automatic_To_NullableLong()
272361
{
273362
// Arrange
274363
var q = new[] { null, new UserProfile(), new UserProfile { UserProfileDetails = new UserProfileDetails { Id = 42 } } }.AsQueryable();
@@ -282,7 +371,7 @@ public void ExpressionTests_Cast_Automatic_To_nullablelong()
282371
}
283372

284373
[Fact]
285-
public void ExpressionTests_Cast_To_newnullableint()
374+
public void ExpressionTests_Cast_To_NewNullableInt()
286375
{
287376
// Arrange
288377
var list = new List<SimpleValuesModel>

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

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
1-

1+
using System.Linq.Dynamic.Core.CustomTypeProviders;
2+
23
namespace System.Linq.Dynamic.Core.Tests.Helpers.Models
34
{
5+
public enum SimpleValuesModelEnum
6+
{
7+
A,
8+
B
9+
}
10+
11+
[DynamicLinqType]
12+
public enum SimpleValuesModelEnumAsDynamicLinqType
13+
{
14+
A,
15+
B
16+
}
17+
418
public class SimpleValuesModel
519
{
620
public int IntValue { get; set; }
@@ -14,5 +28,9 @@ public class SimpleValuesModel
1428
public int? NullableIntValue { get; set; }
1529

1630
public double? NullableDoubleValue { get; set; }
31+
32+
public SimpleValuesModelEnum EnumValue { get; set; }
33+
34+
public SimpleValuesModelEnumAsDynamicLinqType EnumValueDynamicLinqType { get; set; }
1735
}
18-
}
36+
}

0 commit comments

Comments
 (0)