Skip to content

Commit 5ef660f

Browse files
authored
Add TypeConverters to config (#485)
* add test * noda * 9-preview-01 * add test * != null * 9-preview-02 * [Fact(Skip = "Fails in NET452 CI")]
1 parent 52d0831 commit 5ef660f

File tree

11 files changed

+279
-12
lines changed

11 files changed

+279
-12
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ Expression ParseComparisonOperator()
517517

518518
if (!typesAreSameAndImplementCorrectInterface)
519519
{
520-
if (left.Type.GetTypeInfo().IsClass && right is ConstantExpression)
520+
if ((TypeHelper.IsClass(left.Type) || TypeHelper.IsStruct(left.Type)) && right is ConstantExpression)
521521
{
522522
if (HasImplicitConversion(left.Type, right.Type))
523523
{
@@ -528,7 +528,7 @@ Expression ParseComparisonOperator()
528528
right = Expression.Convert(right, left.Type);
529529
}
530530
}
531-
else if (right.Type.GetTypeInfo().IsClass && left is ConstantExpression)
531+
else if ((TypeHelper.IsClass(right.Type) || TypeHelper.IsStruct(right.Type)) && left is ConstantExpression)
532532
{
533533
if (HasImplicitConversion(right.Type, left.Type))
534534
{
@@ -2042,7 +2042,9 @@ void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref E
20422042
// first try left operand's equality operators
20432043
found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, null, ref args);
20442044
if (!found)
2045+
{
20452046
found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, null, ref args);
2047+
}
20462048
}
20472049

20482050
if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, null, ref args))

src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public int FindMethod(Type type, string methodName, bool staticAccess, ref Expre
5252

5353
if (instance != null)
5454
{
55-
// TRY to solve with registered extension methods
55+
// Try to solve with registered extension methods
5656
if (_parsingConfig.CustomTypeProvider.GetExtensionMethods().TryGetValue(type, out var methods))
5757
{
5858
var argsList = args.ToList();
@@ -164,7 +164,7 @@ bool IsApplicable(MethodData method, Expression[] args)
164164
var paramType = method.Parameters.Last().ParameterType;
165165
var paramElementType = paramType.GetElementType();
166166

167-
List<Expression> arrayInitializerExpressions = new List<Expression>();
167+
var arrayInitializerExpressions = new List<Expression>();
168168

169169
for (int j = method.Parameters.Length - 1; j < args.Length; j++)
170170
{
@@ -305,7 +305,10 @@ void AddInterface(List<Type> types, Type type)
305305
if (!types.Contains(type))
306306
{
307307
types.Add(type);
308-
foreach (Type t in type.GetInterfaces()) AddInterface(types, t);
308+
foreach (Type t in type.GetInterfaces())
309+
{
310+
AddInterface(types, t);
311+
}
309312
}
310313
}
311314
}

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

+35-1
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,40 @@ public static bool IsCompatibleWith(Type source, Type target)
243243
#endif
244244
}
245245

246+
public static bool IsClass(Type type)
247+
{
248+
bool result = false;
249+
if (type.GetTypeInfo().IsClass)
250+
{
251+
// Is Class or Delegate
252+
if (type != typeof(Delegate))
253+
{
254+
result = true;
255+
}
256+
}
257+
return result;
258+
}
259+
260+
public static bool IsStruct(Type type)
261+
{
262+
var nonNullableType = GetNonNullableType(type);
263+
if (nonNullableType.GetTypeInfo().IsValueType)
264+
{
265+
if (!nonNullableType.GetTypeInfo().IsPrimitive)
266+
{
267+
if (nonNullableType != typeof(decimal) && nonNullableType != typeof(DateTime) && nonNullableType != typeof(Guid))
268+
{
269+
if (!nonNullableType.GetTypeInfo().IsEnum)
270+
{
271+
return true;
272+
}
273+
}
274+
}
275+
}
276+
277+
return false;
278+
}
279+
246280
public static bool IsEnumType(Type type)
247281
{
248282
return GetNonNullableType(type).GetTypeInfo().IsEnum;
@@ -288,7 +322,7 @@ private static int GetNumericTypeKind(Type type)
288322
{
289323
type = GetNonNullableType(type);
290324

291-
#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD)
325+
#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD)
292326
if (type.GetTypeInfo().IsEnum)
293327
{
294328
return 0;

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Globalization;
1+
using System.Collections.Generic;
2+
using System.ComponentModel;
3+
using System.Globalization;
24
using System.Linq.Dynamic.Core.CustomTypeProviders;
35
using System.Linq.Dynamic.Core.Parser;
46

@@ -184,5 +186,10 @@ public IQueryableAnalyzer QueryableAnalyzer
184186
/// Default value is CultureInfo.InvariantCulture
185187
/// </summary>
186188
public CultureInfo NumberParseCulture { get; set; } = CultureInfo.InvariantCulture;
189+
190+
/// <summary>
191+
/// Additional TypeConverters
192+
/// </summary>
193+
public IDictionary<Type, TypeConverter> TypeConverters { get; set; }
187194
}
188195
}

src/System.Linq.Dynamic.Core/TypeConverters/TypeConverterFactory.cs

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using JetBrains.Annotations;
22
using System.ComponentModel;
3+
using System.Linq.Dynamic.Core.Parser;
34
using System.Linq.Dynamic.Core.Validation;
45

56
namespace System.Linq.Dynamic.Core.TypeConverters
@@ -25,6 +26,12 @@ public TypeConverter GetConverter(Type type)
2526
return new CustomDateTimeConverter();
2627
}
2728

29+
var typeToCheck = TypeHelper.IsNullableType(type) ? TypeHelper.GetNonNullableType(type) : type;
30+
if (_config.TypeConverters != null && _config.TypeConverters.TryGetValue(typeToCheck, out var typeConverter))
31+
{
32+
return typeConverter;
33+
}
34+
2835
#if !SILVERLIGHT
2936
return TypeDescriptor.GetConverter(type);
3037
#else

test/EntityFramework.DynamicLinq.Tests/EntityFramework.DynamicLinq.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
3737
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
3838
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
39+
<PackageReference Include="NodaTime" Version="2.4.7" />
3940
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
4041
<PrivateAssets>all</PrivateAssets>
4142
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

test/System.Linq.Dynamic.Core.Tests/MikArea/Dictionary.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,12 @@ public void Test_ContainsKey_3()
8686
Check.That(3).IsEqualTo(data.Count);
8787
}
8888

89-
[Fact] // https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/397
90-
public void Test_DynamicIndexCall()
89+
#if NETCOREAPP3_1
90+
[Fact]
91+
#else
92+
[Fact(Skip = "Fails in NET452 CI")]
93+
#endif
94+
public void Test_DynamicIndexCall() // https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/397
9195
{
9296
object CreateDicParameter(string name) => new Dictionary<string, object>
9397
{

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<Authors>Stef Heyenrath</Authors>
44
<TargetFrameworks>net452;netcoreapp3.1</TargetFrameworks>
5-
<!--<TargetFramework>netcoreapp3.1</TargetFramework>-->
5+
<!--<TargetFramework>netcoreapp31</TargetFramework>-->
66
<AssemblyName>System.Linq.Dynamic.Core.Tests</AssemblyName>
77
<DebugType>full</DebugType>
88
<SignAssembly>True</SignAssembly>
@@ -55,6 +55,7 @@
5555
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.0.1" />
5656
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.1" />
5757
<PackageReference Include="LinqKit.Microsoft.EntityFrameworkCore" Version="2.0.0" />
58+
<PackageReference Include="NodaTime" Version="2.4.7" />
5859

5960
<ProjectReference Include="..\..\src\Microsoft.EntityFrameworkCore.DynamicLinq.EFCore2\Microsoft.EntityFrameworkCore.DynamicLinq.EFCore2.csproj" />
6061
</ItemGroup>
@@ -65,7 +66,8 @@
6566
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.0" />
6667
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
6768
<PackageReference Include="LinqKit.Microsoft.EntityFrameworkCore" Version="3.0.0" />
68-
69+
<PackageReference Include="NodaTime" Version="2.4.7" />
70+
6971
<ProjectReference Include="..\..\src\Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3\Microsoft.EntityFrameworkCore.DynamicLinq.EFCore3.csproj" />
7072
</ItemGroup>
7173

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#if !NET452
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Globalization;
6+
using System.Linq.Dynamic.Core.CustomTypeProviders;
7+
using System.Reflection;
8+
using FluentAssertions;
9+
using NodaTime;
10+
using NodaTime.Text;
11+
using Xunit;
12+
13+
namespace System.Linq.Dynamic.Core.Tests.TypeConvertors
14+
{
15+
public class NodaTimeConverterTests
16+
{
17+
private class Entity
18+
{
19+
public Guid Id { get; set; }
20+
public string FirstName { get; set; }
21+
public DateTime? MyDateTimeNullable { get; set; }
22+
public LocalDate BirthDate { get; set; }
23+
public LocalDate? BirthDateNullable { get; set; }
24+
public LocalTime? Time { get; set; }
25+
}
26+
27+
private readonly IQueryable<Entity> entities;
28+
29+
public NodaTimeConverterTests()
30+
{
31+
entities = new List<Entity>()
32+
{
33+
new Entity { Id = Guid.NewGuid(), FirstName = "Paul", MyDateTimeNullable = new DateTime(1987, 10, 12), BirthDate = new LocalDate(1987, 10, 12), BirthDateNullable = new LocalDate(1987, 10, 12), Time = new LocalTime(11, 1) },
34+
new Entity { Id = Guid.NewGuid(), FirstName = "Abigail", BirthDate = new LocalDate(1970, 02, 13), Time = new LocalTime(12, 2) },
35+
new Entity { Id = Guid.NewGuid(), FirstName = "Sophia", BirthDate = new LocalDate(1983, 05, 01), Time = new LocalTime(13, 3) }
36+
}.AsQueryable();
37+
}
38+
39+
[Fact]
40+
public void FilterByLocalDate_WithTypeConverter()
41+
{
42+
// Arrange
43+
var config = new ParsingConfig
44+
{
45+
TypeConverters = new Dictionary<Type, TypeConverter>
46+
{
47+
{ typeof(LocalDate), new LocalDateConverter() }
48+
}
49+
};
50+
51+
// Act
52+
var result = entities.AsQueryable().Where(config, "BirthDate == @0", "1987-10-12").ToList();
53+
54+
// Assert
55+
Assert.Single(result);
56+
}
57+
58+
[Fact]
59+
public void FilterByNullableLocalDate_WithTypeConverter()
60+
{
61+
// Arrange
62+
var config = new ParsingConfig
63+
{
64+
TypeConverters = new Dictionary<Type, TypeConverter>
65+
{
66+
{ typeof(LocalDate), new LocalDateConverter() }
67+
}
68+
};
69+
70+
// Act
71+
var result = entities.AsQueryable().Where(config, "BirthDateNullable == @0", "1987-10-12").ToList();
72+
73+
// Assert
74+
Assert.Single(result);
75+
}
76+
77+
[Fact]
78+
public void FilterByLocalDate_WithDynamicExpressionParser()
79+
{
80+
// Arrange
81+
var config = new ParsingConfig
82+
{
83+
TypeConverters = new Dictionary<Type, TypeConverter>
84+
{
85+
{ typeof(LocalDate), new LocalDateConverter() }
86+
}
87+
};
88+
89+
// Act
90+
var expr = DynamicExpressionParser.ParseLambda<Entity, bool>(config, false, "BirthDate == @0", "1987-10-12");
91+
var result = entities.AsQueryable().Where(expr).ToList();
92+
93+
// Assert
94+
Assert.Single(result);
95+
}
96+
97+
[Fact]
98+
public void FilterByNullableLocalDate_WithDynamicExpressionParser()
99+
{
100+
// Arrange
101+
var config = new ParsingConfig
102+
{
103+
TypeConverters = new Dictionary<Type, TypeConverter>
104+
{
105+
{ typeof(LocalDate), new LocalDateConverter() }
106+
}
107+
};
108+
109+
// Act
110+
var expr = DynamicExpressionParser.ParseLambda<Entity, bool>(config, false, "BirthDateNullable == \"1987-10-12\"");
111+
var result = entities.AsQueryable().Where(expr).ToList();
112+
113+
// Assert
114+
Assert.Single(result);
115+
}
116+
117+
[Theory]
118+
[InlineData("!= null", 1)]
119+
[InlineData("== null", 2)]
120+
public void FilterByNullableDateTime_WithDynamicExpressionParser_CompareWithNull(string equal, int numberOfEntities)
121+
{
122+
// Arrange
123+
var config = new ParsingConfig
124+
{
125+
TypeConverters = new Dictionary<Type, TypeConverter>
126+
{
127+
{ typeof(LocalDate), new LocalDateConverter() }
128+
}
129+
};
130+
131+
// Act
132+
var expr = DynamicExpressionParser.ParseLambda<Entity, bool>(config, false, $"MyDateTimeNullable {equal}");
133+
var result = entities.AsQueryable().Where(expr).ToList();
134+
135+
// Assert
136+
result.Should().HaveCount(numberOfEntities);
137+
}
138+
139+
[Theory]
140+
[InlineData("!= null", 1)]
141+
[InlineData("== null", 2)]
142+
public void FilterByNullableLocalDate_WithDynamicExpressionParser_CompareWithNull(string equal, int numberOfEntities)
143+
{
144+
// Arrange
145+
var config = new ParsingConfig
146+
{
147+
TypeConverters = new Dictionary<Type, TypeConverter>
148+
{
149+
{ typeof(LocalDate), new LocalDateConverter() }
150+
}
151+
};
152+
153+
// Act
154+
var expr = DynamicExpressionParser.ParseLambda<Entity, bool>(config, false, $"BirthDateNullable {equal}");
155+
var result = entities.AsQueryable().Where(expr).ToList();
156+
157+
// Assert
158+
result.Should().HaveCount(numberOfEntities);
159+
}
160+
161+
public class LocalDateConverter : TypeConverter
162+
{
163+
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string);
164+
165+
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
166+
{
167+
var result = LocalDatePattern.Iso.Parse(value as string);
168+
169+
return result.Success
170+
? result.Value
171+
: throw new FormatException(value?.ToString());
172+
}
173+
174+
protected ParseResult<LocalDate> Convert(object value) => LocalDatePattern.Iso.Parse(value as string);
175+
}
176+
}
177+
}
178+
#endif

0 commit comments

Comments
 (0)