Skip to content

Commit 54208a7

Browse files
authored
Resolve types by simple name #252 (#254)
* SimpleName * add tests * solve code issue * fix code complexity * e != null * TypeFinder * . * add test
1 parent 58fc05a commit 54208a7

20 files changed

+316
-100
lines changed

src-console/ConsoleAppEF2.0.2_InMemory/Program.cs

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public Type ResolveType(string typeName)
4141

4242
return ResolveType(assemblies, typeName);
4343
}
44+
45+
public Type ResolveTypeBySimpleName(string typeName)
46+
{
47+
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
48+
return ResolveTypeBySimpleName(assemblies, typeName);
49+
}
4450
}
4551

4652
private static IQueryable GetQueryable()

src-console/ConsoleAppEF2.0/Program.cs

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ public Type ResolveType(string typeName)
3030
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
3131
return ResolveType(assemblies, typeName);
3232
}
33+
34+
public Type ResolveTypeBySimpleName(string typeName)
35+
{
36+
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
37+
return ResolveTypeBySimpleName(assemblies, typeName);
38+
}
3339
}
3440

3541
private static IQueryable GetQueryable()

src-console/ConsoleAppEF2.1.1/Program.cs

+6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public Type ResolveType(string typeName)
3131

3232
return ResolveType(assemblies, typeName);
3333
}
34+
35+
public Type ResolveTypeBySimpleName(string typeName)
36+
{
37+
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
38+
return ResolveTypeBySimpleName(assemblies, typeName);
39+
}
3440
}
3541

3642
private static IQueryable GetQueryable()

src-console/ConsoleAppEF2.1.1_InMemory/Program.cs

+6
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ private static void OfTypeAndCastTests(TestContext context, ParsingConfig config
189189
var oftypeDynamicWithType = context.BaseDtos.OfType(typeof(TestDto)).ToDynamicArray();
190190
var oftypeDynamicWithString = context.BaseDtos.OfType(config, "ConsoleAppEF2.Database.TestDto").ToDynamicArray();
191191

192+
var configX = new ParsingConfig
193+
{
194+
ResolveTypesBySimpleName = true
195+
};
196+
var oftypeDynamicWithSimpleNameString = context.BaseDtos.OfType(configX, "TestDto").ToDynamicArray();
197+
192198
int isOfType = context.BaseDtos.Count(b => b is TestDto);
193199
int isOfTypeDynamicTestDto = context.BaseDtos.Count(config, "OfType(\"ConsoleAppEF2.Database.TestDto\")");
194200
int isOfTypeDynamicOtherTestDto = context.BaseDtos.Count(config, "OfType(\"ConsoleAppEF2.Database.OtherTestDto\")");

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

+29
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,35 @@ protected Type ResolveType([NotNull] IEnumerable<Assembly> assemblies, [NotNull]
4747
return null;
4848
}
4949

50+
/// <summary>
51+
/// Resolve a type by the simple name which is registered in the current application domain.
52+
/// </summary>
53+
/// <param name="assemblies">The assemblies to inspect.</param>
54+
/// <param name="simpleTypeName">The simple typename to resolve.</param>
55+
/// <returns>A resolved <see cref="Type"/> or null when not found.</returns>
56+
protected Type ResolveTypeBySimpleName([NotNull] IEnumerable<Assembly> assemblies, [NotNull] string simpleTypeName)
57+
{
58+
Check.NotNull(assemblies, nameof(assemblies));
59+
Check.NotEmpty(simpleTypeName, nameof(simpleTypeName));
60+
61+
foreach (var assembly in assemblies)
62+
{
63+
var fullnames = assembly.GetTypes().Select(t => t.FullName).Distinct();
64+
var firstMatchingFullname = fullnames.FirstOrDefault(fn => fn.EndsWith($".{simpleTypeName}"));
65+
66+
if (firstMatchingFullname != null)
67+
{
68+
Type resolvedType = assembly.GetType(firstMatchingFullname, false, true);
69+
if (resolvedType != null)
70+
{
71+
return resolvedType;
72+
}
73+
}
74+
}
75+
76+
return null;
77+
}
78+
5079
#if (WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD)
5180
/// <summary>
5281
/// Gets the assembly types annotated with <see cref="DynamicLinqTypeAttribute"/> in an Exception friendly way.

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

+9
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ public Type ResolveType(string typeName)
5454
return ResolveType(assemblies, typeName);
5555
}
5656

57+
/// <inheritdoc cref="IDynamicLinkCustomTypeProvider.ResolveTypeBySimpleName"/>
58+
public Type ResolveTypeBySimpleName(string simpleTypeName)
59+
{
60+
Check.NotEmpty(simpleTypeName, nameof(simpleTypeName));
61+
62+
IEnumerable<Assembly> assemblies = _assemblyHelper.GetAssemblies();
63+
return ResolveTypeBySimpleName(assemblies, simpleTypeName);
64+
}
65+
5766
private HashSet<Type> GetCustomTypesInternal()
5867
{
5968
IEnumerable<Assembly> assemblies = _assemblyHelper.GetAssemblies();

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@ public interface IDynamicLinkCustomTypeProvider
1515
HashSet<Type> GetCustomTypes();
1616

1717
/// <summary>
18-
/// Resolve any type which is registered in the current application domain.
18+
/// Resolve any type by fullname which is registered in the current application domain.
1919
/// </summary>
2020
/// <param name="typeName">The typename to resolve.</param>
2121
/// <returns>A resolved <see cref="Type"/> or null when not found.</returns>
2222
Type ResolveType([NotNull] string typeName);
23+
24+
/// <summary>
25+
/// Resolve any type by the simple name which is registered in the current application domain.
26+
/// </summary>
27+
/// <param name="simpleTypeName">The typename to resolve.</param>
28+
/// <returns>A resolved <see cref="Type"/> or null when not found.</returns>
29+
Type ResolveTypeBySimpleName([NotNull] string simpleTypeName);
2330
}
2431
}

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

+7-14
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,10 @@ public static IQueryable Cast([NotNull] this IQueryable source, [NotNull] Parsin
229229
Check.NotNull(config, nameof(config));
230230
Check.NotEmpty(typeName, nameof(typeName));
231231

232-
config.AllowNewToEvaluateAnyType = true;
233-
var newExpression = DynamicExpressionParser.ParseLambda(config, null, $"new {typeName}()");
232+
var finder = new TypeFinder(config, new KeywordsHelper(config));
233+
Type type = finder.FindTypeByName(typeName, null, true);
234234

235-
return Cast(source, newExpression.Body.Type);
235+
return Cast(source, type);
236236
}
237237

238238
/// <summary>
@@ -1025,10 +1025,10 @@ public static IQueryable OfType([NotNull] this IQueryable source, [NotNull] Pars
10251025
Check.NotNull(config, nameof(config));
10261026
Check.NotEmpty(typeName, nameof(typeName));
10271027

1028-
config.AllowNewToEvaluateAnyType = true;
1029-
var newExpression = DynamicExpressionParser.ParseLambda(config, null, $"new {typeName}()");
1028+
var finder = new TypeFinder(config, new KeywordsHelper(config));
1029+
Type type = finder.FindTypeByName(typeName, null, true);
10301030

1031-
return OfType(source, newExpression.Body.Type);
1031+
return OfType(source, type);
10321032
}
10331033

10341034
/// <summary>
@@ -2156,14 +2156,7 @@ private static TResult Execute<TResult>(MethodInfo operatorMethodInfo, IQueryabl
21562156

21572157
private static MethodInfo GetGenericMethod(string name)
21582158
{
2159-
try
2160-
{
2161-
return typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => mi.IsGenericMethod);
2162-
}
2163-
catch (Exception ex)
2164-
{
2165-
throw new Exception("Method not found: " + name, ex);
2166-
}
2159+
return typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => mi.IsGenericMethod);
21672160
}
21682161

21692162
private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<MethodInfo, bool> predicate = null)

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

+5-51
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ public class ExpressionParser
2525

2626
private readonly ParsingConfig _parsingConfig;
2727
private readonly MethodFinder _methodFinder;
28-
private readonly KeywordsHelper _keywordsHelper;
28+
private readonly IKeywordsHelper _keywordsHelper;
2929
private readonly TextParser _textParser;
3030
private readonly IExpressionHelper _expressionHelper;
31+
private readonly ITypeFinder _typeFinder;
3132
private readonly Dictionary<string, object> _internals;
3233
private readonly Dictionary<string, object> _symbols;
3334

@@ -73,6 +74,7 @@ public ExpressionParser([CanBeNull] ParameterExpression[] parameters, [NotNull]
7374
_textParser = new TextParser(expression);
7475
_methodFinder = new MethodFinder(_parsingConfig);
7576
_expressionHelper = new ExpressionHelper(_parsingConfig);
77+
_typeFinder = new TypeFinder(_parsingConfig, _keywordsHelper);
7678
}
7779

7880
void ProcessParameters(ParameterExpression[] parameters)
@@ -1242,7 +1244,7 @@ Expression ParseNew()
12421244
_textParser.NextToken();
12431245
}
12441246

1245-
newType = FindType(newTypeName);
1247+
newType = _typeFinder.FindTypeByName(newTypeName, new[] { _it, _parent, _root }, false);
12461248
if (newType == null)
12471249
{
12481250
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName);
@@ -1654,54 +1656,6 @@ Expression ParseMemberAccess(Type type, Expression instance)
16541656
throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type));
16551657
}
16561658

1657-
Type FindType(string name)
1658-
{
1659-
_keywordsHelper.TryGetValue(name, out object type);
1660-
1661-
Type result = type as Type;
1662-
if (result != null)
1663-
{
1664-
return result;
1665-
}
1666-
1667-
if (_it != null && _it.Type.Name == name)
1668-
{
1669-
return _it.Type;
1670-
}
1671-
1672-
if (_parent != null && _parent.Type.Name == name)
1673-
{
1674-
return _parent.Type;
1675-
}
1676-
1677-
if (_root != null && _root.Type.Name == name)
1678-
{
1679-
return _root.Type;
1680-
}
1681-
1682-
if (_it != null && _it.Type.Namespace + "." + _it.Type.Name == name)
1683-
{
1684-
return _it.Type;
1685-
}
1686-
1687-
if (_parent != null && _parent.Type.Namespace + "." + _parent.Type.Name == name)
1688-
{
1689-
return _parent.Type;
1690-
}
1691-
1692-
if (_root != null && _root.Type.Namespace + "." + _root.Type.Name == name)
1693-
{
1694-
return _root.Type;
1695-
}
1696-
1697-
if (_parsingConfig.AllowNewToEvaluateAnyType && _parsingConfig.CustomTypeProvider != null)
1698-
{
1699-
return _parsingConfig.CustomTypeProvider.ResolveType(name);
1700-
}
1701-
1702-
return null;
1703-
}
1704-
17051659
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos, bool isQueryable)
17061660
{
17071661
var oldParent = _parent;
@@ -1806,7 +1760,7 @@ private Type ResolveTypeFromArgumentExpression(string functionName, Expression a
18061760
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresOneNotNullArg, functionName, typeName);
18071761
}
18081762

1809-
Type resultType = FindType(typeName);
1763+
Type resultType = _typeFinder.FindTypeByName(typeName, new[] { _it, _parent, _root }, true);
18101764
if (resultType == null)
18111765
{
18121766
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeName);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace System.Linq.Dynamic.Core.Parser
2+
{
3+
interface IKeywordsHelper
4+
{
5+
bool TryGetValue(string name, out object type);
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Linq.Expressions;
2+
using JetBrains.Annotations;
3+
4+
namespace System.Linq.Dynamic.Core.Parser
5+
{
6+
interface ITypeFinder
7+
{
8+
Type FindTypeByName([NotNull] string name, [CanBeNull] ParameterExpression[] expressions, bool forceUseCustomTypeProvider);
9+
}
10+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace System.Linq.Dynamic.Core.Parser
44
{
5-
internal class KeywordsHelper
5+
internal class KeywordsHelper : IKeywordsHelper
66
{
77
public const string SYMBOL_IT = "$";
88
public const string SYMBOL_PARENT = "^";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using JetBrains.Annotations;
2+
using System.Linq.Dynamic.Core.Validation;
3+
using System.Linq.Expressions;
4+
5+
namespace System.Linq.Dynamic.Core.Parser
6+
{
7+
internal class TypeFinder : ITypeFinder
8+
{
9+
private readonly IKeywordsHelper _keywordsHelper;
10+
private readonly ParsingConfig _parsingConfig;
11+
12+
public TypeFinder([NotNull] ParsingConfig parsingConfig, [NotNull] IKeywordsHelper keywordsHelper)
13+
{
14+
Check.NotNull(parsingConfig, nameof(parsingConfig));
15+
Check.NotNull(keywordsHelper, nameof(keywordsHelper));
16+
17+
_keywordsHelper = keywordsHelper;
18+
_parsingConfig = parsingConfig;
19+
}
20+
21+
public Type FindTypeByName(string name, ParameterExpression[] expressions, bool forceUseCustomTypeProvider)
22+
{
23+
Check.NotEmpty(name, nameof(name));
24+
25+
_keywordsHelper.TryGetValue(name, out object type);
26+
27+
Type result = type as Type;
28+
if (result != null)
29+
{
30+
return result;
31+
}
32+
33+
if (expressions != null && TryResolveTypeUsingExpressions(name, expressions, out result))
34+
{
35+
return result;
36+
}
37+
38+
return ResolveTypeByUsingCustomTypeProvider(name, forceUseCustomTypeProvider);
39+
}
40+
41+
private Type ResolveTypeByUsingCustomTypeProvider(string name, bool forceUseCustomTypeProvider)
42+
{
43+
if ((forceUseCustomTypeProvider || _parsingConfig.AllowNewToEvaluateAnyType) && _parsingConfig.CustomTypeProvider != null)
44+
{
45+
Type resolvedType = _parsingConfig.CustomTypeProvider.ResolveType(name);
46+
if (resolvedType != null)
47+
{
48+
return resolvedType;
49+
}
50+
51+
// In case the type is not found based on fullname, try to get the type on simplename if allowed
52+
if (_parsingConfig.ResolveTypesBySimpleName)
53+
{
54+
return _parsingConfig.CustomTypeProvider.ResolveTypeBySimpleName(name);
55+
}
56+
}
57+
58+
return null;
59+
}
60+
61+
private bool TryResolveTypeUsingExpressions(string name, ParameterExpression[] expressions, out Type result)
62+
{
63+
foreach (var expression in expressions.Where(e => e != null))
64+
{
65+
if (expression.Type.Name == name)
66+
{
67+
result = expression.Type;
68+
return true;
69+
}
70+
71+
if ($"{expression.Type.Namespace}.{expression.Type.Name}" == name)
72+
{
73+
result = expression.Type;
74+
return true;
75+
}
76+
77+
if (_parsingConfig.ResolveTypesBySimpleName && _parsingConfig.CustomTypeProvider != null)
78+
{
79+
string possibleFullName = $"{expression.Type.Namespace}.{name}";
80+
var resolvedType = _parsingConfig.CustomTypeProvider.ResolveType(possibleFullName);
81+
if (resolvedType != null)
82+
{
83+
result = resolvedType;
84+
return true;
85+
}
86+
}
87+
}
88+
89+
result = null;
90+
return false;
91+
}
92+
}
93+
}

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

+8
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,13 @@ public IQueryableAnalyzer QueryableAnalyzer
131131
/// where a member access on a non existing member happens. Default value is false.
132132
/// </summary>
133133
public bool DisableMemberAccessToIndexAccessorFallback { get; set; } = false;
134+
135+
/// <summary>
136+
/// By default finding types by a simple name is not suported.
137+
/// Use this flag to use the CustomTypeProvider to resolve types by a simple name like "Employee" instead of "MyDatabase.Entities.Employee".
138+
/// Note that a first matching type is returned and this functionality needs to scan all types from all assemblies, so use with caution.
139+
/// Default value is false.
140+
/// </summary>
141+
public bool ResolveTypesBySimpleName { get; set; } = false;
134142
}
135143
}

0 commit comments

Comments
 (0)