Skip to content

Commit 88cc1fb

Browse files
authored
Performance Fix (#237)
* update some code-comments * fix * update test project * ReflectionTypeLoadException * _cachedCustomTypes * revert ParsingConfig * // Ignore all other exceptions * Include="Newtonsoft.Json" Version="11.0.2"
1 parent f8afeb1 commit 88cc1fb

15 files changed

+190
-44
lines changed

System.Linq.Dynamic.Core.sln

+22
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ Project("{7CF6DF6D-3B04-46F8-A40B-537D21BCA0B4}") = "System.Linq.Dynamic.Core",
7474
{D3804228-91F4-4502-9595-39584E510002} = {D3804228-91F4-4502-9595-39584E510002}
7575
EndProjectSection
7676
EndProject
77+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppPerformanceTest236", "src-console\ConsoleAppPerformanceTest236\ConsoleAppPerformanceTest236.csproj", "{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}"
78+
EndProject
7779
Global
80+
GlobalSection(Performance) = preSolution
81+
HasPerformanceSessions = true
82+
EndGlobalSection
7883
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7984
Debug|Any CPU = Debug|Any CPU
8085
Debug|ARM = Debug|ARM
@@ -388,6 +393,22 @@ Global
388393
{591F9224-A8D6-49CF-8AF8-F9B383C1F9FF}.Release|x64.Build.0 = Release|Any CPU
389394
{591F9224-A8D6-49CF-8AF8-F9B383C1F9FF}.Release|x86.ActiveCfg = Release|Any CPU
390395
{591F9224-A8D6-49CF-8AF8-F9B383C1F9FF}.Release|x86.Build.0 = Release|Any CPU
396+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
397+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Debug|Any CPU.Build.0 = Debug|Any CPU
398+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Debug|ARM.ActiveCfg = Debug|Any CPU
399+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Debug|ARM.Build.0 = Debug|Any CPU
400+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Debug|x64.ActiveCfg = Debug|Any CPU
401+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Debug|x64.Build.0 = Debug|Any CPU
402+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Debug|x86.ActiveCfg = Debug|Any CPU
403+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Debug|x86.Build.0 = Debug|Any CPU
404+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Release|Any CPU.ActiveCfg = Release|Any CPU
405+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Release|Any CPU.Build.0 = Release|Any CPU
406+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Release|ARM.ActiveCfg = Release|Any CPU
407+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Release|ARM.Build.0 = Release|Any CPU
408+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Release|x64.ActiveCfg = Release|Any CPU
409+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Release|x64.Build.0 = Release|Any CPU
410+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Release|x86.ActiveCfg = Release|Any CPU
411+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84}.Release|x86.Build.0 = Release|Any CPU
391412
EndGlobalSection
392413
GlobalSection(SolutionProperties) = preSolution
393414
HideSolutionNode = FALSE
@@ -412,6 +433,7 @@ Global
412433
{F1880F07-238F-4A3A-9E58-141350665E1F} = {7971CAEB-B9F2-416B-966D-2D697C4C1E62}
413434
{926D446C-8358-465A-AFAC-2F9078C22262} = {ECA5702B-5D32-4888-A34E-9461FC533F23}
414435
{591F9224-A8D6-49CF-8AF8-F9B383C1F9FF} = {1384C18E-DCF3-4A5B-9560-2BF5DD8C51CE}
436+
{E9C52E5E-28DC-4D45-B9AB-1B2CF2924A84} = {7971CAEB-B9F2-416B-966D-2D697C4C1E62}
415437
EndGlobalSection
416438
GlobalSection(ExtensibilityGlobals) = postSolution
417439
SolutionGuid = {94C56722-194E-4B8B-BC23-B3F754E89A20}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFrameworks>net472</TargetFrameworks>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.7" />
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\src\System.Linq.Dynamic.Core\System.Linq.Dynamic.Core.csproj" />
14+
</ItemGroup>
15+
16+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace ConsoleAppPerformanceTest236
2+
{
3+
class Program
4+
{
5+
static void Main(string[] args)
6+
{
7+
Test.DoIt();
8+
int x = 0;
9+
}
10+
}
11+
}
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Dynamic.Core;
4+
5+
namespace ConsoleAppPerformanceTest236
6+
{
7+
public static class Test
8+
{
9+
public static void DoIt()
10+
{
11+
var q = User.GenerateSampleModels(10000).AsQueryable();
12+
13+
int count = 100;
14+
for (int i = 0; i <= count; i++)
15+
{
16+
if (i % 10 == 0)
17+
{
18+
Console.WriteLine(i);
19+
}
20+
var result = q.OrderBy("FullName ASC, City DESC").FirstOrDefault();
21+
}
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using RandomDataGenerator.FieldOptions;
4+
using RandomDataGenerator.Randomizers;
5+
6+
namespace ConsoleAppPerformanceTest236
7+
{
8+
public class User
9+
{
10+
public Guid Id { get; set; }
11+
12+
public string FullName { get; set; }
13+
14+
public string City { get; set; }
15+
16+
public int? NullableInt { get; set; }
17+
18+
public int Income { get; set; }
19+
20+
public static IList<User> GenerateSampleModels(int total, bool allowNullableProfiles = false)
21+
{
22+
var list = new List<User>();
23+
24+
var randomizerFullName = RandomizerFactory.GetRandomizer(new FieldOptionsFullName());
25+
var randomizerCity= RandomizerFactory.GetRandomizer(new FieldOptionsCity());
26+
27+
for (int i = 0; i < total; i++)
28+
{
29+
var user = new User
30+
{
31+
Id = Guid.NewGuid(),
32+
FullName = randomizerFullName.Generate(),
33+
City = randomizerCity.Generate(),
34+
Income = 1 + i % 15 * 100
35+
};
36+
37+
list.Add(user);
38+
}
39+
40+
return list.ToArray();
41+
}
42+
}
43+
}

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

+31-28
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,27 @@
11
using JetBrains.Annotations;
22
using System.Collections.Generic;
3-
using System.Reflection;
43
using System.Linq.Dynamic.Core.Validation;
4+
using System.Reflection;
55

66
namespace System.Linq.Dynamic.Core.CustomTypeProviders
77
{
88
/// <summary>
9-
/// The abstract DynamicLinqCustomTypeProvider which is used by the <see cref="IDynamicLinkCustomTypeProvider"/> and can be used by a custom TypeProvider like in .NET Core.
9+
/// The abstract DynamicLinqCustomTypeProvider which is used by the DefaultDynamicLinqCustomTypeProvider and can be used by a custom TypeProvider like in .NET Core.
1010
/// </summary>
1111
public abstract class AbstractDynamicLinqCustomTypeProvider
1212
{
1313
/// <summary>
14-
/// Finds the types marked with DynamicLinqTypeAttribute.
14+
/// Finds the unique types marked with DynamicLinqTypeAttribute.
1515
/// </summary>
1616
/// <param name="assemblies">The assemblies to process.</param>
17-
/// <returns>IEnumerable{Type}</returns>
17+
/// <returns><see cref="IEnumerable{Type}" /></returns>
1818
protected IEnumerable<Type> FindTypesMarkedWithDynamicLinqTypeAttribute([NotNull] IEnumerable<Assembly> assemblies)
1919
{
2020
Check.NotNull(assemblies, nameof(assemblies));
2121
#if !NET35
22-
assemblies = assemblies.Where(x => !x.IsDynamic);
23-
#endif
24-
var definedTypes = GetAssemblyTypes(assemblies);
25-
26-
#if (WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD)
27-
return definedTypes.Where(x => x.CustomAttributes.Any(y => y.AttributeType == typeof(DynamicLinqTypeAttribute))).Select(x => x.AsType());
28-
#else
29-
return definedTypes.Where(x => x.GetCustomAttributes(typeof(DynamicLinqTypeAttribute), false).Any());
22+
assemblies = assemblies.Where(a => !a.IsDynamic);
3023
#endif
24+
return GetAssemblyTypesWithDynamicLinqTypeAttribute(assemblies).Distinct().ToArray();
3125
}
3226

3327
/// <summary>
@@ -41,7 +35,7 @@ protected Type ResolveType([NotNull] IEnumerable<Assembly> assemblies, [NotNull]
4135
Check.NotNull(assemblies, nameof(assemblies));
4236
Check.NotEmpty(typeName, nameof(typeName));
4337

44-
foreach (Assembly assembly in assemblies)
38+
foreach (var assembly in assemblies)
4539
{
4640
Type resolvedType = assembly.GetType(typeName, false, true);
4741
if (resolvedType != null)
@@ -55,28 +49,32 @@ protected Type ResolveType([NotNull] IEnumerable<Assembly> assemblies, [NotNull]
5549

5650
#if (WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD)
5751
/// <summary>
58-
/// Gets the assembly types in an Exception friendly way.
52+
/// Gets the assembly types annotated with <see cref="DynamicLinqTypeAttribute"/> in an Exception friendly way.
5953
/// </summary>
6054
/// <param name="assemblies">The assemblies to process.</param>
61-
/// <returns>IEnumerable{Type}</returns>
62-
protected IEnumerable<TypeInfo> GetAssemblyTypes([NotNull] IEnumerable<Assembly> assemblies)
55+
/// <returns><see cref="IEnumerable{Type}" /></returns>
56+
protected IEnumerable<Type> GetAssemblyTypesWithDynamicLinqTypeAttribute([NotNull] IEnumerable<Assembly> assemblies)
6357
{
6458
Check.NotNull(assemblies, nameof(assemblies));
6559

6660
foreach (var assembly in assemblies)
6761
{
68-
IEnumerable<TypeInfo> definedTypes = null;
62+
Type[] definedTypes = null;
6963

7064
try
7165
{
72-
definedTypes = assembly.DefinedTypes;
66+
definedTypes = assembly.ExportedTypes.Where(t => t.GetTypeInfo().IsDefined(typeof(DynamicLinqTypeAttribute), false)).ToArray();
67+
}
68+
catch (ReflectionTypeLoadException reflectionTypeLoadException)
69+
{
70+
definedTypes = reflectionTypeLoadException.Types;
7371
}
7472
catch
7573
{
76-
// Ignore error
74+
// Ignore all other exceptions
7775
}
7876

79-
if (definedTypes != null)
77+
if (definedTypes != null && definedTypes.Length > 0)
8078
{
8179
foreach (var definedType in definedTypes)
8280
{
@@ -87,28 +85,33 @@ protected IEnumerable<TypeInfo> GetAssemblyTypes([NotNull] IEnumerable<Assembly>
8785
}
8886
#else
8987
/// <summary>
90-
/// Gets the assembly types in an Exception friendly way.
88+
/// Gets the assembly types annotated with <see cref="DynamicLinqTypeAttribute"/> in an Exception friendly way.
9189
/// </summary>
9290
/// <param name="assemblies">The assemblies to process.</param>
93-
/// <returns>IEnumerable{Type}</returns>
94-
protected IEnumerable<Type> GetAssemblyTypes([NotNull] IEnumerable<Assembly> assemblies)
91+
/// <returns><see cref="IEnumerable{Type}" /></returns>
92+
protected IEnumerable<Type> GetAssemblyTypesWithDynamicLinqTypeAttribute([NotNull] IEnumerable<Assembly> assemblies)
9593
{
9694
Check.NotNull(assemblies, nameof(assemblies));
9795

98-
foreach (var assembly in assemblies)
96+
foreach (var assembly in assemblies.Where(a => !a.GlobalAssemblyCache)) // Skip System DLL's
9997
{
100-
IEnumerable<Type> definedTypes = null;
98+
Type[] definedTypes = null;
10199

102100
try
103101
{
104-
definedTypes = assembly.GetTypes();
102+
definedTypes = assembly.GetExportedTypes()
103+
.Where(t => t.IsDefined(typeof(DynamicLinqTypeAttribute), false)).ToArray();
104+
}
105+
catch (ReflectionTypeLoadException reflectionTypeLoadException)
106+
{
107+
definedTypes = reflectionTypeLoadException.Types;
105108
}
106109
catch
107110
{
108-
// Ignore error
111+
// Ignore all other exceptions
109112
}
110113

111-
if (definedTypes != null)
114+
if (definedTypes != null && definedTypes.Length > 0)
112115
{
113116
foreach (var definedType in definedTypes)
114117
{

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

+32-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
#if !(WINDOWS_APP || UAP10_0)
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using System.Linq.Dynamic.Core.Validation;
43
using System.Reflection;
54

65
namespace System.Linq.Dynamic.Core.CustomTypeProviders
76
{
87
/// <summary>
9-
/// The default <see cref="IDynamicLinkCustomTypeProvider"/>.
8+
/// The default implementation for <see cref="IDynamicLinkCustomTypeProvider"/>.
9+
///
1010
/// Scans the current AppDomain for all types marked with <see cref="DynamicLinqTypeAttribute"/>, and adds them as custom Dynamic Link types.
1111
///
1212
/// Also provides functionality to resolve a Type in the current Application Domain.
@@ -16,12 +16,33 @@ namespace System.Linq.Dynamic.Core.CustomTypeProviders
1616
public class DefaultDynamicLinqCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider
1717
{
1818
private readonly IAssemblyHelper _assemblyHelper = new DefaultAssemblyHelper();
19+
private readonly bool _cacheCustomTypes;
20+
21+
private HashSet<Type> _cachedCustomTypes;
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="DefaultDynamicLinqCustomTypeProvider"/> class.
25+
/// </summary>
26+
/// <param name="cacheCustomTypes">Defines whether to cache the CustomTypes which are found in the Application Domain. Default set to 'true'.</param>
27+
public DefaultDynamicLinqCustomTypeProvider(bool cacheCustomTypes = true)
28+
{
29+
_cacheCustomTypes = cacheCustomTypes;
30+
}
1931

2032
/// <inheritdoc cref="IDynamicLinkCustomTypeProvider.GetCustomTypes"/>
2133
public virtual HashSet<Type> GetCustomTypes()
2234
{
23-
IEnumerable<Assembly> assemblies = _assemblyHelper.GetAssemblies();
24-
return new HashSet<Type>(FindTypesMarkedWithDynamicLinqTypeAttribute(assemblies));
35+
if (_cacheCustomTypes)
36+
{
37+
if (_cachedCustomTypes == null)
38+
{
39+
_cachedCustomTypes = GetCustomTypesInternal();
40+
}
41+
42+
return _cachedCustomTypes;
43+
}
44+
45+
return GetCustomTypesInternal();
2546
}
2647

2748
/// <inheritdoc cref="IDynamicLinkCustomTypeProvider.ResolveType"/>
@@ -32,6 +53,11 @@ public Type ResolveType(string typeName)
3253
IEnumerable<Assembly> assemblies = _assemblyHelper.GetAssemblies();
3354
return ResolveType(assemblies, typeName);
3455
}
56+
57+
private HashSet<Type> GetCustomTypesInternal()
58+
{
59+
IEnumerable<Assembly> assemblies = _assemblyHelper.GetAssemblies();
60+
return new HashSet<Type>(FindTypesMarkedWithDynamicLinqTypeAttribute(assemblies));
61+
}
3562
}
3663
}
37-
#endif

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System.Collections.Generic;
2-
using JetBrains.Annotations;
1+
using JetBrains.Annotations;
2+
using System.Collections.Generic;
33

44
namespace System.Linq.Dynamic.Core.CustomTypeProviders
55
{
@@ -11,7 +11,7 @@ public interface IDynamicLinkCustomTypeProvider
1111
/// <summary>
1212
/// Returns a list of custom types that System.Linq.Dynamic.Core will understand.
1313
/// </summary>
14-
/// <returns>A <see cref="HashSet&lt;Type&gt;" /> list of custom types.</returns>
14+
/// <returns>A <see cref="HashSet{Type}" /> list of custom types.</returns>
1515
HashSet<Type> GetCustomTypes();
1616

1717
/// <summary>

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -1564,7 +1564,7 @@ Type FindType(string name)
15641564
{
15651565
_keywordsHelper.TryGetValue(name, out object type);
15661566

1567-
var result = type as Type;
1567+
Type result = type as Type;
15681568
if (result != null)
15691569
{
15701570
return result;
@@ -1584,6 +1584,7 @@ Type FindType(string name)
15841584
{
15851585
return _root.Type;
15861586
}
1587+
15871588
if (_it != null && _it.Type.Namespace + "." + _it.Type.Name == name)
15881589
{
15891590
return _it.Type;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public IDynamicLinkCustomTypeProvider CustomTypeProvider
3535
get
3636
{
3737
#if !(DOTNET5_1 || WINDOWS_APP || UAP10_0 || NETSTANDARD)
38-
// only use DefaultDynamicLinqCustomTypeProvider if not WINDOWS_APP || UAP10_0 || NETSTANDARD
38+
// only use DefaultDynamicLinqCustomTypeProvider for full .NET Framework
3939
return _customTypeProvider ?? (_customTypeProvider = new DefaultDynamicLinqCustomTypeProvider());
4040
#else
4141
return _customTypeProvider;

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@
6464
<Reference Include="Moq, Version=4.10.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
6565
<HintPath>..\..\packages\Moq.4.10.0\lib\net45\Moq.dll</HintPath>
6666
</Reference>
67-
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
68-
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
67+
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
68+
<HintPath>..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
6969
</Reference>
7070
<Reference Include="NFluent, Version=2.1.1.107, Culture=neutral, PublicKeyToken=18828b37b84b1437, processorArchitecture=MSIL">
7171
<HintPath>..\..\packages\NFluent.2.1.1\lib\net45\NFluent.dll</HintPath>

0 commit comments

Comments
 (0)