Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support nullable notation "xxx?" in As expression #647

Merged
merged 8 commits into from
Nov 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src-console/ConsoleApp_net6.0/ConsoleApp_net6.0.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>ConsoleApp_net6._0</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>ConsoleApp_net6._0</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\System.Linq.Dynamic.Core\System.Linq.Dynamic.Core.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\System.Linq.Dynamic.Core\System.Linq.Dynamic.Core.csproj" />
</ItemGroup>

</Project>
26 changes: 22 additions & 4 deletions src-console/ConsoleApp_net6.0/Program.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;

namespace ConsoleApp_net6._0
{
public class X
{
public string Key { get; set; } = null!;

public List<Y>? Contestants { get; set; }
}

public class Y
{
}

class Program
{
static void Main(string[] args)
{
var q = new[]
{
new X { Key = "x" },
new X { Key = "a" },
new X { Key = "a", Contestants = new List<Y> { new Y() } }
}.AsQueryable();
var groupByKey = q.GroupBy("Key");
var selectQry = groupByKey.Select("new (Key, Sum(np(Contestants.Count, 0)) As TotalCount)").ToDynamicList();

Normal();
Dynamic();

int y = 0;
}

private static void Normal()
Expand Down Expand Up @@ -40,4 +58,4 @@ private static void Dynamic()
//var d = q.FirstOrDefault(i => i == 0, 42);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public Dictionary<Type, List<MethodInfo>> GetExtensionMethods()
/// <inheritdoc cref="IDynamicLinqCustomTypeProvider.ResolveType"/>
public Type? ResolveType(string typeName)
{
Check.NotEmpty(typeName, nameof(typeName));
Check.NotEmpty(typeName);

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

IEnumerable<Assembly> assemblies = _assemblyHelper.GetAssemblies();
return ResolveTypeBySimpleName(assemblies, simpleTypeName);
Expand Down
13 changes: 13 additions & 0 deletions src/System.Linq.Dynamic.Core/Extensions/LinqExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Collections.Generic;

namespace System.Linq.Dynamic.Core.Extensions
{
internal static class LinqExtensions
{
// Ex: collection.TakeLast(5);
public static IEnumerable<T> TakeLast<T>(this IList<T> source, int n)
{
return source.Skip(Math.Max(0, source.Count() - n));
}
}
}
9 changes: 8 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1907,13 +1907,20 @@ private Type ResolveTypeFromArgumentExpression(string functionName, Expression a

private Type ResolveTypeStringFromArgument(string typeName)
{
bool typeIsNullable = false;
if (typeName.EndsWith("?"))
{
typeName = typeName.TrimEnd('?');
typeIsNullable = true;
}

var resultType = _typeFinder.FindTypeByName(typeName, new[] { _it, _parent, _root }, true);
if (resultType == null)
{
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeName);
}

return resultType;
return typeIsNullable ? TypeHelper.ToNullableType(resultType) : resultType;
}

private Expression[] ParseArgumentList()
Expand Down
19 changes: 16 additions & 3 deletions src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,29 @@ public static bool IsNullableType(Type type)

public static bool TypeCanBeNull(Type type)
{
Check.NotNull(type, nameof(type));
Check.NotNull(type);

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

public static Type ToNullableType(Type type)
{
Check.NotNull(type, nameof(type));
Check.NotNull(type);

if (IsNullableType(type))
{
// Already nullable, just return the type.
return type;
}

if (!type.GetTypeInfo().IsValueType)
{
// Type is a not a value type, just return the type.
return type;
}

return IsNullableType(type) ? type : typeof(Nullable<>).MakeGenericType(type);
// Convert type to a nullable type
return typeof(Nullable<>).MakeGenericType(type);
}

public static bool IsSignedIntegralType(Type type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>../../src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.snk</AssemblyOriginatorKeyFile>
<IsPackable>false</IsPackable>

<Nullable>enable</Nullable>
<DefineConstants>$(DefineConstants);NETCOREAPP;EFCORE;EFCORE_3X;NETCOREAPP3_1</DefineConstants>
</PropertyGroup>

Expand Down
2 changes: 2 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/Entities/Department.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
public class Department
{
public BaseEmployee Employee { get; set; }

public BaseEmployee? NullableEmployee { get; set; }
}
}
95 changes: 92 additions & 3 deletions test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.Linq.Dynamic.Core.CustomTypeProviders;
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Dynamic.Core.Tests.Helpers;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
using FluentAssertions;
using Moq;
using Newtonsoft.Json.Linq;
using NFluent;
using Xunit;
Expand Down Expand Up @@ -251,7 +253,7 @@ public void ExpressionTests_BinaryOrNumericConvert()
}

[Fact]
public void ExpressionTests_Cast_To_nullableint()
public void ExpressionTests_Cast_To_NullableInt()
{
// Arrange
var list = new List<SimpleValuesModel>
Expand All @@ -268,7 +270,94 @@ public void ExpressionTests_Cast_To_nullableint()
}

[Fact]
public void ExpressionTests_Cast_Automatic_To_nullablelong()
public void ExpressionTests_Cast_To_Enum_Using_DynamicLinqType()
{
// Arrange
var list = new List<SimpleValuesModel>
{
new SimpleValuesModel { EnumValueDynamicLinqType = SimpleValuesModelEnumAsDynamicLinqType.A }
};

// Act
var expectedResult = list.Select(x => x.EnumValueDynamicLinqType);
var result = list.AsQueryable().Select("SimpleValuesModelEnumAsDynamicLinqType(EnumValueDynamicLinqType)");

// Assert
Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray<SimpleValuesModelEnumAsDynamicLinqType>());
}

[Fact]
public void ExpressionTests_Cast_To_Enum_Using_CustomTypeProvider()
{
// Arrange
var dynamicLinqCustomTypeProviderMock = new Mock<IDynamicLinkCustomTypeProvider>();
dynamicLinqCustomTypeProviderMock.Setup(d => d.GetCustomTypes()).Returns(new HashSet<Type> { typeof(SimpleValuesModelEnum) });
var config = new ParsingConfig
{
CustomTypeProvider = dynamicLinqCustomTypeProviderMock.Object
};

var list = new List<SimpleValuesModel>
{
new SimpleValuesModel { EnumValue = SimpleValuesModelEnum.A }
};

// Act
var expectedResult = list.Select(x => x.EnumValue);
var result = list.AsQueryable().Select(config, "SimpleValuesModelEnum(EnumValue)");

// Assert
Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray<SimpleValuesModelEnum>());
}

[Fact]
public void ExpressionTests_Cast_To_NullableEnum_Using_DynamicLinqType()
{
// Arrange
var list = new List<SimpleValuesModel>
{
new SimpleValuesModel { EnumValueDynamicLinqType = SimpleValuesModelEnumAsDynamicLinqType.A }
};

// Act
var expectedResult = list.Select(x => (SimpleValuesModelEnumAsDynamicLinqType?)x.EnumValueDynamicLinqType);
var result = list.AsQueryable().Select("SimpleValuesModelEnumAsDynamicLinqType?(EnumValueDynamicLinqType)");

// Assert
Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray<SimpleValuesModelEnumAsDynamicLinqType?>());
}

[Fact]
public void ExpressionTests_Cast_To_NullableEnum_Using_CustomTypeProvider()
{
// Arrange
var dynamicLinqCustomTypeProviderMock = new Mock<IDynamicLinkCustomTypeProvider>();
dynamicLinqCustomTypeProviderMock.Setup(d => d.GetCustomTypes()).Returns(new HashSet<Type>
{
typeof(SimpleValuesModelEnum),
typeof(SimpleValuesModelEnum?)
});

var config = new ParsingConfig
{
CustomTypeProvider = dynamicLinqCustomTypeProviderMock.Object
};

var list = new List<SimpleValuesModel>
{
new SimpleValuesModel { EnumValue = SimpleValuesModelEnum.A }
};

// Act
var expectedResult = list.Select(x => (SimpleValuesModelEnum?)x.EnumValue);
var result = list.AsQueryable().Select(config, $"SimpleValuesModelEnum?(EnumValue)");

// Assert
Assert.Equal(expectedResult.ToArray(), result.ToDynamicArray<SimpleValuesModelEnum?>());
}

[Fact]
public void ExpressionTests_Cast_Automatic_To_NullableLong()
{
// Arrange
var q = new[] { null, new UserProfile(), new UserProfile { UserProfileDetails = new UserProfileDetails { Id = 42 } } }.AsQueryable();
Expand All @@ -282,7 +371,7 @@ public void ExpressionTests_Cast_Automatic_To_nullablelong()
}

[Fact]
public void ExpressionTests_Cast_To_newnullableint()
public void ExpressionTests_Cast_To_NewNullableInt()
{
// Arrange
var list = new List<SimpleValuesModel>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@

using System.Linq.Dynamic.Core.CustomTypeProviders;

namespace System.Linq.Dynamic.Core.Tests.Helpers.Models
{
public enum SimpleValuesModelEnum
{
A,
B
}

[DynamicLinqType]
public enum SimpleValuesModelEnumAsDynamicLinqType
{
A,
B
}

public class SimpleValuesModel
{
public int IntValue { get; set; }
Expand All @@ -14,5 +28,9 @@ public class SimpleValuesModel
public int? NullableIntValue { get; set; }

public double? NullableDoubleValue { get; set; }

public SimpleValuesModelEnum EnumValue { get; set; }

public SimpleValuesModelEnumAsDynamicLinqType EnumValueDynamicLinqType { get; set; }
}
}
}
Loading