From 74071f8ed7382ab8eb1c39b8148dd967be825efa Mon Sep 17 00:00:00 2001 From: John Gathogo Date: Mon, 28 Nov 2022 18:47:16 +0300 Subject: [PATCH] Fix performance regression in DataServiceContext DefaultResolveType method --- .../DataServiceContext.cs | 7 +- .../Metadata/ClientTypeUtil.cs | 153 ++++++++++++++---- 2 files changed, 129 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.OData.Client/DataServiceContext.cs b/src/Microsoft.OData.Client/DataServiceContext.cs index 2c4d9d26ef..a9d6ae7308 100644 --- a/src/Microsoft.OData.Client/DataServiceContext.cs +++ b/src/Microsoft.OData.Client/DataServiceContext.cs @@ -3279,7 +3279,12 @@ protected internal Type DefaultResolveType(string typeName, string fullNamespace if (!this.resolveTypesCache.TryGetValue(typeName, out matchedType)) { - if (ClientTypeUtil.TryResolveType(typeName, fullNamespace, languageDependentNamespace, out matchedType)) + if (ClientTypeUtil.TryResolveType( + this.GetType().GetAssembly(), + typeName, + fullNamespace, + languageDependentNamespace, + out matchedType)) { this.resolveTypesCache.TryAdd(typeName, matchedType); diff --git a/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs b/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs index 192f31597c..de8231be54 100644 --- a/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs +++ b/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs @@ -454,7 +454,7 @@ internal static string GetServerDefinedName(MemberInfo memberInfo) /// The server defined type name. internal static string GetServerDefinedTypeName(Type type) { - ODataTypeInfo typeInfo = GetODataTypeInfo(type); + ODataTypeInfo typeInfo = GetODataTypeInfo(type); return typeInfo.ServerDefinedTypeName; } @@ -478,7 +478,7 @@ internal static string GetServerDefinedTypeFullName(Type type) internal static string GetClientFieldName(Type t, string serverDefinedName) { ODataTypeInfo typeInfo = GetODataTypeInfo(t); - + List serverDefinedNames = serverDefinedName.Split(',').Select(name => name.Trim()).ToList(); List clientMemberNames = new List(); foreach (var serverSideName in serverDefinedNames) @@ -618,7 +618,7 @@ internal static bool TryGetContainerProperty(object instance, out IDictionary , SortedDictionary<,> , ConcurrentDictionary<,> , etc - must also have parameterless constructor if (!propertyType.IsInterface() && !propertyType.IsAbstract() && propertyType.GetInstanceConstructor(true, new Type[0]) != null) { @@ -631,7 +631,7 @@ internal static bool TryGetContainerProperty(object instance, out IDictionary)Util.ActivatorCreateInstance(dictionaryType); } else - { + { // Not easy to figure out the implementing type return false; } @@ -688,59 +688,152 @@ private static bool IsOverride(Type type, PropertyInfo propertyInfo) } /// - /// Tries to resolve a type with specified name from the loaded assemblies. + /// Tries to resolve a type with specified name, first from the specified assembly and then from other loaded assemblies. /// + /// Assembly expected to contain the proxy classes. /// Name of the type to resolve. /// Namespace of the type. /// Namespace that the resolved type is expected to be. /// Usually same as but can be different /// where namespace for client types does not match namespace in service types. /// The resolved type. - /// true if type was successfully resolved; otherwise false. - internal static bool TryResolveType(string typeName, string fullNamespace, string languageDependentNamespace, out Type matchedType) - { + /// true if a type with the specified name is successfully resolved; otherwise false. + internal static bool TryResolveType( + Assembly targetAssembly, + string typeName, + string fullNamespace, + string languageDependentNamespace, + out Type matchedType) + { + Debug.Assert(targetAssembly != null, "targetAssembly != null"); Debug.Assert(typeName != null, "typeName != null"); - matchedType = null; - int namespaceLength = fullNamespace?.Length ?? 0; - string serverDefinedName = typeName.Substring(namespaceLength + 1); + int fullNamespaceLength = fullNamespace?.Length ?? 0; + string qualifiedTypeName = string.Concat(languageDependentNamespace, typeName.Substring(fullNamespaceLength)); + string serverDefinedName = fullNamespaceLength > 0 ? typeName.Substring(fullNamespaceLength + 1) : typeName; - // Searching only loaded assemblies, not referenced assemblies - foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + // We first try to look for the type from the assembly expected to contain the proxy classes + matchedType = FindType( + targetAssembly, + qualifiedTypeName, + serverDefinedName, + languageDependentNamespace); + + if (matchedType != null) + { + return true; + } + + var entryAssembly = Assembly.GetEntryAssembly(); + if (entryAssembly != null && !entryAssembly.Equals(targetAssembly)) { - matchedType = assembly.GetType(string.Concat(languageDependentNamespace, typeName.Substring(namespaceLength)), false); + // Next, we try to look for the type from the entry assembly + matchedType = FindType( + entryAssembly, + qualifiedTypeName, + serverDefinedName, + languageDependentNamespace); + if (matchedType != null) { return true; } + } - IEnumerable types = null; - - try + // Searching only loaded assemblies, not referenced assemblies + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.Equals(targetAssembly) + || assembly.Equals(entryAssembly) + || SkipAssembly(assembly)) { - types = assembly.GetTypes(); + continue; } - catch (ReflectionTypeLoadException) + + matchedType = FindType( + assembly, + qualifiedTypeName, + serverDefinedName, + languageDependentNamespace); + + if (matchedType != null) { - // Ignore + return true; } + } - if (types != null) + return false; + } + + /// + /// Searches for a type that matches the specified type name from the specified assembly. + /// + /// Assembly that the specified type is expected to be. + /// The namespace-qualified name of the type. + /// The namespace-qualified name of corresponding server type. + /// Namespace that the specified type is expected to be. + /// The type if found in the specified assembly; otherwise null. + private static Type FindType( + Assembly assembly, + string qualifiedTypeName, + string serverDefinedName, + string languageDependentNamespace) + { + Type matchedType = assembly.GetType(qualifiedTypeName, throwOnError: false); + + if (matchedType != null) + { + return matchedType; + } + + Type[] types = null; + + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException) + { + // Ignore + } + + if (types != null) + { + for (int i = 0; i < types.Length; i++) { - foreach (Type type in types) + Type type = types[i]; + + object[] originalNameAttributes = type.GetCustomAttributes(typeof(OriginalNameAttribute), inherit: true); + if (originalNameAttributes.Length == 0) { - OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)type.GetCustomAttributes(typeof(OriginalNameAttribute), true).SingleOrDefault(); - if (string.Equals(originalNameAttribute?.OriginalName, serverDefinedName, StringComparison.Ordinal) - && type.Namespace.Equals(languageDependentNamespace, StringComparison.Ordinal)) - { - matchedType = type; - return true; - } + continue; + } + + OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)originalNameAttributes[0]; + if (string.Equals(originalNameAttribute.OriginalName, serverDefinedName, StringComparison.Ordinal) + && type.Namespace.Equals(languageDependentNamespace, StringComparison.Ordinal)) + { + matchedType = type; } } } - return false; + return matchedType; + } + + /// + /// Checks whether to skip the assembly when trying to find a type to be used for materialization. + /// + /// The assembly to check. + /// true to skip the assembly; otherwise false. + private static bool SkipAssembly(Assembly assembly) + { + return assembly.Equals(typeof(string).Assembly) // mscorlib assembly + || assembly.Equals(typeof(Uri).Assembly) // Common types assembly + || assembly.Equals(typeof(ClientEdmModel).Assembly) // OData client assembly + || assembly.Equals(typeof(ODataItem).Assembly) // OData core assembly + || assembly.Equals(typeof(EdmModel).Assembly) // OData Edm assembly + || assembly.Equals(typeof(Spatial.Geography).Assembly); // Spatial assembly } } }