Skip to content

Commit 7729996

Browse files
authored
Add support to cast to a fully qualified type (#653)
* ... * fix * x * 24-preview-01 * 24-preview-02
1 parent 3e377cf commit 7729996

File tree

12 files changed

+359
-28
lines changed

12 files changed

+359
-28
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# v1.2.24 (27 November 2022)
2+
- [#621](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/621) - Fix Join on inherited class [bug] contributed by [StefH](https://github.com/StefH)
3+
- [#646](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/646) - Add more unittests for issue 645 contributed by [StefH](https://github.com/StefH)
4+
- [#647](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/647) - Support nullable notation "xxx?" in As expression [feature] contributed by [StefH](https://github.com/StefH)
5+
- [#614](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/614) - Join problem with inherited entities [bug]
6+
17
# v1.2.23 (12 November 2022)
28
- [#644](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/644) - Add support for .NET 7 and EF Core 7 [feature] contributed by [StefH](https://github.com/StefH)
39

Generate-ReleaseNotes.bat

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
rem https://github.com/StefH/GitHubReleaseNotes
22

3-
SET version=v1.2.23
3+
SET version=v1.2.24
44

55
GitHubReleaseNotes --output CHANGELOG.md --exclude-labels invalid question documentation wontfix --language en --version %version% --token %GH_TOKEN%

src-console/ConsoleAppEF6_InMemory/Program.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,25 @@ static async Task Main(string[] args)
1818
await using (var context = new TestContextEF6())
1919
{
2020
context.Products.Add(new ProductDynamic { NullableInt = 1, Dict = new Dictionary<string, object> { { "Name", "test" } } });
21+
context.Products.Add(new ProductDynamic { NullableInt = 2, Dict = new Dictionary<string, object> { { "Name1", "test1" } } });
2122
await context.SaveChangesAsync();
2223
}
2324

25+
await using (var context = new TestContextEF6())
26+
{
27+
var intType = typeof(int).FullName;
28+
29+
var a1 = context.Products.Select($"\"{intType}\"(Key)").ToDynamicArray();
30+
Console.WriteLine("a1 {0}", string.Join(",", a1));
31+
32+
var a2 = context.Products.Select($"\"{intType}\"?(Key)").ToDynamicArray();
33+
Console.WriteLine("a2 {0}", string.Join(",", a2));
34+
}
35+
2436
await using (var context = new TestContextEF6())
2537
{
2638
var resultsNormal = context.Products.Where(p => p.Dict["Name"] == "test").ToListAsync();
27-
39+
2840
var results1 = await context.Products.Where("Dict.Name == @0", "test").ToListAsync();
2941
Console.WriteLine("results1:");
3042
foreach (var result in results1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by https://github.com/StefH/AnyOf.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
namespace AnyOfTypes
11+
{
12+
internal enum AnyOfType
13+
{
14+
Undefined = 0, First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by https://github.com/StefH/AnyOf.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
using System;
11+
using System.Diagnostics;
12+
using System.Collections.Generic;
13+
14+
namespace AnyOfTypes
15+
{
16+
[DebuggerDisplay("{_thisType}, AnyOfType = {_currentType}; Type = {_currentValueType?.Name}; Value = '{ToString()}'")]
17+
internal struct AnyOf<TFirst, TSecond>
18+
{
19+
private readonly string _thisType => $"AnyOf<{typeof(TFirst).Name}, {typeof(TSecond).Name}>";
20+
private readonly int _numberOfTypes;
21+
private readonly object _currentValue;
22+
private readonly Type _currentValueType;
23+
private readonly AnyOfType _currentType;
24+
25+
private readonly TFirst _first;
26+
private readonly TSecond _second;
27+
28+
public readonly AnyOfType[] AnyOfTypes => new[] { AnyOfType.First, AnyOfType.Second };
29+
public readonly Type[] Types => new[] { typeof(TFirst), typeof(TSecond) };
30+
public bool IsUndefined => _currentType == AnyOfType.Undefined;
31+
public bool IsFirst => _currentType == AnyOfType.First;
32+
public bool IsSecond => _currentType == AnyOfType.Second;
33+
34+
public static implicit operator AnyOf<TFirst, TSecond>(TFirst value) => new AnyOf<TFirst, TSecond>(value);
35+
36+
public static implicit operator TFirst(AnyOf<TFirst, TSecond> @this) => @this.First;
37+
38+
public AnyOf(TFirst value)
39+
{
40+
_numberOfTypes = 2;
41+
_currentType = AnyOfType.First;
42+
_currentValue = value;
43+
_currentValueType = typeof(TFirst);
44+
_first = value;
45+
_second = default;
46+
}
47+
48+
public TFirst First
49+
{
50+
get
51+
{
52+
Validate(AnyOfType.First);
53+
return _first;
54+
}
55+
}
56+
57+
public static implicit operator AnyOf<TFirst, TSecond>(TSecond value) => new AnyOf<TFirst, TSecond>(value);
58+
59+
public static implicit operator TSecond(AnyOf<TFirst, TSecond> @this) => @this.Second;
60+
61+
public AnyOf(TSecond value)
62+
{
63+
_numberOfTypes = 2;
64+
_currentType = AnyOfType.Second;
65+
_currentValue = value;
66+
_currentValueType = typeof(TSecond);
67+
_second = value;
68+
_first = default;
69+
}
70+
71+
public TSecond Second
72+
{
73+
get
74+
{
75+
Validate(AnyOfType.Second);
76+
return _second;
77+
}
78+
}
79+
80+
private void Validate(AnyOfType desiredType)
81+
{
82+
if (desiredType != _currentType)
83+
{
84+
throw new InvalidOperationException($"Attempting to get {desiredType} when {_currentType} is set");
85+
}
86+
}
87+
88+
public AnyOfType CurrentType
89+
{
90+
get
91+
{
92+
return _currentType;
93+
}
94+
}
95+
96+
public object CurrentValue
97+
{
98+
get
99+
{
100+
return _currentValue;
101+
}
102+
}
103+
104+
public Type CurrentValueType
105+
{
106+
get
107+
{
108+
return _currentValueType;
109+
}
110+
}
111+
112+
public override int GetHashCode()
113+
{
114+
var fields = new object[]
115+
{
116+
_numberOfTypes,
117+
_currentValue,
118+
_currentType,
119+
_first,
120+
_second,
121+
typeof(TFirst),
122+
typeof(TSecond),
123+
};
124+
return HashCodeCalculator.GetHashCode(fields);
125+
}
126+
127+
private bool Equals(AnyOf<TFirst, TSecond> other)
128+
{
129+
return _currentType == other._currentType &&
130+
_numberOfTypes == other._numberOfTypes &&
131+
EqualityComparer<object>.Default.Equals(_currentValue, other._currentValue) &&
132+
EqualityComparer<TFirst>.Default.Equals(_first, other._first) &&
133+
EqualityComparer<TSecond>.Default.Equals(_second, other._second);
134+
}
135+
136+
public static bool operator ==(AnyOf<TFirst, TSecond> obj1, AnyOf<TFirst, TSecond> obj2)
137+
{
138+
return obj1.Equals(obj2);
139+
}
140+
141+
public static bool operator !=(AnyOf<TFirst, TSecond> obj1, AnyOf<TFirst, TSecond> obj2)
142+
{
143+
return !obj1.Equals(obj2);
144+
}
145+
146+
public override bool Equals(object obj)
147+
{
148+
return obj is AnyOf<TFirst, TSecond> o && Equals(o);
149+
}
150+
151+
public override string ToString()
152+
{
153+
return IsUndefined ? null : $"{_currentValue}";
154+
}
155+
}
156+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by https://github.com/StefH/AnyOf.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
using System.Collections.Generic;
11+
using System.Linq;
12+
13+
namespace AnyOfTypes
14+
{
15+
// Code is based on https://github.com/Informatievlaanderen/hashcode-calculator
16+
internal static class HashCodeCalculator
17+
{
18+
public static int GetHashCode(IEnumerable<object> hashFieldValues)
19+
{
20+
const int offset = unchecked((int)2166136261);
21+
const int prime = 16777619;
22+
23+
static int HashCodeAggregator(int hashCode, object value) => value == null
24+
? (hashCode ^ 0) * prime
25+
: (hashCode ^ value.GetHashCode()) * prime;
26+
27+
return hashFieldValues.Aggregate(offset, HashCodeAggregator);
28+
}
29+
}
30+
}

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

+42-11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Linq.Dynamic.Core.Validation;
1111
using System.Linq.Expressions;
1212
using System.Reflection;
13+
using AnyOfTypes;
1314

1415
namespace System.Linq.Dynamic.Core.Parser
1516
{
@@ -734,7 +735,7 @@ private Expression ParseUnary()
734735

735736
private Expression ParsePrimary()
736737
{
737-
Expression expr = ParsePrimaryStart();
738+
var expr = ParsePrimaryStart();
738739
_expressionHelper.WrapConstantExpression(ref expr);
739740

740741
while (true)
@@ -757,6 +758,7 @@ private Expression ParsePrimary()
757758
break;
758759
}
759760
}
761+
760762
return expr;
761763
}
762764

@@ -766,38 +768,55 @@ private Expression ParsePrimaryStart()
766768
{
767769
case TokenId.Identifier:
768770
return ParseIdentifier();
771+
769772
case TokenId.StringLiteral:
770-
return ParseStringLiteral();
773+
var expressionOrType = ParseStringLiteral(false);
774+
return expressionOrType.IsFirst ? expressionOrType.First : ParseTypeAccess(expressionOrType.Second, false);
775+
771776
case TokenId.IntegerLiteral:
772777
return ParseIntegerLiteral();
778+
773779
case TokenId.RealLiteral:
774780
return ParseRealLiteral();
781+
775782
case TokenId.OpenParen:
776783
return ParseParenExpression();
784+
777785
default:
778786
throw ParseError(Res.ExpressionExpected);
779787
}
780788
}
781789

782-
private Expression ParseStringLiteral()
790+
private AnyOf<Expression, Type> ParseStringLiteral(bool forceParseAsString)
783791
{
784792
_textParser.ValidateToken(TokenId.StringLiteral);
785793

786-
string result = StringParser.ParseString(_textParser.CurrentToken.Text);
794+
var stringValue = StringParser.ParseString(_textParser.CurrentToken.Text);
787795

788796
if (_textParser.CurrentToken.Text[0] == '\'')
789797
{
790-
if (result.Length > 1)
798+
if (stringValue.Length > 1)
791799
{
792800
throw ParseError(Res.InvalidCharacterLiteral);
793801
}
794802

795803
_textParser.NextToken();
796-
return ConstantExpressionHelper.CreateLiteral(result[0], result);
804+
return ConstantExpressionHelper.CreateLiteral(stringValue[0], stringValue);
797805
}
798806

799807
_textParser.NextToken();
800-
return ConstantExpressionHelper.CreateLiteral(result, result);
808+
809+
if (_parsingConfig.SupportFullTypeCastingUsingDoubleQuotes && !forceParseAsString && stringValue.Length > 2 && stringValue.Contains('.'))
810+
{
811+
// Try to resolve this string as a type
812+
var type = _typeFinder.FindTypeByName(stringValue, null, false);
813+
if (type is { })
814+
{
815+
return type;
816+
}
817+
}
818+
819+
return ConstantExpressionHelper.CreateLiteral(stringValue, stringValue);
801820
}
802821

803822
private Expression ParseIntegerLiteral()
@@ -844,7 +863,7 @@ private Expression ParseIdentifier()
844863
{
845864
if (value is Type typeValue)
846865
{
847-
return ParseTypeAccess(typeValue);
866+
return ParseTypeAccess(typeValue, true);
848867
}
849868

850869
switch (value)
@@ -1476,10 +1495,13 @@ private Expression ParseLambdaInvocation(LambdaExpression lambda)
14761495
return Expression.Invoke(lambda, args);
14771496
}
14781497

1479-
private Expression ParseTypeAccess(Type type)
1498+
private Expression ParseTypeAccess(Type type, bool getNext)
14801499
{
14811500
int errorPos = _textParser.CurrentToken.Pos;
1482-
_textParser.NextToken();
1501+
if (getNext)
1502+
{
1503+
_textParser.NextToken();
1504+
}
14831505

14841506
if (_textParser.CurrentToken.Id == TokenId.Question)
14851507
{
@@ -1496,7 +1518,16 @@ private Expression ParseTypeAccess(Type type)
14961518
bool shorthand = _textParser.CurrentToken.Id == TokenId.StringLiteral;
14971519
if (_textParser.CurrentToken.Id == TokenId.OpenParen || shorthand)
14981520
{
1499-
Expression[] args = shorthand ? new[] { ParseStringLiteral() } : ParseArgumentList();
1521+
Expression[] args;
1522+
if (shorthand)
1523+
{
1524+
var expressionOrType = ParseStringLiteral(true);
1525+
args = new[] { expressionOrType.First };
1526+
}
1527+
else
1528+
{
1529+
args = ParseArgumentList();
1530+
}
15001531

15011532
// If only 1 argument and
15021533
// - the arg is ConstantExpression, return the conversion

0 commit comments

Comments
 (0)