Skip to content

Commit

Permalink
Fix performance regression in DataServiceContext DefaultResolveType m…
Browse files Browse the repository at this point in the history
…ethod (#2569)
  • Loading branch information
gathogojr authored Dec 8, 2022
1 parent a9760d6 commit 2000fe6
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 33 deletions.
7 changes: 6 additions & 1 deletion src/Microsoft.OData.Client/DataServiceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
153 changes: 123 additions & 30 deletions src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ internal static string GetServerDefinedName(MemberInfo memberInfo)
/// <returns>The server defined type name.</returns>
internal static string GetServerDefinedTypeName(Type type)
{
ODataTypeInfo typeInfo = GetODataTypeInfo(type);
ODataTypeInfo typeInfo = GetODataTypeInfo(type);

return typeInfo.ServerDefinedTypeName;
}
Expand All @@ -478,7 +478,7 @@ internal static string GetServerDefinedTypeFullName(Type type)
internal static string GetClientFieldName(Type t, string serverDefinedName)
{
ODataTypeInfo typeInfo = GetODataTypeInfo(t);

List<string> serverDefinedNames = serverDefinedName.Split(',').Select(name => name.Trim()).ToList();
List<string> clientMemberNames = new List<string>();
foreach (var serverSideName in serverDefinedNames)
Expand Down Expand Up @@ -618,7 +618,7 @@ internal static bool TryGetContainerProperty(object instance, out IDictionary<st
if (containerProperty == null)
{
Type propertyType = propertyInfo.PropertyType;

// Handle Dictionary<,> , SortedDictionary<,> , ConcurrentDictionary<,> , etc - must also have parameterless constructor
if (!propertyType.IsInterface() && !propertyType.IsAbstract() && propertyType.GetInstanceConstructor(true, new Type[0]) != null)
{
Expand All @@ -631,7 +631,7 @@ internal static bool TryGetContainerProperty(object instance, out IDictionary<st
containerProperty = (IDictionary<string, object>)Util.ActivatorCreateInstance(dictionaryType);
}
else
{
{
// Not easy to figure out the implementing type
return false;
}
Expand Down Expand Up @@ -688,59 +688,152 @@ private static bool IsOverride(Type type, PropertyInfo propertyInfo)
}

/// <summary>
/// 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.
/// </summary>
/// <param name="targetAssembly">Assembly expected to contain the proxy classes.</param>
/// <param name="typeName">Name of the type to resolve.</param>
/// <param name="fullNamespace">Namespace of the type.</param>
/// <param name="languageDependentNamespace">Namespace that the resolved type is expected to be.
/// Usually same as <paramref name="fullNamespace"/> but can be different
/// where namespace for client types does not match namespace in service types.</param>
/// <param name="matchedType">The resolved type.</param>
/// <returns>true if type was successfully resolved; otherwise false.</returns>
internal static bool TryResolveType(string typeName, string fullNamespace, string languageDependentNamespace, out Type matchedType)
{
/// <returns>true if a type with the specified name is successfully resolved; otherwise false.</returns>
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<Type> 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;
}

/// <summary>
/// Searches for a type that matches the specified type name from the specified assembly.
/// </summary>
/// <param name="assembly">Assembly that the specified type is expected to be.</param>
/// <param name="qualifiedTypeName">The namespace-qualified name of the type.</param>
/// <param name="serverDefinedName">The namespace-qualified name of corresponding server type.</param>
/// <param name="languageDependentNamespace">Namespace that the specified type is expected to be.</param>
/// <returns>The type if found in the specified assembly; otherwise null.</returns>
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;
}

/// <summary>
/// Checks whether to skip the assembly when trying to find a type to be used for materialization.
/// </summary>
/// <param name="assembly">The assembly to check.</param>
/// <returns>true to skip the assembly; otherwise false.</returns>
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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public partial class CamelCasedTypeMaterializationTests

private const string ServiceUri = "http://tempuri.org/";
private ClientEdmModel clientModel;
private DataServiceContext dataServiceContext;
private Container dataServiceContext;

public CamelCasedTypeMaterializationTests()
{
Expand Down Expand Up @@ -125,6 +125,46 @@ public void MaterializationForEntitySetBoundToBaseEntityTypeCollection()
Assert.Empty(rectangles[0].Attributes);
}

[Fact]
public void MaterializationTypeShouldBeResolvedFromTargetAssembly()
{
var typeName = typeof(Rectangle).FullName;
var typeNamespace = typeName.Substring(0, typeName.LastIndexOf('.'));

var resolvedType = dataServiceContext.DefaultResolveType(typeName, typeNamespace, typeNamespace);

Assert.Equal(typeof(Rectangle), resolvedType);
}

[Fact]
public void MaterializationTypeShouldBeResolvedFromLoadedAssembly()
{
var localClientModel = new ClientEdmModel(ODataProtocolVersion.V4);
// Use of DataServiceContext class directly will cause the materialization type not to be in the target assembly
// since this.GetType().GetAssembly() in DefaultResolveType method will return Microsoft.OData.Client assembly.
// Materialization type will be resolved from a loaded assembly
var localDataServiceContext = new DataServiceContext(
new Uri(ServiceUri),
ODataProtocolVersion.V4,
localClientModel);
localDataServiceContext.UndeclaredPropertyBehavior = UndeclaredPropertyBehavior.Support;

using (var reader = XmlReader.Create(new StringReader(CamelCasedEdmx)))
{
if (CsdlReader.TryParse(reader, out IEdmModel localServiceModel, out _))
{
localDataServiceContext.Format.UseJson(localServiceModel);
}
}

var typeName = typeof(Rectangle).FullName;
var typeNamespace = typeName.Substring(0, typeName.LastIndexOf('.'));

var resolvedType = localDataServiceContext.DefaultResolveType(typeName, typeNamespace, typeNamespace);

Assert.Equal(typeof(Rectangle), resolvedType);
}

private void ConfigureOnMessageCreating(string payload)
{
dataServiceContext.Configurations.RequestPipeline.OnMessageCreating = (args) =>
Expand All @@ -145,7 +185,7 @@ private void ConfigureOnMessageCreating(string payload)
private void InitializeEdmModel()
{
this.clientModel = new ClientEdmModel(ODataProtocolVersion.V4);
this.dataServiceContext = new DataServiceContext(new Uri(ServiceUri), ODataProtocolVersion.V4, this.clientModel);
this.dataServiceContext = new Container(new Uri(ServiceUri), this.clientModel);
this.dataServiceContext.UndeclaredPropertyBehavior = UndeclaredPropertyBehavior.Support;
this.dataServiceContext.ResolveType = (typeName) =>
{
Expand Down Expand Up @@ -186,6 +226,7 @@ private void InitializeEdmModel()

namespace NS.Models
{
using System;
using System.Collections.ObjectModel;
using Microsoft.OData.Client;

Expand Down Expand Up @@ -216,4 +257,13 @@ public Rectangle()
[OriginalName("attributes")]
public ObservableCollection<string> Attributes { get; set; }
}

internal class Container : DataServiceContext
{
internal Container(Uri serviceRoot, ClientEdmModel model)
: base(serviceRoot, ODataProtocolVersion.V4, model)
{

}
}
}

0 comments on commit 2000fe6

Please sign in to comment.